Update Kirby and dependencies

This commit is contained in:
Paul Nicoué 2022-08-31 15:02:43 +02:00
parent 503b339974
commit 399fa20902
439 changed files with 66915 additions and 64442 deletions

View file

@ -19,147 +19,147 @@ use Kirby\Toolkit\Dom;
*/
class DomHandler extends Handler
{
/**
* List of all MIME types that may
* be used in data URIs
*
* @var array
*/
public static $allowedDataUris = [
'data:image/png',
'data:image/gif',
'data:image/jpg',
'data:image/jpe',
'data:image/pjp',
'data:img/png',
'data:img/gif',
'data:img/jpg',
'data:img/jpe',
'data:img/pjp',
];
/**
* List of all MIME types that may
* be used in data URIs
*
* @var array
*/
public static $allowedDataUris = [
'data:image/png',
'data:image/gif',
'data:image/jpg',
'data:image/jpe',
'data:image/pjp',
'data:img/png',
'data:img/gif',
'data:img/jpg',
'data:img/jpe',
'data:img/pjp',
];
/**
* Allowed hostnames for HTTP(S) URLs
*
* @var array
*/
public static $allowedDomains = [];
/**
* Allowed hostnames for HTTP(S) URLs
*
* @var array
*/
public static $allowedDomains = [];
/**
* Names of allowed XML processing instructions
*
* @var array
*/
public static $allowedPIs = [];
/**
* Names of allowed XML processing instructions
*
* @var array
*/
public static $allowedPIs = [];
/**
* The document type (`'HTML'` or `'XML'`)
* (to be set in child classes)
*
* @var string
*/
protected static $type = 'XML';
/**
* The document type (`'HTML'` or `'XML'`)
* (to be set in child classes)
*
* @var string
*/
protected static $type = 'XML';
/**
* Sanitizes the given string
*
* @param string $string
* @return string
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
*/
public static function sanitize(string $string): string
{
$dom = static::parse($string);
$dom->sanitize(static::options());
return $dom->toString();
}
/**
* Sanitizes the given string
*
* @param string $string
* @return string
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
*/
public static function sanitize(string $string): string
{
$dom = static::parse($string);
$dom->sanitize(static::options());
return $dom->toString();
}
/**
* Validates file contents
*
* @param string $string
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
*/
public static function validate(string $string): void
{
$dom = static::parse($string);
$errors = $dom->sanitize(static::options());
if (count($errors) > 0) {
// there may be multiple errors, we can only throw one of them at a time
throw $errors[0];
}
}
/**
* Validates file contents
*
* @param string $string
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
*/
public static function validate(string $string): void
{
$dom = static::parse($string);
$errors = $dom->sanitize(static::options());
if (count($errors) > 0) {
// there may be multiple errors, we can only throw one of them at a time
throw $errors[0];
}
}
/**
* Custom callback for additional attribute sanitization
* @internal
*
* @param \DOMAttr $attr
* @return array Array with exception objects for each modification
*/
public static function sanitizeAttr(DOMAttr $attr): array
{
// to be extended in child classes
return [];
}
/**
* Custom callback for additional attribute sanitization
* @internal
*
* @param \DOMAttr $attr
* @return array Array with exception objects for each modification
*/
public static function sanitizeAttr(DOMAttr $attr): array
{
// to be extended in child classes
return [];
}
/**
* Custom callback for additional element sanitization
* @internal
*
* @param \DOMElement $element
* @return array Array with exception objects for each modification
*/
public static function sanitizeElement(DOMElement $element): array
{
// to be extended in child classes
return [];
}
/**
* Custom callback for additional element sanitization
* @internal
*
* @param \DOMElement $element
* @return array Array with exception objects for each modification
*/
public static function sanitizeElement(DOMElement $element): array
{
// to be extended in child classes
return [];
}
/**
* Custom callback for additional doctype validation
* @internal
*
* @param \DOMDocumentType $doctype
* @return void
*/
public static function validateDoctype(DOMDocumentType $doctype): void
{
// to be extended in child classes
}
/**
* Custom callback for additional doctype validation
* @internal
*
* @param \DOMDocumentType $doctype
* @return void
*/
public static function validateDoctype(DOMDocumentType $doctype): void
{
// to be extended in child classes
}
/**
* Returns the sanitization options for the handler
* (to be extended in child classes)
*
* @return array
*/
protected static function options(): array
{
return [
'allowedDataUris' => static::$allowedDataUris,
'allowedDomains' => static::$allowedDomains,
'allowedPIs' => static::$allowedPIs,
'attrCallback' => [static::class, 'sanitizeAttr'],
'doctypeCallback' => [static::class, 'validateDoctype'],
'elementCallback' => [static::class, 'sanitizeElement'],
];
}
/**
* Returns the sanitization options for the handler
* (to be extended in child classes)
*
* @return array
*/
protected static function options(): array
{
return [
'allowedDataUris' => static::$allowedDataUris,
'allowedDomains' => static::$allowedDomains,
'allowedPIs' => static::$allowedPIs,
'attrCallback' => [static::class, 'sanitizeAttr'],
'doctypeCallback' => [static::class, 'validateDoctype'],
'elementCallback' => [static::class, 'sanitizeElement'],
];
}
/**
* Parses the given string into a `Toolkit\Dom` object
*
* @param string $string
* @return \Kirby\Toolkit\Dom
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
*/
protected static function parse(string $string)
{
return new Dom($string, static::$type);
}
/**
* Parses the given string into a `Toolkit\Dom` object
*
* @param string $string
* @return \Kirby\Toolkit\Dom
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
*/
protected static function parse(string $string)
{
return new Dom($string, static::$type);
}
}

View file

@ -19,73 +19,73 @@ use Kirby\Filesystem\F;
*/
abstract class Handler
{
/**
* Sanitizes the given string
*
* @param string $string
* @return string
*/
abstract public static function sanitize(string $string): string;
/**
* Sanitizes the given string
*
* @param string $string
* @return string
*/
abstract public static function sanitize(string $string): string;
/**
* Sanitizes the contents of a file by overwriting
* the file with the sanitized version
*
* @param string $file
* @return void
*
* @throws \Kirby\Exception\Exception If the file does not exist
* @throws \Kirby\Exception\Exception On other errors
*/
public static function sanitizeFile(string $file): void
{
$sanitized = static::sanitize(static::readFile($file));
F::write($file, $sanitized);
}
/**
* Sanitizes the contents of a file by overwriting
* the file with the sanitized version
*
* @param string $file
* @return void
*
* @throws \Kirby\Exception\Exception If the file does not exist
* @throws \Kirby\Exception\Exception On other errors
*/
public static function sanitizeFile(string $file): void
{
$sanitized = static::sanitize(static::readFile($file));
F::write($file, $sanitized);
}
/**
* Validates file contents
*
* @param string $string
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\Exception On other errors
*/
abstract public static function validate(string $string): void;
/**
* Validates file contents
*
* @param string $string
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\Exception On other errors
*/
abstract public static function validate(string $string): void;
/**
* Validates the contents of a file
*
* @param string $file
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\Exception If the file does not exist
* @throws \Kirby\Exception\Exception On other errors
*/
public static function validateFile(string $file): void
{
static::validate(static::readFile($file));
}
/**
* Validates the contents of a file
*
* @param string $file
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\Exception If the file does not exist
* @throws \Kirby\Exception\Exception On other errors
*/
public static function validateFile(string $file): void
{
static::validate(static::readFile($file));
}
/**
* Reads the contents of a file
* for sanitization or validation
*
* @param string $file
* @return string
*
* @throws \Kirby\Exception\Exception If the file does not exist
*/
protected static function readFile(string $file): string
{
$contents = F::read($file);
/**
* Reads the contents of a file
* for sanitization or validation
*
* @param string $file
* @return string
*
* @throws \Kirby\Exception\Exception If the file does not exist
*/
protected static function readFile(string $file): string
{
$contents = F::read($file);
if ($contents === false) {
throw new Exception('The file "' . $file . '" does not exist');
}
if ($contents === false) {
throw new Exception('The file "' . $file . '" does not exist');
}
return $contents;
}
return $contents;
}
}

View file

@ -15,130 +15,130 @@ namespace Kirby\Sane;
*/
class Html extends DomHandler
{
/**
* Global list of allowed attribute prefixes
*
* @var array
*/
public static $allowedAttrPrefixes = [
'aria-',
'data-',
];
/**
* Global list of allowed attribute prefixes
*
* @var array
*/
public static $allowedAttrPrefixes = [
'aria-',
'data-',
];
/**
* Global list of allowed attributes
*
* @var array
*/
public static $allowedAttrs = [
'class',
'id',
];
/**
* Global list of allowed attributes
*
* @var array
*/
public static $allowedAttrs = [
'class',
'id',
];
/**
* Allowed hostnames for HTTP(S) URLs
*
* @var array
*/
public static $allowedDomains = true;
/**
* Allowed hostnames for HTTP(S) URLs
*
* @var array
*/
public static $allowedDomains = true;
/**
* Associative array of all allowed tag names with the value
* of either an array with the list of all allowed attributes
* for this tag, `true` to allow any attribute from the
* `allowedAttrs` list or `false` to allow the tag without
* any attributes
*
* @var array
*/
public static $allowedTags = [
'a' => ['href', 'rel', 'title', 'target'],
'abbr' => ['title'],
'b' => true,
'body' => true,
'blockquote' => true,
'br' => true,
'code' => true,
'dl' => true,
'dd' => true,
'del' => true,
'div' => true,
'dt' => true,
'em' => true,
'footer' => true,
'h1' => true,
'h2' => true,
'h3' => true,
'h4' => true,
'h5' => true,
'h6' => true,
'hr' => true,
'html' => true,
'i' => true,
'ins' => true,
'li' => true,
'small' => true,
'span' => true,
'strong' => true,
'sub' => true,
'sup' => true,
'ol' => true,
'p' => true,
'pre' => true,
's' => true,
'u' => true,
'ul' => true,
];
/**
* Associative array of all allowed tag names with the value
* of either an array with the list of all allowed attributes
* for this tag, `true` to allow any attribute from the
* `allowedAttrs` list or `false` to allow the tag without
* any attributes
*
* @var array
*/
public static $allowedTags = [
'a' => ['href', 'rel', 'title', 'target'],
'abbr' => ['title'],
'b' => true,
'body' => true,
'blockquote' => true,
'br' => true,
'code' => true,
'dl' => true,
'dd' => true,
'del' => true,
'div' => true,
'dt' => true,
'em' => true,
'footer' => true,
'h1' => true,
'h2' => true,
'h3' => true,
'h4' => true,
'h5' => true,
'h6' => true,
'hr' => true,
'html' => true,
'i' => true,
'ins' => true,
'li' => true,
'small' => true,
'span' => true,
'strong' => true,
'sub' => true,
'sup' => true,
'ol' => true,
'p' => true,
'pre' => true,
's' => true,
'u' => true,
'ul' => true,
];
/**
* Array of explicitly disallowed tags
*
* IMPORTANT: Use lower-case names here because
* of the case-insensitive matching
*
* @var array
*/
public static $disallowedTags = [
'iframe',
'meta',
'object',
'script',
'style',
];
/**
* Array of explicitly disallowed tags
*
* IMPORTANT: Use lower-case names here because
* of the case-insensitive matching
*
* @var array
*/
public static $disallowedTags = [
'iframe',
'meta',
'object',
'script',
'style',
];
/**
* List of attributes that may contain URLs
*
* @var array
*/
public static $urlAttrs = [
'href',
'src',
'xlink:href',
];
/**
* List of attributes that may contain URLs
*
* @var array
*/
public static $urlAttrs = [
'href',
'src',
'xlink:href',
];
/**
* The document type (`'HTML'` or `'XML'`)
*
* @var string
*/
protected static $type = 'HTML';
/**
* The document type (`'HTML'` or `'XML'`)
*
* @var string
*/
protected static $type = 'HTML';
/**
* Returns the sanitization options for the handler
*
* @return array
*/
protected static function options(): array
{
return array_merge(parent::options(), [
'allowedAttrPrefixes' => static::$allowedAttrPrefixes,
'allowedAttrs' => static::$allowedAttrs,
'allowedNamespaces' => [],
'allowedPIs' => [],
'allowedTags' => static::$allowedTags,
'disallowedTags' => static::$disallowedTags,
'urlAttrs' => static::$urlAttrs,
]);
}
/**
* Returns the sanitization options for the handler
*
* @return array
*/
protected static function options(): array
{
return array_merge(parent::options(), [
'allowedAttrPrefixes' => static::$allowedAttrPrefixes,
'allowedAttrs' => static::$allowedAttrs,
'allowedNamespaces' => [],
'allowedPIs' => [],
'allowedTags' => static::$allowedTags,
'disallowedTags' => static::$disallowedTags,
'urlAttrs' => static::$urlAttrs,
]);
}
}

View file

@ -21,189 +21,189 @@ use Kirby\Filesystem\F;
*/
class Sane
{
/**
* Handler Type Aliases
*
* @var array
*/
public static $aliases = [
'application/xml' => 'xml',
'image/svg' => 'svg',
'image/svg+xml' => 'svg',
'text/html' => 'html',
'text/xml' => 'xml',
];
/**
* Handler Type Aliases
*
* @var array
*/
public static $aliases = [
'application/xml' => 'xml',
'image/svg' => 'svg',
'image/svg+xml' => 'svg',
'text/html' => 'html',
'text/xml' => 'xml',
];
/**
* All registered handlers
*
* @var array
*/
public static $handlers = [
'html' => 'Kirby\Sane\Html',
'svg' => 'Kirby\Sane\Svg',
'svgz' => 'Kirby\Sane\Svgz',
'xml' => 'Kirby\Sane\Xml',
];
/**
* All registered handlers
*
* @var array
*/
public static $handlers = [
'html' => 'Kirby\Sane\Html',
'svg' => 'Kirby\Sane\Svg',
'svgz' => 'Kirby\Sane\Svgz',
'xml' => 'Kirby\Sane\Xml',
];
/**
* Handler getter
*
* @param string $type
* @param bool $lazy If set to `true`, `null` is returned for undefined handlers
* @return \Kirby\Sane\Handler|null
*
* @throws \Kirby\Exception\NotFoundException If no handler was found and `$lazy` was set to `false`
*/
public static function handler(string $type, bool $lazy = false)
{
// normalize the type
$type = mb_strtolower($type);
/**
* Handler getter
*
* @param string $type
* @param bool $lazy If set to `true`, `null` is returned for undefined handlers
* @return \Kirby\Sane\Handler|null
*
* @throws \Kirby\Exception\NotFoundException If no handler was found and `$lazy` was set to `false`
*/
public static function handler(string $type, bool $lazy = false)
{
// normalize the type
$type = mb_strtolower($type);
// find a handler or alias
$handler = static::$handlers[$type] ??
static::$handlers[static::$aliases[$type] ?? null] ??
null;
// find a handler or alias
$handler = static::$handlers[$type] ??
static::$handlers[static::$aliases[$type] ?? null] ??
null;
if (empty($handler) === false && class_exists($handler) === true) {
return new $handler();
}
if (empty($handler) === false && class_exists($handler) === true) {
return new $handler();
}
if ($lazy === true) {
return null;
}
if ($lazy === true) {
return null;
}
throw new NotFoundException('Missing handler for type: "' . $type . '"');
}
throw new NotFoundException('Missing handler for type: "' . $type . '"');
}
/**
* Sanitizes the given string with the specified handler
* @since 3.6.0
*
* @param string $string
* @param string $type
* @return string
*/
public static function sanitize(string $string, string $type): string
{
return static::handler($type)->sanitize($string);
}
/**
* Sanitizes the given string with the specified handler
* @since 3.6.0
*
* @param string $string
* @param string $type
* @return string
*/
public static function sanitize(string $string, string $type): string
{
return static::handler($type)->sanitize($string);
}
/**
* Sanitizes the contents of a file by overwriting
* the file with the sanitized version;
* the sane handlers are automatically chosen by
* the extension and MIME type if not specified
* @since 3.6.0
*
* @param string $file
* @param string|bool $typeLazy Explicit handler type string,
* `true` for lazy autodetection or
* `false` for normal autodetection
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\LogicException If more than one handler applies
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public static function sanitizeFile(string $file, $typeLazy = false): void
{
if (is_string($typeLazy) === true) {
static::handler($typeLazy)->sanitizeFile($file);
return;
}
/**
* Sanitizes the contents of a file by overwriting
* the file with the sanitized version;
* the sane handlers are automatically chosen by
* the extension and MIME type if not specified
* @since 3.6.0
*
* @param string $file
* @param string|bool $typeLazy Explicit handler type string,
* `true` for lazy autodetection or
* `false` for normal autodetection
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\LogicException If more than one handler applies
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public static function sanitizeFile(string $file, $typeLazy = false): void
{
if (is_string($typeLazy) === true) {
static::handler($typeLazy)->sanitizeFile($file);
return;
}
// try to find exactly one matching handler
$handlers = static::handlersForFile($file, $typeLazy === true);
switch (count($handlers)) {
case 0:
// lazy autodetection didn't find a handler
break;
case 1:
$handlers[0]->sanitizeFile($file);
break;
default:
// more than one matching handler;
// sanitizing with all handlers will not leave much in the output
$handlerNames = array_map('get_class', $handlers);
throw new LogicException(
'Cannot sanitize file as more than one handler applies: ' .
implode(', ', $handlerNames)
);
}
}
// try to find exactly one matching handler
$handlers = static::handlersForFile($file, $typeLazy === true);
switch (count($handlers)) {
case 0:
// lazy autodetection didn't find a handler
break;
case 1:
$handlers[0]->sanitizeFile($file);
break;
default:
// more than one matching handler;
// sanitizing with all handlers will not leave much in the output
$handlerNames = array_map('get_class', $handlers);
throw new LogicException(
'Cannot sanitize file as more than one handler applies: ' .
implode(', ', $handlerNames)
);
}
}
/**
* Validates file contents with the specified handler
*
* @param string $string
* @param string $type
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public static function validate(string $string, string $type): void
{
static::handler($type)->validate($string);
}
/**
* Validates file contents with the specified handler
*
* @param string $string
* @param string $type
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public static function validate(string $string, string $type): void
{
static::handler($type)->validate($string);
}
/**
* Validates the contents of a file;
* the sane handlers are automatically chosen by
* the extension and MIME type if not specified
*
* @param string $file
* @param string|bool $typeLazy Explicit handler type string,
* `true` for lazy autodetection or
* `false` for normal autodetection
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public static function validateFile(string $file, $typeLazy = false): void
{
if (is_string($typeLazy) === true) {
static::handler($typeLazy)->validateFile($file);
return;
}
/**
* Validates the contents of a file;
* the sane handlers are automatically chosen by
* the extension and MIME type if not specified
*
* @param string $file
* @param string|bool $typeLazy Explicit handler type string,
* `true` for lazy autodetection or
* `false` for normal autodetection
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
* @throws \Kirby\Exception\NotFoundException If the handler was not found
* @throws \Kirby\Exception\Exception On other errors
*/
public static function validateFile(string $file, $typeLazy = false): void
{
if (is_string($typeLazy) === true) {
static::handler($typeLazy)->validateFile($file);
return;
}
foreach (static::handlersForFile($file, $typeLazy === true) as $handler) {
$handler->validateFile($file);
}
}
foreach (static::handlersForFile($file, $typeLazy === true) as $handler) {
$handler->validateFile($file);
}
}
/**
* Returns all handler objects that apply to the given file based on
* file extension and MIME type
*
* @param string $file
* @param bool $lazy If set to `true`, undefined handlers are skipped
* @return array<\Kirby\Sane\Handler>
*/
protected static function handlersForFile(string $file, bool $lazy = false): array
{
$handlers = $handlerClasses = [];
/**
* Returns all handler objects that apply to the given file based on
* file extension and MIME type
*
* @param string $file
* @param bool $lazy If set to `true`, undefined handlers are skipped
* @return array<\Kirby\Sane\Handler>
*/
protected static function handlersForFile(string $file, bool $lazy = false): array
{
$handlers = $handlerClasses = [];
// all values that can be used for the handler search;
// filter out all empty options
$options = array_filter([F::extension($file), F::mime($file)]);
// all values that can be used for the handler search;
// filter out all empty options
$options = array_filter([F::extension($file), F::mime($file)]);
foreach ($options as $option) {
$handler = static::handler($option, $lazy);
$handlerClass = $handler ? get_class($handler) : null;
foreach ($options as $option) {
$handler = static::handler($option, $lazy);
$handlerClass = $handler ? get_class($handler) : null;
// ensure that each handler class is only returned once
if ($handler && in_array($handlerClass, $handlerClasses) === false) {
$handlers[] = $handler;
$handlerClasses[] = $handlerClass;
}
}
// ensure that each handler class is only returned once
if ($handler && in_array($handlerClass, $handlerClasses) === false) {
$handlers[] = $handler;
$handlerClasses[] = $handlerClass;
}
}
return $handlers;
}
return $handlers;
}
}

View file

@ -23,487 +23,487 @@ use Kirby\Toolkit\Str;
*/
class Svg extends Xml
{
/**
* Allow and block lists are inspired by DOMPurify
*
* @link https://github.com/cure53/DOMPurify
* @copyright 2015 Mario Heiderich
* @license https://www.apache.org/licenses/LICENSE-2.0
*/
/**
* Allow and block lists are inspired by DOMPurify
*
* @link https://github.com/cure53/DOMPurify
* @copyright 2015 Mario Heiderich
* @license https://www.apache.org/licenses/LICENSE-2.0
*/
/**
* Global list of allowed attribute prefixes
*
* @var array
*/
public static $allowedAttrPrefixes = [
'aria-',
'data-',
];
/**
* Global list of allowed attribute prefixes
*
* @var array
*/
public static $allowedAttrPrefixes = [
'aria-',
'data-',
];
/**
* Global list of allowed attributes
*
* @var array
*/
public static $allowedAttrs = [
// core attributes
'id',
'lang',
'tabindex',
'xml:id',
'xml:lang',
'xml:space',
/**
* Global list of allowed attributes
*
* @var array
*/
public static $allowedAttrs = [
// core attributes
'id',
'lang',
'tabindex',
'xml:id',
'xml:lang',
'xml:space',
// styling attributes
'class',
'style',
// styling attributes
'class',
'style',
// conditional processing attributes
'systemLanguage',
// conditional processing attributes
'systemLanguage',
// presentation attributes
'alignment-baseline',
'baseline-shift',
'clip',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'd',
'direction',
'display',
'dominant-baseline',
'enable-background',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'image-rendering',
'kerning',
'letter-spacing',
'lighting-color',
'marker-end',
'marker-mid',
'marker-start',
'mask',
'opacity',
'overflow',
'paint-order',
'shape-rendering',
'stop-color',
'stop-opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-decoration',
'text-rendering',
'transform',
'visibility',
'word-spacing',
'writing-mode',
// presentation attributes
'alignment-baseline',
'baseline-shift',
'clip',
'clip-path',
'clip-rule',
'color',
'color-interpolation',
'color-interpolation-filters',
'color-profile',
'color-rendering',
'd',
'direction',
'display',
'dominant-baseline',
'enable-background',
'fill',
'fill-opacity',
'fill-rule',
'filter',
'flood-color',
'flood-opacity',
'font-family',
'font-size',
'font-size-adjust',
'font-stretch',
'font-style',
'font-variant',
'font-weight',
'image-rendering',
'kerning',
'letter-spacing',
'lighting-color',
'marker-end',
'marker-mid',
'marker-start',
'mask',
'opacity',
'overflow',
'paint-order',
'shape-rendering',
'stop-color',
'stop-opacity',
'stroke',
'stroke-dasharray',
'stroke-dashoffset',
'stroke-linecap',
'stroke-linejoin',
'stroke-miterlimit',
'stroke-opacity',
'stroke-width',
'text-anchor',
'text-decoration',
'text-rendering',
'transform',
'visibility',
'word-spacing',
'writing-mode',
// animation attribute target attributes
'attributeName',
'attributeType',
// animation attribute target attributes
'attributeName',
'attributeType',
// animation timing attributes
'begin',
'dur',
'end',
'max',
'min',
'repeatCount',
'repeatDur',
'restart',
// animation timing attributes
'begin',
'dur',
'end',
'max',
'min',
'repeatCount',
'repeatDur',
'restart',
// animation value attributes
'by',
'from',
'keySplines',
'keyTimes',
'to',
'values',
// animation value attributes
'by',
'from',
'keySplines',
'keyTimes',
'to',
'values',
// animation addition attributes
'accumulate',
'additive',
// animation addition attributes
'accumulate',
'additive',
// filter primitive attributes
'height',
'result',
'width',
'x',
'y',
// filter primitive attributes
'height',
'result',
'width',
'x',
'y',
// transfer function attributes
'amplitude',
'exponent',
'intercept',
'offset',
'slope',
'tableValues',
'type',
// transfer function attributes
'amplitude',
'exponent',
'intercept',
'offset',
'slope',
'tableValues',
'type',
// other attributes specific to one or multiple elements
'azimuth',
'baseFrequency',
'bias',
'clipPathUnits',
'cx',
'cy',
'diffuseConstant',
'divisor',
'dx',
'dy',
'edgeMode',
'elevation',
'filterUnits',
'fr',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'glyphRef',
'gradientTransform',
'gradientUnits',
'href',
'hreflang',
'in',
'in2',
'k',
'k1',
'k2',
'k3',
'k4',
'kernelMatrix',
'kernelUnitLength',
'keyPoints',
'lengthAdjust',
'limitingConeAngle',
'markerHeight',
'markerUnits',
'markerWidth',
'maskContentUnits',
'maskUnits',
'media',
'method',
'mode',
'numOctaves',
'operator',
'order',
'orient',
'orientation',
'path',
'pathLength',
'patternContentUnits',
'patternTransform',
'patternUnits',
'points',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'preserveAlpha',
'preserveAspectRatio',
'primitiveUnits',
'r',
'radius',
'refX',
'refY',
'rotate',
'rx',
'ry',
'scale',
'seed',
'side',
'spacing',
'specularConstant',
'specularExponent',
'spreadMethod',
'startOffset',
'stdDeviation',
'stitchTiles',
'surfaceScale',
'targetX',
'targetY',
'textLength',
'u1',
'u2',
'unicode',
'version',
'vert-adv-y',
'vert-origin-x',
'vert-origin-y',
'viewBox',
'x1',
'x2',
'xChannelSelector',
'xlink:href',
'xlink:title',
'y1',
'y2',
'yChannelSelector',
'z',
'zoomAndPan',
];
// other attributes specific to one or multiple elements
'azimuth',
'baseFrequency',
'bias',
'clipPathUnits',
'cx',
'cy',
'diffuseConstant',
'divisor',
'dx',
'dy',
'edgeMode',
'elevation',
'filterUnits',
'fr',
'fx',
'fy',
'g1',
'g2',
'glyph-name',
'glyphRef',
'gradientTransform',
'gradientUnits',
'href',
'hreflang',
'in',
'in2',
'k',
'k1',
'k2',
'k3',
'k4',
'kernelMatrix',
'kernelUnitLength',
'keyPoints',
'lengthAdjust',
'limitingConeAngle',
'markerHeight',
'markerUnits',
'markerWidth',
'maskContentUnits',
'maskUnits',
'media',
'method',
'mode',
'numOctaves',
'operator',
'order',
'orient',
'orientation',
'path',
'pathLength',
'patternContentUnits',
'patternTransform',
'patternUnits',
'points',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'preserveAlpha',
'preserveAspectRatio',
'primitiveUnits',
'r',
'radius',
'refX',
'refY',
'rotate',
'rx',
'ry',
'scale',
'seed',
'side',
'spacing',
'specularConstant',
'specularExponent',
'spreadMethod',
'startOffset',
'stdDeviation',
'stitchTiles',
'surfaceScale',
'targetX',
'targetY',
'textLength',
'u1',
'u2',
'unicode',
'version',
'vert-adv-y',
'vert-origin-x',
'vert-origin-y',
'viewBox',
'x1',
'x2',
'xChannelSelector',
'xlink:href',
'xlink:title',
'y1',
'y2',
'yChannelSelector',
'z',
'zoomAndPan',
];
/**
* Associative array of all allowed namespace URIs
*
* @var array
*/
public static $allowedNamespaces = [
'' => 'http://www.w3.org/2000/svg',
'xlink' => 'http://www.w3.org/1999/xlink'
];
/**
* Associative array of all allowed namespace URIs
*
* @var array
*/
public static $allowedNamespaces = [
'' => 'http://www.w3.org/2000/svg',
'xlink' => 'http://www.w3.org/1999/xlink'
];
/**
* Associative array of all allowed tag names with the value
* of either an array with the list of all allowed attributes
* for this tag, `true` to allow any attribute from the
* `allowedAttrs` list or `false` to allow the tag without
* any attributes
*
* @var array
*/
public static $allowedTags = [
'a' => true,
'altGlyph' => true,
'altGlyphDef' => true,
'altGlyphItem' => true,
'animateColor' => true,
'animateMotion' => true,
'animateTransform' => true,
'circle' => true,
'clipPath' => true,
'defs' => true,
'desc' => true,
'ellipse' => true,
'feBlend' => true,
'feColorMatrix' => true,
'feComponentTransfer' => true,
'feComposite' => true,
'feConvolveMatrix' => true,
'feDiffuseLighting' => true,
'feDisplacementMap' => true,
'feDistantLight' => true,
'feFlood' => true,
'feFuncA' => true,
'feFuncB' => true,
'feFuncG' => true,
'feFuncR' => true,
'feGaussianBlur' => true,
'feMerge' => true,
'feMergeNode' => true,
'feMorphology' => true,
'feOffset' => true,
'fePointLight' => true,
'feSpecularLighting' => true,
'feSpotLight' => true,
'feTile' => true,
'feTurbulence' => true,
'filter' => true,
'font' => true,
'g' => true,
'glyph' => true,
'glyphRef' => true,
'hkern' => true,
'image' => true,
'line' => true,
'linearGradient' => true,
'marker' => true,
'mask' => true,
'metadata' => true,
'mpath' => true,
'path' => true,
'pattern' => true,
'polygon' => true,
'polyline' => true,
'radialGradient' => true,
'rect' => true,
'stop' => true,
'style' => true,
'svg' => true,
'switch' => true,
'symbol' => true,
'text' => true,
'textPath' => true,
'title' => true,
'tref' => true,
'tspan' => true,
'use' => true,
'view' => true,
'vkern' => true,
];
/**
* Associative array of all allowed tag names with the value
* of either an array with the list of all allowed attributes
* for this tag, `true` to allow any attribute from the
* `allowedAttrs` list or `false` to allow the tag without
* any attributes
*
* @var array
*/
public static $allowedTags = [
'a' => true,
'altGlyph' => true,
'altGlyphDef' => true,
'altGlyphItem' => true,
'animateColor' => true,
'animateMotion' => true,
'animateTransform' => true,
'circle' => true,
'clipPath' => true,
'defs' => true,
'desc' => true,
'ellipse' => true,
'feBlend' => true,
'feColorMatrix' => true,
'feComponentTransfer' => true,
'feComposite' => true,
'feConvolveMatrix' => true,
'feDiffuseLighting' => true,
'feDisplacementMap' => true,
'feDistantLight' => true,
'feFlood' => true,
'feFuncA' => true,
'feFuncB' => true,
'feFuncG' => true,
'feFuncR' => true,
'feGaussianBlur' => true,
'feMerge' => true,
'feMergeNode' => true,
'feMorphology' => true,
'feOffset' => true,
'fePointLight' => true,
'feSpecularLighting' => true,
'feSpotLight' => true,
'feTile' => true,
'feTurbulence' => true,
'filter' => true,
'font' => true,
'g' => true,
'glyph' => true,
'glyphRef' => true,
'hkern' => true,
'image' => true,
'line' => true,
'linearGradient' => true,
'marker' => true,
'mask' => true,
'metadata' => true,
'mpath' => true,
'path' => true,
'pattern' => true,
'polygon' => true,
'polyline' => true,
'radialGradient' => true,
'rect' => true,
'stop' => true,
'style' => true,
'svg' => true,
'switch' => true,
'symbol' => true,
'text' => true,
'textPath' => true,
'title' => true,
'tref' => true,
'tspan' => true,
'use' => true,
'view' => true,
'vkern' => true,
];
/**
* Array of explicitly disallowed tags
*
* IMPORTANT: Use lower-case names here because
* of the case-insensitive matching
*
* @var array
*/
public static $disallowedTags = [
'animate',
'color-profile',
'cursor',
'discard',
'fedropshadow',
'feimage',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignobject',
'hatch',
'hatchpath',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'missing-glyph',
'script',
'set',
'solidcolor',
'unknown',
];
/**
* Array of explicitly disallowed tags
*
* IMPORTANT: Use lower-case names here because
* of the case-insensitive matching
*
* @var array
*/
public static $disallowedTags = [
'animate',
'color-profile',
'cursor',
'discard',
'fedropshadow',
'feimage',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignobject',
'hatch',
'hatchpath',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'missing-glyph',
'script',
'set',
'solidcolor',
'unknown',
];
/**
* Custom callback for additional attribute sanitization
* @internal
*
* @param \DOMAttr $attr
* @return array Array with exception objects for each modification
*/
public static function sanitizeAttr(DOMAttr $attr): array
{
$element = $attr->ownerElement;
$name = $attr->name;
$value = $attr->value;
$errors = [];
/**
* Custom callback for additional attribute sanitization
* @internal
*
* @param \DOMAttr $attr
* @return array Array with exception objects for each modification
*/
public static function sanitizeAttr(DOMAttr $attr): array
{
$element = $attr->ownerElement;
$name = $attr->name;
$value = $attr->value;
$errors = [];
// block nested <use> elements ("Billion Laughs" DoS attack)
if (
$element->localName === 'use' &&
Str::contains($name, 'href') !== false &&
Str::startsWith($value, '#') === true
) {
// find the target (used element)
$id = str_replace('"', '', mb_substr($value, 1));
$target = (new DOMXPath($attr->ownerDocument))->query('//*[@id="' . $id . '"]')->item(0);
// block nested <use> elements ("Billion Laughs" DoS attack)
if (
$element->localName === 'use' &&
Str::contains($name, 'href') !== false &&
Str::startsWith($value, '#') === true
) {
// find the target (used element)
$id = str_replace('"', '', mb_substr($value, 1));
$target = (new DOMXPath($attr->ownerDocument))->query('//*[@id="' . $id . '"]')->item(0);
// the target must not contain any other <use> elements
if (
is_a($target, 'DOMElement') === true &&
$target->getElementsByTagName('use')->count() > 0
) {
$errors[] = new InvalidArgumentException(
'Nested "use" elements are not allowed' .
' (used in line ' . $element->getLineNo() . ')'
);
$element->removeAttributeNode($attr);
}
}
// the target must not contain any other <use> elements
if (
is_a($target, 'DOMElement') === true &&
$target->getElementsByTagName('use')->count() > 0
) {
$errors[] = new InvalidArgumentException(
'Nested "use" elements are not allowed' .
' (used in line ' . $element->getLineNo() . ')'
);
$element->removeAttributeNode($attr);
}
}
return $errors;
}
return $errors;
}
/**
* Custom callback for additional element sanitization
* @internal
*
* @param \DOMElement $element
* @return array Array with exception objects for each modification
*/
public static function sanitizeElement(DOMElement $element): array
{
$errors = [];
/**
* Custom callback for additional element sanitization
* @internal
*
* @param \DOMElement $element
* @return array Array with exception objects for each modification
*/
public static function sanitizeElement(DOMElement $element): array
{
$errors = [];
// check for URLs inside <style> elements
if ($element->tagName === 'style') {
foreach (Dom::extractUrls($element->textContent) as $url) {
if (Dom::isAllowedUrl($url, static::options()) !== true) {
$errors[] = new InvalidArgumentException(
'The URL is not allowed in the "style" element' .
' (around line ' . $element->getLineNo() . ')'
);
Dom::remove($element);
}
}
}
// check for URLs inside <style> elements
if ($element->tagName === 'style') {
foreach (Dom::extractUrls($element->textContent) as $url) {
if (Dom::isAllowedUrl($url, static::options()) !== true) {
$errors[] = new InvalidArgumentException(
'The URL is not allowed in the "style" element' .
' (around line ' . $element->getLineNo() . ')'
);
Dom::remove($element);
}
}
}
return $errors;
}
return $errors;
}
/**
* Custom callback for additional doctype validation
* @internal
*
* @param \DOMDocumentType $doctype
* @return void
*/
public static function validateDoctype(DOMDocumentType $doctype): void
{
if (mb_strtolower($doctype->name) !== 'svg') {
throw new InvalidArgumentException('Invalid doctype');
}
}
/**
* Custom callback for additional doctype validation
* @internal
*
* @param \DOMDocumentType $doctype
* @return void
*/
public static function validateDoctype(DOMDocumentType $doctype): void
{
if (mb_strtolower($doctype->name) !== 'svg') {
throw new InvalidArgumentException('Invalid doctype');
}
}
/**
* Returns the sanitization options for the handler
*
* @return array
*/
protected static function options(): array
{
return array_merge(parent::options(), [
'allowedAttrPrefixes' => static::$allowedAttrPrefixes,
'allowedAttrs' => static::$allowedAttrs,
'allowedNamespaces' => static::$allowedNamespaces,
'allowedTags' => static::$allowedTags,
'disallowedTags' => static::$disallowedTags,
]);
}
/**
* Returns the sanitization options for the handler
*
* @return array
*/
protected static function options(): array
{
return array_merge(parent::options(), [
'allowedAttrPrefixes' => static::$allowedAttrPrefixes,
'allowedAttrs' => static::$allowedAttrs,
'allowedNamespaces' => static::$allowedNamespaces,
'allowedTags' => static::$allowedTags,
'disallowedTags' => static::$disallowedTags,
]);
}
/**
* Parses the given string into a `Toolkit\Dom` object
*
* @param string $string
* @return \Kirby\Toolkit\Dom
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
*/
protected static function parse(string $string)
{
$svg = parent::parse($string);
/**
* Parses the given string into a `Toolkit\Dom` object
*
* @param string $string
* @return \Kirby\Toolkit\Dom
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
*/
protected static function parse(string $string)
{
$svg = parent::parse($string);
// basic validation before we continue sanitizing/validating
$rootName = $svg->document()->documentElement->nodeName;
if ($rootName !== 'svg') {
throw new InvalidArgumentException('The file is not a SVG (got <' . $rootName . '>)');
}
// basic validation before we continue sanitizing/validating
$rootName = $svg->document()->documentElement->nodeName;
if ($rootName !== 'svg') {
throw new InvalidArgumentException('The file is not a SVG (got <' . $rootName . '>)');
}
return $svg;
}
return $svg;
}
}

View file

@ -16,57 +16,57 @@ use Kirby\Exception\InvalidArgumentException;
*/
class Svgz extends Svg
{
/**
* Sanitizes the given string
*
* @param string $string
* @return string
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed or recompressed
*/
public static function sanitize(string $string): string
{
$string = static::uncompress($string);
$string = parent::sanitize($string);
$string = @gzencode($string);
/**
* Sanitizes the given string
*
* @param string $string
* @return string
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed or recompressed
*/
public static function sanitize(string $string): string
{
$string = static::uncompress($string);
$string = parent::sanitize($string);
$string = @gzencode($string);
if (is_string($string) !== true) {
throw new InvalidArgumentException('Could not recompress gzip data'); // @codeCoverageIgnore
}
if (is_string($string) !== true) {
throw new InvalidArgumentException('Could not recompress gzip data'); // @codeCoverageIgnore
}
return $string;
}
return $string;
}
/**
* Validates file contents
*
* @param string $string
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
*/
public static function validate(string $string): void
{
parent::validate(static::uncompress($string));
}
/**
* Validates file contents
*
* @param string $string
* @return void
*
* @throws \Kirby\Exception\InvalidArgumentException If the file couldn't be parsed
* @throws \Kirby\Exception\InvalidArgumentException If the file didn't pass validation
*/
public static function validate(string $string): void
{
parent::validate(static::uncompress($string));
}
/**
* Uncompresses the SVGZ data
*
* @param string $string
* @return string
*/
protected static function uncompress(string $string): string
{
// only support uncompressed files up to 10 MB to
// prevent gzip bombs from crashing the process
$string = @gzdecode($string, 10000000);
/**
* Uncompresses the SVGZ data
*
* @param string $string
* @return string
*/
protected static function uncompress(string $string): string
{
// only support uncompressed files up to 10 MB to
// prevent gzip bombs from crashing the process
$string = @gzdecode($string, 10000000);
if (is_string($string) !== true) {
throw new InvalidArgumentException('Could not uncompress gzip data');
}
if (is_string($string) !== true) {
throw new InvalidArgumentException('Could not uncompress gzip data');
}
return $string;
}
return $string;
}
}

View file

@ -20,55 +20,55 @@ use Kirby\Toolkit\Str;
*/
class Xml extends DomHandler
{
/**
* Custom callback for additional element sanitization
* @internal
*
* @param \DOMElement $element
* @return array Array with exception objects for each modification
*/
public static function sanitizeElement(DOMElement $element): array
{
$errors = [];
/**
* Custom callback for additional element sanitization
* @internal
*
* @param \DOMElement $element
* @return array Array with exception objects for each modification
*/
public static function sanitizeElement(DOMElement $element): array
{
$errors = [];
// if we are validating an XML file, block all SVG and HTML namespaces
if (static::class === self::class) {
$simpleXmlElement = simplexml_import_dom($element);
foreach ($simpleXmlElement->getDocNamespaces(false, false) as $namespace => $value) {
if (
Str::contains($value, 'html', true) === true ||
Str::contains($value, 'svg', true) === true
) {
$element->removeAttributeNS($value, $namespace);
$errors[] = new InvalidArgumentException(
'The namespace "' . $value . '" is not allowed' .
' (around line ' . $element->getLineNo() . ')'
);
}
}
}
// if we are validating an XML file, block all SVG and HTML namespaces
if (static::class === self::class) {
$simpleXmlElement = simplexml_import_dom($element);
foreach ($simpleXmlElement->getDocNamespaces(false, false) as $namespace => $value) {
if (
Str::contains($value, 'html', true) === true ||
Str::contains($value, 'svg', true) === true
) {
$element->removeAttributeNS($value, $namespace);
$errors[] = new InvalidArgumentException(
'The namespace "' . $value . '" is not allowed' .
' (around line ' . $element->getLineNo() . ')'
);
}
}
}
return $errors;
}
return $errors;
}
/**
* Custom callback for additional doctype validation
* @internal
*
* @param \DOMDocumentType $doctype
* @return void
*/
public static function validateDoctype(DOMDocumentType $doctype): void
{
// if we are validating an XML file, block all SVG and HTML doctypes
if (
static::class === self::class &&
(
Str::contains($doctype->name, 'html', true) === true ||
Str::contains($doctype->name, 'svg', true) === true
)
) {
throw new InvalidArgumentException('The doctype is not allowed in XML files');
}
}
/**
* Custom callback for additional doctype validation
* @internal
*
* @param \DOMDocumentType $doctype
* @return void
*/
public static function validateDoctype(DOMDocumentType $doctype): void
{
// if we are validating an XML file, block all SVG and HTML doctypes
if (
static::class === self::class &&
(
Str::contains($doctype->name, 'html', true) === true ||
Str::contains($doctype->name, 'svg', true) === true
)
) {
throw new InvalidArgumentException('The doctype is not allowed in XML files');
}
}
}