Revert "composer package update"
This reverts commit e979959219e95fc6435ad40f7e60b602f8bbd208.
This commit is contained in:
+6
-26
@@ -1,16 +1,11 @@
|
||||
# HTML5 Definitions for HTML Purifier
|
||||
|
||||

|
||||
[](https://travis-ci.org/xemlock/htmlpurifier-html5)
|
||||
[](https://coveralls.io/github/xemlock/htmlpurifier-html5?branch=master)
|
||||
[](https://packagist.org/packages/xemlock/htmlpurifier-html5)
|
||||
[](https://packagist.org/packages/xemlock/htmlpurifier-html5/stats)
|
||||
[](https://packagist.org/packages/xemlock/htmlpurifier-html5)
|
||||
[](https://packagist.org/packages/xemlock/htmlpurifier-html5)
|
||||
|
||||
This library provides HTML5 element definitions for [HTML Purifier](https://github.com/ezyang/htmlpurifier).
|
||||
|
||||
It is the most complete HTML5-compliant solution among all based on HTML Purifier. Apart from providing the most extensive set of element definitions, it provides tidy/sanitization rules for transforming the input into a valid HTML5 output.
|
||||
|
||||
This library provides HTML5 element definitions for [HTMLPurifier](http://htmlpurifier.org/).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -20,7 +15,6 @@ Install with [Composer](https://getcomposer.org/) by running the following comma
|
||||
composer require xemlock/htmlpurifier-html5
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The most basic usage is similar to the original HTML Purifier. Create a HTML5-compatible config
|
||||
@@ -49,33 +43,19 @@ or equivalently:
|
||||
```php
|
||||
$config = HTMLPurifier_HTML5Config::createDefault();
|
||||
$config->set('HTML.SafeIframe', true);
|
||||
$config->set('URI.SafeIframeRegexp', '%^//www\.youtube\.com/embed/%');
|
||||
$config->set('URI.SafeIframeRegexp', '#^//www\.youtube\.com/embed/#');
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Apart from HTML Purifier's built-in [configuration directives](http://htmlpurifier.org/live/configdoc/plain.html), the following new directives are also supported:
|
||||
|
||||
* __HTML.IframeAllowFullscreen__
|
||||
|
||||
Version added: 0.1.11\
|
||||
Type: [Boolean](http://htmlpurifier.org/live/configdoc/plain.html#type-bool)\
|
||||
Default: `false`
|
||||
|
||||
Whether or not to permit `allowfullscreen` attribute on `iframe` tags. It requires either [%HTML.SafeIframe](http://htmlpurifier.org/live/configdoc/plain.html#HTML.SafeIframe) or [%HTML.Trusted](http://htmlpurifier.org/live/configdoc/plain.html#HTML.Trusted) to be `true`.
|
||||
|
||||
|
||||
## Supported HTML5 elements
|
||||
|
||||
Aside from HTML elements supported originally by HTML Purifier, this library
|
||||
adds support for the following HTML5 elements:
|
||||
|
||||
`<article>`, `<aside>`, `<audio>`, `<bdi>`, `<details>`, `<dialog>`, `<figcaption>`, `<figure>`, `<footer>`, `<header>`, `<hgroup>`, `<main>`, `<mark>`, `<nav>`, `<picture>`, `<progress>`, `<section>`, `<source>`, `<summary>`, `<time>`, `<track>`, `<video>`, `<wbr>`
|
||||
article, aside, audio, details, figcaption, figure, footer, header, hgroup, main, nav, picture, section, source, summary, time, track, video
|
||||
|
||||
as well as HTML5 attributes added to existing HTML elements, such as:
|
||||
|
||||
`<a>`, `<del>`, `<fieldset>`, `<ins>`, `<script>`
|
||||
### Elements not (yet) supported
|
||||
|
||||
dialog, menu, menuitem, progress
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+4
-6
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "xemlock/htmlpurifier-html5",
|
||||
"description": "HTML5 element definitions for HTML Purifier",
|
||||
"keywords": ["HtmlPurifier", "html", "html5", "purifier", "security", "xss", "tidy", "validator"],
|
||||
"description": "HTML Purifier definitions for HTML5 elements",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
@@ -12,16 +11,15 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.2",
|
||||
"ezyang/htmlpurifier": "^4.8"
|
||||
"ezyang/htmlpurifier": "^4.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": ">=4.7 <8.0",
|
||||
"php-coveralls/php-coveralls": "^1.1|^2.1"
|
||||
"phpunit/phpunit": "4.7.*|5.7.*"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["library/HTMLPurifier/"]
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit --coverage-html build/coverage"
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates a floating point number
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Float extends HTMLPurifier_AttrDef
|
||||
{
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
protected $min;
|
||||
|
||||
/**
|
||||
* @var int|float
|
||||
*/
|
||||
protected $max;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $minInclusive = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $maxInclusive = true;
|
||||
|
||||
/**
|
||||
* Supported options:
|
||||
*
|
||||
* - 'min' => int|float
|
||||
* - 'max' => int|float
|
||||
* - 'minInclusive' => bool
|
||||
* - 'maxInclusive' => bool
|
||||
*
|
||||
* @param array $options OPTIONAL
|
||||
*/
|
||||
public function __construct($options = null)
|
||||
{
|
||||
$options = is_array($options) ? $options : array();
|
||||
|
||||
$this->min = isset($options['min']) ? floatval($options['min']) : null;
|
||||
$this->max = isset($options['max']) ? floatval($options['max']) : null;
|
||||
|
||||
if (isset($options['minInclusive'])) {
|
||||
$this->minInclusive = (bool) $options['minInclusive'];
|
||||
}
|
||||
|
||||
if (isset($options['maxInclusive'])) {
|
||||
$this->maxInclusive = (bool) $options['maxInclusive'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $number
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return string
|
||||
*/
|
||||
public function validate($number, $config, $context)
|
||||
{
|
||||
$number = $this->parseCDATA($number);
|
||||
|
||||
if ($number === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Up to PHP 5.6 is_numeric() returns TRUE for hex strings
|
||||
// http://php.net/manual/en/function.is-numeric.php
|
||||
if (!preg_match('/^[-+.0-9Ee]+$/', $number) || !is_numeric($number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// HTML numbers cannot start with '+' character
|
||||
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number
|
||||
if (substr($number, 0, 1) === '+') {
|
||||
$number = substr($number, 1);
|
||||
}
|
||||
|
||||
$value = floatval($number);
|
||||
|
||||
if (($this->min !== null) &&
|
||||
(($this->minInclusive && $value < $this->min) || (!$this->minInclusive && $value <= $this->min))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($this->max !== null) &&
|
||||
(($this->maxInclusive && $this->max < $value) || (!$this->maxInclusive && $this->max <= $value))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function
|
||||
*
|
||||
* @param string $string A comma-delimited list of key:value pairs. Example: "min:0,max:10".
|
||||
* @return HTMLPurifier_AttrDef_Float
|
||||
*/
|
||||
public function make($string)
|
||||
{
|
||||
$options = array();
|
||||
foreach (explode(',', $string) as $pair) {
|
||||
$parts = explode(':', $pair, 2);
|
||||
if (count($parts) === 2) {
|
||||
list($key, $value) = $parts;
|
||||
$options[$key] = $value;
|
||||
}
|
||||
}
|
||||
$class = get_class($this);
|
||||
return new $class($options);
|
||||
}
|
||||
}
|
||||
-148
@@ -1,148 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates 'rel' attribute on <a> and <area> elements, as defined by the
|
||||
* HTML5 spec and the MicroFormats link type extensions tables.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML5_ARel extends HTMLPurifier_AttrDef
|
||||
{
|
||||
/**
|
||||
* Lookup table for valid values
|
||||
* @var array
|
||||
*/
|
||||
protected static $values = array(
|
||||
// https://html.spec.whatwg.org/multipage/links.html#linkTypes
|
||||
'alternate' => true,
|
||||
'author' => true,
|
||||
'bookmark' => true,
|
||||
'external' => true,
|
||||
'help' => true,
|
||||
'license' => true,
|
||||
'next' => true,
|
||||
'nofollow' => true,
|
||||
'noopener' => true,
|
||||
'noreferrer' => true,
|
||||
'opener' => true,
|
||||
'prev' => true,
|
||||
'search' => true,
|
||||
'sidebar' => true,
|
||||
'tag' => true,
|
||||
// http://microformats.org/wiki/existing-rel-values#HTML5_link_type_extensions
|
||||
'acquaintance' => true,
|
||||
'amphtml' => true,
|
||||
'appendix' => true,
|
||||
'archived' => true,
|
||||
'attachment' => true,
|
||||
'canonical' => true,
|
||||
'category' => true,
|
||||
'chapter' => true,
|
||||
'child' => true,
|
||||
'co-resident' => true,
|
||||
'co-worker' => true,
|
||||
'code-license' => true,
|
||||
'code-repository' => true,
|
||||
'colleague' => true,
|
||||
'contact' => true,
|
||||
'content-license' => true,
|
||||
'content-repository' => true,
|
||||
'contents' => true,
|
||||
'copyright' => true,
|
||||
'crush' => true,
|
||||
'date' => true,
|
||||
'disclosure' => true,
|
||||
'discussion' => true,
|
||||
'enclosure' => true,
|
||||
'entry-content' => true,
|
||||
'first' => true,
|
||||
'friend' => true,
|
||||
'glossary' => true,
|
||||
'home' => true,
|
||||
'http://docs.oasis-open.org/ns/cmis/link/200908/acl' => true,
|
||||
'hub' => true,
|
||||
'in-reply-to' => true,
|
||||
'index' => true,
|
||||
'issues' => true,
|
||||
'jslicense' => true,
|
||||
'last' => true,
|
||||
'kin' => true,
|
||||
'lightbox' => true,
|
||||
'lightvideo' => true,
|
||||
'me' => true,
|
||||
'met' => true,
|
||||
'muse' => true,
|
||||
'neighbor' => true,
|
||||
'parent' => true,
|
||||
'prerender' => true,
|
||||
'previous' => true,
|
||||
'profile' => true,
|
||||
'publisher' => true,
|
||||
'radioepg' => true,
|
||||
'rendition' => true,
|
||||
'reply-to' => true,
|
||||
'root' => true,
|
||||
'section' => true,
|
||||
'sibling' => true,
|
||||
'spouse' => true,
|
||||
'start' => true,
|
||||
'subsection' => true,
|
||||
'sweetheart' => true,
|
||||
'syndication' => true,
|
||||
'toc' => true,
|
||||
'transformation' => true,
|
||||
'webmention' => true,
|
||||
'widget' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Return lookup table for valid 'rel' values
|
||||
*
|
||||
* @return array
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function values()
|
||||
{
|
||||
return self::$values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $allowed;
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
if ($this->allowed === null) {
|
||||
$allowedRel = (array) $config->get('Attr.AllowedRel');
|
||||
if (empty($allowedRel)) {
|
||||
$allowed = array();
|
||||
} else {
|
||||
$allowed = array_intersect_key($allowedRel, self::$values);
|
||||
}
|
||||
$this->allowed = $allowed;
|
||||
}
|
||||
|
||||
$string = $this->parseCDATA($string);
|
||||
$parts = explode(' ', $string);
|
||||
|
||||
$result = array();
|
||||
foreach ($parts as $part) {
|
||||
$part = strtolower(trim($part));
|
||||
if (!isset($this->allowed[$part])) {
|
||||
continue;
|
||||
}
|
||||
$result[$part] = true;
|
||||
}
|
||||
|
||||
if (empty($result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return implode(' ', array_keys($result));
|
||||
}
|
||||
}
|
||||
-360
@@ -1,360 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates HTML5 date and time strings according to spec
|
||||
* https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates-and-times
|
||||
*
|
||||
* This validator tries to parse as much data as possible and then tries to
|
||||
* render it in the desired format. It fails if either no datetime data can
|
||||
* be extracted from the input, or the extracted data is insufficient for
|
||||
* the desired format (with the exception of DatetimeGlobal format, which
|
||||
* uses server timezone offset if none is detected).
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML5_Datetime extends HTMLPurifier_AttrDef
|
||||
{
|
||||
const REGEX = '/^
|
||||
(
|
||||
(?P<year>\d{4,})
|
||||
(
|
||||
-
|
||||
(?P<month>[01]\d)
|
||||
(
|
||||
-
|
||||
(?P<day>[0-3]\d)
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
(
|
||||
(^|(\s+|T))
|
||||
(?P<hour>[0-2]\d)
|
||||
:
|
||||
(?P<minute>[0-5]\d)
|
||||
(
|
||||
:
|
||||
(?P<second>[0-5]\d(\.\d+)?)
|
||||
)?
|
||||
)?
|
||||
(
|
||||
(?P<tzZulu>Z)
|
||||
|
|
||||
(
|
||||
(?P<tzHour>[+-][0-2]\d)
|
||||
:?
|
||||
(?P<tzMinute>[0-5]\d)
|
||||
)
|
||||
)?
|
||||
$/xi';
|
||||
|
||||
/**
|
||||
* Lookup table for supported formats and if they are enabled by default
|
||||
* @var array
|
||||
*/
|
||||
protected static $formats = array(
|
||||
'Datetime' => true,
|
||||
'DatetimeGlobal' => false,
|
||||
'DatetimeLocal' => false,
|
||||
'Date' => true,
|
||||
'Month' => true,
|
||||
'Year' => true,
|
||||
'Time' => true,
|
||||
'TimezoneOffset' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* Lookup table for allowed formats
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedFormats = array();
|
||||
|
||||
/**
|
||||
* @param array $allowedFormats OPTIONAL
|
||||
* @throws HTMLPurifier_Exception If an invalid format is provided
|
||||
*/
|
||||
public function __construct(array $allowedFormats = array())
|
||||
{
|
||||
// Validate allowed formats
|
||||
$allowedFormatsLookup = array();
|
||||
foreach ($allowedFormats as $format) {
|
||||
if (!isset(self::$formats[$format])) {
|
||||
throw new HTMLPurifier_Exception("'$format' is not a valid format");
|
||||
}
|
||||
$allowedFormatsLookup[$format] = true;
|
||||
}
|
||||
|
||||
// Formats must be set in the same order as in self::$formats, so that
|
||||
// in default mode the result will be the longest matching format
|
||||
foreach (self::$formats as $format => $_) {
|
||||
if (isset($allowedFormatsLookup[$format])) {
|
||||
$this->allowedFormats[$format] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->allowedFormats)) {
|
||||
$this->allowedFormats = array_filter(self::$formats, 'intval');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
if (($data = $this->parse($string)) === false) {
|
||||
return false;
|
||||
}
|
||||
return $this->format($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return array|bool
|
||||
*/
|
||||
public function parse($string)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if ($string === '' || !preg_match(self::REGEX, $string, $match)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure all named patterns are present in the match array
|
||||
$match += array(
|
||||
'year' => '',
|
||||
'month' => '',
|
||||
'day' => '',
|
||||
'hour' => '',
|
||||
'minute' => '',
|
||||
'second' => '',
|
||||
'tzZulu' => '',
|
||||
'tzHour' => '',
|
||||
'tzMinute' => '',
|
||||
);
|
||||
|
||||
$year = $month = $day = null;
|
||||
|
||||
if ($match['year'] !== '') {
|
||||
$year = (int) $match['year'];
|
||||
|
||||
// Dates before the year one can't be represented as a datetime in HTML5
|
||||
if ($year <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($match['month'] !== '') {
|
||||
$month = (int) $match['month'];
|
||||
if ($month < 1 || $month > 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($match['day'] !== '') {
|
||||
$day = (int) $match['day'];
|
||||
if (!checkdate($month, $day, $year)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hour = $minute = $second = null;
|
||||
|
||||
if ($match['hour'] !== '') {
|
||||
$hour = (int) $match['hour'];
|
||||
$minute = (int) $match['minute'];
|
||||
$second = $match['second'] !== '' ? (float) $match['second'] : null;
|
||||
|
||||
if ($hour > 23) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$tzHour = $tzMinute = null;
|
||||
|
||||
if ($match['tzZulu'] !== '') {
|
||||
$tzHour = 'Z';
|
||||
|
||||
} elseif ($match['tzHour'] !== '') {
|
||||
$tzHour = (int) $match['tzHour'];
|
||||
$tzMinute = (int) $match['tzMinute'];
|
||||
|
||||
if ($tzHour < -23 || $tzHour > 23) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return compact(
|
||||
'year', 'month', 'day', 'hour', 'minute', 'second', 'tzHour', 'tzMinute'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function format(array $data)
|
||||
{
|
||||
foreach ($this->allowedFormats as $format => $_) {
|
||||
if (($result = call_user_func(array($this, 'format' . $format), $data)) !== false) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatYear(array $data)
|
||||
{
|
||||
if (($year = $data['year']) === null) {
|
||||
return false;
|
||||
}
|
||||
return sprintf('%04d', $year);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#months
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatMonth(array $data)
|
||||
{
|
||||
if (($year = $data['year']) === null ||
|
||||
($month = $data['month']) === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return sprintf('%04d-%02d', $year, $month);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#dates
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatDate(array $data)
|
||||
{
|
||||
if (($year = $data['year']) === null ||
|
||||
($month = $data['month']) === null ||
|
||||
($day = $data['day']) === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#times
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatTime(array $data)
|
||||
{
|
||||
if (($hour = $data['hour']) === null ||
|
||||
($minute = $data['minute']) === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
$time = sprintf('%02d:%02d', $hour, $minute);
|
||||
if (($second = $data['second']) !== null) {
|
||||
$sec = (int) $second;
|
||||
$time .= sprintf(':%02d', $sec);
|
||||
|
||||
$msec = round(($second - $sec) * 1000);
|
||||
if ($msec > 0) {
|
||||
$time .= rtrim(sprintf('.%03d', $msec), '0');
|
||||
}
|
||||
}
|
||||
return $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#local-dates-and-times
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatDatetimeLocal(array $data)
|
||||
{
|
||||
if (($date = $this->formatDate($data)) === false ||
|
||||
($time = $this->formatTime($data)) === false
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// Use 'T' as normalized date/time separator, see:
|
||||
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-normalised-local-date-and-time-string
|
||||
// Also it's the only separator recognized by input[type=datetime-local]
|
||||
return $date . 'T' . $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats data as datetime with timezone offset. If no timezone offset
|
||||
* is present, the default server timezone offset is used.
|
||||
*
|
||||
* @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#global-dates-and-times
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatDatetimeGlobal(array $data)
|
||||
{
|
||||
if (($datetime = $this->formatDatetimeLocal($data)) === false) {
|
||||
return false;
|
||||
}
|
||||
if (($timezoneOffset = $this->formatTimezoneOffset($data)) === false) {
|
||||
$timezoneOffset = date('P');
|
||||
}
|
||||
return $datetime . $timezoneOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats data as datetime with optional timezone offset.
|
||||
*
|
||||
* This is used in particular for 'datetime' attribute of <time> element.
|
||||
*
|
||||
* @param array $data
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function formatDatetime(array $data)
|
||||
{
|
||||
if (($datetime = $this->formatDatetimeLocal($data)) === false) {
|
||||
return false;
|
||||
}
|
||||
if (($timezoneOffset = $this->formatTimezoneOffset($data)) !== false) {
|
||||
$datetime .= $timezoneOffset;
|
||||
}
|
||||
return $datetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#time-zones
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function formatTimezoneOffset(array $data)
|
||||
{
|
||||
if (($tzHour = $data['tzHour']) === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($tzHour === 'Z') {
|
||||
$tzOffset = 'Z';
|
||||
} else {
|
||||
$tzMinute = (int) $data['tzMinute'];
|
||||
$tzOffset = sprintf('%s%02d:%02d', $tzHour < 0 ? '-' : '+', abs($tzHour), $tzMinute);
|
||||
}
|
||||
|
||||
return $tzOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $formats
|
||||
* @return HTMLPurifier_AttrDef_HTML5_Datetime
|
||||
* @throws HTMLPurifier_Exception If an invalid format is provided
|
||||
*/
|
||||
public function make($formats)
|
||||
{
|
||||
return new self(explode(',', $formats));
|
||||
}
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates HTML5 duration string according to spec
|
||||
* https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#durations
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML5_Duration extends HTMLPurifier_AttrDef
|
||||
{
|
||||
const REGEX_ISO8601 = '/^
|
||||
P
|
||||
(?P<w>\d+W)?
|
||||
(?P<d>\d+D)?
|
||||
(
|
||||
T
|
||||
(?P<h>\d+H)?
|
||||
(?P<m>\d+M)?
|
||||
(?P<s>\d+(\.\d+)?S)?
|
||||
)?
|
||||
$/xi';
|
||||
|
||||
const REGEX_HUMAN = '/(\d+(\s*[WDHMS]|\.\d+\s*S))/i';
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if (($result = $this->validateISODuration($string)) !== false) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (($result = $this->validateHumanDuration($string)) !== false) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate ISO-8601 duration string
|
||||
*
|
||||
* Note: duration as defined in the HTML5 spec cannot include months or years
|
||||
*
|
||||
* @param string $string
|
||||
* @return boolean
|
||||
*/
|
||||
protected function validateISODuration($string)
|
||||
{
|
||||
if (!preg_match(self::REGEX_ISO8601, $string, $match)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parts = array(
|
||||
'w' => 0,
|
||||
'd' => 0,
|
||||
'h' => 0,
|
||||
'm' => 0,
|
||||
's' => 0,
|
||||
);
|
||||
|
||||
foreach ($parts as $unit => $_) {
|
||||
if (!isset($match[$unit])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = substr($match[$unit], 0, -1);
|
||||
$value = $unit === 's' ? (float) $value : (int) $value;
|
||||
|
||||
$parts[$unit] = $value;
|
||||
}
|
||||
|
||||
// The spec self-contradicts itself in disallowing weeks in ISO-8601
|
||||
// format, but allowing them in Human format - each week being equal
|
||||
// to 604800 seconds (7 days).
|
||||
|
||||
if ($parts['w'] > 0) {
|
||||
$parts['d'] += $parts['w'] * 7;
|
||||
$parts['w'] = 0;
|
||||
}
|
||||
|
||||
$duration = 'P';
|
||||
foreach ($parts as $unit => $value) {
|
||||
if ($unit === 'h') {
|
||||
$duration .= 'T';
|
||||
}
|
||||
|
||||
if ($value > 0) {
|
||||
$duration .= ($unit === 's' ? $this->formatSeconds($value) : $value) . strtoupper($unit);
|
||||
}
|
||||
}
|
||||
$duration = rtrim($duration, 'T');
|
||||
|
||||
// At least one element must be present, thus "P" is not a valid
|
||||
// representation for a duration of 0 seconds. "PT0S" or "P0D" are both
|
||||
// valid and represent the same duration.
|
||||
// https://en.wikipedia.org/wiki/ISO_8601#Durations
|
||||
|
||||
if ($duration === 'P') {
|
||||
$duration = 'PT0S';
|
||||
}
|
||||
|
||||
return $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate human readable HTML5 duration string
|
||||
*
|
||||
* @param string $string
|
||||
* @return boolean|string
|
||||
*/
|
||||
protected function validateHumanDuration($string)
|
||||
{
|
||||
if (!preg_match_all(self::REGEX_HUMAN, $string, $matches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// One or more duration time components, each with a different duration
|
||||
// time component scale, in any order.
|
||||
$parts = array(
|
||||
'w' => false,
|
||||
'd' => false,
|
||||
'h' => false,
|
||||
'm' => false,
|
||||
's' => false,
|
||||
);
|
||||
|
||||
foreach ($matches[0] as $match) {
|
||||
$unit = strtolower(substr($match, -1));
|
||||
|
||||
$value = rtrim(substr($match, 0, -1));
|
||||
$value = $unit === 's' ? (float) $value : (int) $value;
|
||||
|
||||
if ($value > 0 && $parts[$unit] === false) {
|
||||
$parts[$unit] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$duration = array();
|
||||
foreach ($parts as $unit => $value) {
|
||||
if ($value === false) {
|
||||
continue;
|
||||
}
|
||||
$duration[] = ($unit === 's' ? $this->formatSeconds($value) : $value) . $unit;
|
||||
}
|
||||
$duration = implode(' ', $duration);
|
||||
|
||||
if ($duration === '') {
|
||||
$duration = '0s';
|
||||
}
|
||||
|
||||
return $duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats seconds without leading zero and at most 3 non-zero decimals
|
||||
*
|
||||
* @param float $sec
|
||||
* @return string
|
||||
*/
|
||||
protected function formatSeconds($sec)
|
||||
{
|
||||
$msec = round(($sec - (int) $sec) * 1000);
|
||||
if ($msec > 0) {
|
||||
return rtrim(sprintf('%d.%03d', $sec, $msec), '0');
|
||||
}
|
||||
return sprintf('%d', $sec);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates HTML5 week string according to
|
||||
* https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#weeks
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML5_Week extends HTMLPurifier_AttrDef
|
||||
{
|
||||
const REGEX = '/^
|
||||
(?P<year>\d{4,})
|
||||
-W
|
||||
(?P<week>[0-5]\d)
|
||||
$/xi';
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if (!preg_match(self::REGEX, $string, $match)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$year = (int) $match['year'];
|
||||
$week = (int) $match['week'];
|
||||
|
||||
if ($year <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ISO-8601 specification says that December 28th is always in the last
|
||||
// week of its year.
|
||||
// https://en.wikipedia.org/wiki/ISO_8601#Week_dates
|
||||
$time = mktime(0, 0, 0, 12, 28, $year);
|
||||
|
||||
if ($week < 1 || $week > date('W', $time)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates HTML5 yearless date string according to the spec
|
||||
* https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#yearless-dates
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML5_YearlessDate extends HTMLPurifier_AttrDef
|
||||
{
|
||||
const REGEX = '/^(?P<month>[01]\d)-(?P<day>[0-3]\d)$/';
|
||||
|
||||
protected static $daysInMonths = array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|string
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if (!preg_match(self::REGEX, $string, $match)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$month = (int) $match['month'];
|
||||
|
||||
if ($month < 1 || $month > 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$day = (int) $match['day'];
|
||||
|
||||
if ($day < 1 || $day > self::$daysInMonths[$month - 1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_AttrDef_Regexp extends HTMLPurifier_AttrDef
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $pattern;
|
||||
|
||||
/**
|
||||
* @param string $pattern
|
||||
*/
|
||||
public function __construct($pattern = null)
|
||||
{
|
||||
if ($pattern !== null) {
|
||||
$pattern = (string) $pattern;
|
||||
if (false === @preg_match($pattern, 'Test')) {
|
||||
throw new HTMLPurifier_Exception('Invalid regular expression pattern provided');
|
||||
}
|
||||
$this->pattern = $pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
if ($this->pattern) {
|
||||
return (bool) preg_match($this->pattern, $string);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string
|
||||
* @return HTMLPurifier_AttrDef_Regexp
|
||||
*/
|
||||
public function make($string)
|
||||
{
|
||||
return new self($string);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_AttrTransform_HTML5_Dialog extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
/**
|
||||
* @param array $attr
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function transform($attr, $config, $context)
|
||||
{
|
||||
// The tabindex attribute must not be specified on dialog elements.
|
||||
// https://html.spec.whatwg.org/dev/interactive-elements.html#the-dialog-element
|
||||
unset($attr['tabindex']);
|
||||
|
||||
return $attr;
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Post-transform performing validations for <progress> elements ensuring
|
||||
* that if value is present, it is within a valid range (0..1) or (0..max)
|
||||
*
|
||||
* Implementation is based on sanitization performed by browsers (compared
|
||||
* against Chrome 68 and Firefox 61).
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_HTML5_Progress extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
/**
|
||||
* @param array $attr
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function transform($attr, $config, $context)
|
||||
{
|
||||
$max = isset($attr['max']) ? (float) $attr['max'] : 1;
|
||||
|
||||
if ($max <= 0) {
|
||||
$this->confiscateAttr($attr, 'max');
|
||||
}
|
||||
|
||||
if (isset($attr['value'])) {
|
||||
$value = (float) $attr['value'];
|
||||
|
||||
if ($value < 0) {
|
||||
$this->confiscateAttr($attr, 'value');
|
||||
} elseif ($value > $max) {
|
||||
$attr['value'] = isset($attr['max']) ? $attr['max'] : 1;
|
||||
}
|
||||
}
|
||||
|
||||
return $attr;
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_AttrTransform_HTML5_Script extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
/**
|
||||
* @param array $attr
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function transform($attr, $config, $context)
|
||||
{
|
||||
// If 'src' is specified, it must be a valid non-empty URL potentially
|
||||
// surrounded by spaces.
|
||||
// If 'src' is present, regardless it's empty or not, script text is
|
||||
// ignored by browsers.
|
||||
if (isset($attr['src']) && trim($attr['src']) === '') {
|
||||
unset($attr['src']);
|
||||
}
|
||||
|
||||
return $attr;
|
||||
}
|
||||
}
|
||||
+22
-23
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_HTML5_Details extends HTMLPurifier_ChildDef
|
||||
class HTMLPurifier_ChildDef_Details extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'details';
|
||||
|
||||
@@ -33,53 +33,52 @@ class HTMLPurifier_ChildDef_HTML5_Details extends HTMLPurifier_ChildDef
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array|bool
|
||||
* @return array
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
// if summary is not allowed, delete parent node
|
||||
if (empty($children)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($config->getHTMLDefinition()->info['summary'])) {
|
||||
trigger_error("Cannot allow details without allowing summary", E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
$summary = array();
|
||||
$spaces = array();
|
||||
$others = array();
|
||||
|
||||
while ($children) {
|
||||
$child = reset($children);
|
||||
if ($child instanceof HTMLPurifier_Node_Text && $child->is_whitespace) {
|
||||
$spaces[] = array_shift($children);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
$summary = null;
|
||||
$result = array();
|
||||
|
||||
// Content model:
|
||||
// One summary element followed by flow content
|
||||
foreach ($children as $node) {
|
||||
if (!$summary && $node->name === 'summary') {
|
||||
$summary[] = $node;
|
||||
$summary = $node;
|
||||
continue;
|
||||
}
|
||||
if ($node->name === 'summary') {
|
||||
// duplicated summary, add only its children
|
||||
$others = array_merge($others, (array) $node->children);
|
||||
$result = array_merge($result, (array) $node->children);
|
||||
} else {
|
||||
$others[] = $node;
|
||||
$result[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
$whitespaceOnly = true;
|
||||
foreach ($result as $node) {
|
||||
$whitespaceOnly = $whitespaceOnly && !empty($node->is_whitespace);
|
||||
}
|
||||
|
||||
if (!$summary) {
|
||||
// remove empty <details> without <summary>
|
||||
if (!$others) {
|
||||
// remove parent node if there are no children or all children are whitespace-only
|
||||
if ($whitespaceOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$summary[] = new HTMLPurifier_Node_Element('summary');
|
||||
$summary = new HTMLPurifier_Node_Element('summary');
|
||||
}
|
||||
|
||||
return array_merge($spaces, $summary, $others);
|
||||
array_unshift($result, $summary);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
+21
-4
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_HTML5_Figure extends HTMLPurifier_ChildDef
|
||||
class HTMLPurifier_ChildDef_Figure extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'figure';
|
||||
|
||||
@@ -33,10 +33,11 @@ class HTMLPurifier_ChildDef_HTML5_Figure extends HTMLPurifier_ChildDef
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array|bool
|
||||
* @return array
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
$allowFigcaption = isset($config->getHTMLDefinition()->info['figcaption']);
|
||||
$hasFigcaption = false;
|
||||
$figcaptionPos = -1;
|
||||
|
||||
@@ -47,14 +48,20 @@ class HTMLPurifier_ChildDef_HTML5_Figure extends HTMLPurifier_ChildDef
|
||||
// Or: flow content followed by one figcaption element.
|
||||
// Or: flow content.
|
||||
|
||||
// Scan through children, accept at most one figcaption.
|
||||
// Scan through children, accept at most one figcaption. If additional
|
||||
// figcaption appears replace it with div
|
||||
foreach ($children as $node) {
|
||||
if ($node->name === 'figcaption') {
|
||||
if (!$hasFigcaption) {
|
||||
if ($allowFigcaption && !$hasFigcaption) {
|
||||
$hasFigcaption = true;
|
||||
$figcaptionPos = count($result);
|
||||
$result[] = $node;
|
||||
continue;
|
||||
}
|
||||
|
||||
$div = new HTMLPurifier_Node_Element('div', $node->attr);
|
||||
$div->children = $node->children;
|
||||
$result[] = $div;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -67,6 +74,16 @@ class HTMLPurifier_ChildDef_HTML5_Figure extends HTMLPurifier_ChildDef
|
||||
$result[] = $node;
|
||||
}
|
||||
|
||||
$whitespaceOnly = true;
|
||||
foreach ($result as $node) {
|
||||
$whitespaceOnly = $whitespaceOnly && !empty($node->is_whitespace);
|
||||
}
|
||||
|
||||
// remove parent node if there are no children or all children are whitespace-only
|
||||
if (empty($result) || $whitespaceOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
abstract class HTMLPurifier_ChildDef_HTML5_Abstract extends HTMLPurifier_ChildDef
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $allowedElements = array();
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $init = false;
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @throws HTMLPurifier_Exception
|
||||
*/
|
||||
public function __construct($type = null)
|
||||
{
|
||||
if ($type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
// Ensure that the type property is not empty, otherwise the element
|
||||
// will be treated as having an Empty content model (closing tag will
|
||||
// be omitted) if no children are present.
|
||||
if (empty($this->type)) {
|
||||
throw new HTMLPurifier_Exception("The 'type' property is not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedElements($config)
|
||||
{
|
||||
$this->init($config);
|
||||
return $this->allowedElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @return void
|
||||
*/
|
||||
protected function init(HTMLPurifier_Config $config)
|
||||
{
|
||||
if ($this->init) {
|
||||
return;
|
||||
}
|
||||
|
||||
$def = $config->getHTMLDefinition();
|
||||
|
||||
$elements = array();
|
||||
foreach ($this->elements as $name => $_) {
|
||||
if (is_int($name)) {
|
||||
$name = $_;
|
||||
}
|
||||
if (isset($def->info_content_sets[$name])) {
|
||||
$elements = array_merge($elements, $def->info_content_sets[$name]);
|
||||
} else {
|
||||
$elements[$name] = true;
|
||||
}
|
||||
}
|
||||
$this->allowedElements = $elements;
|
||||
|
||||
$this->init = true;
|
||||
}
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_HTML5_Fieldset extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'fieldset';
|
||||
|
||||
public $elements = array(
|
||||
'legend' => true,
|
||||
);
|
||||
|
||||
protected $allowedElements;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedElements($config)
|
||||
{
|
||||
if (null === $this->allowedElements) {
|
||||
// Add Flow content to allowed elements to prevent MakeWellFormed
|
||||
// strategy moving them outside details element
|
||||
$def = $config->getHTMLDefinition();
|
||||
|
||||
$this->allowedElements = array_merge(
|
||||
$def->info_content_sets['Flow'],
|
||||
$this->elements
|
||||
);
|
||||
}
|
||||
return $this->allowedElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array|bool
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
$children = (array) $children;
|
||||
|
||||
// Content model:
|
||||
// An optional <legend> element, followed by flow content.
|
||||
|
||||
// Only one legend element should occur in the content and if present
|
||||
// should only be preceded by whitespace.
|
||||
// https://www.w3.org/TR/xhtml1/dtds.html
|
||||
|
||||
$legend = array();
|
||||
$spaces = array();
|
||||
$others = array();
|
||||
|
||||
while ($children) {
|
||||
$child = reset($children);
|
||||
if ($child instanceof HTMLPurifier_Node_Text && $child->is_whitespace) {
|
||||
$spaces[] = array_shift($children);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($children as $node) {
|
||||
if (!$legend && $node->name === 'legend') {
|
||||
$legend[] = $node;
|
||||
continue;
|
||||
}
|
||||
if ($node->name === 'legend') {
|
||||
// duplicated <legend>, add only its children
|
||||
$others = array_merge($others, (array) $node->children);
|
||||
} else {
|
||||
$others[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return array_merge($spaces, $legend, $others);
|
||||
}
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_HTML5_Script extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'script';
|
||||
|
||||
/**
|
||||
* Whether children (text contents) are allowed
|
||||
* @var bool
|
||||
*/
|
||||
public $allow_children = true;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Node[] $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return HTMLPurifier_Node[]|bool
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
$node = $context->exists('CurrentNode')
|
||||
? $context->get('CurrentNode')
|
||||
: null;
|
||||
|
||||
// Content model:
|
||||
// If there is no src attribute, depends on the value of the type
|
||||
// attribute, but must match script content restrictions.
|
||||
// If there is a src attribute, the element must be either empty
|
||||
// or contain only script documentation that also matches script
|
||||
// content restrictions.
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#the-script-element
|
||||
|
||||
if ($node instanceof HTMLPurifier_Node_Element) {
|
||||
// must validate src attribute here, because children validation is
|
||||
// executed before attribute validation
|
||||
|
||||
// This part I don't like, but currently it's unavoidable because
|
||||
// of how HTMLPurifier works internally. Attribute transformations
|
||||
// and validations are done after children validation. So there is
|
||||
// no way of knowing whether src attribute is valid other than
|
||||
// do the validation here as well.
|
||||
$src = $this->getSrc($node, $config, $context);
|
||||
|
||||
if (strlen($src)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Remove <script> if there is no 'src' attribute and no children
|
||||
// or if children are explicitly forbidden
|
||||
if (empty($children) || !$this->allow_children) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->allow_children ? true : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Node_Element $element
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return string
|
||||
*/
|
||||
protected function getSrc(HTMLPurifier_Node_Element $element, HTMLPurifier_Config $config, HTMLPurifier_Context $context)
|
||||
{
|
||||
$src = isset($element->attr['src']) ? trim($element->attr['src']) : '';
|
||||
|
||||
if (strlen($src)) {
|
||||
$info = $config->getHTMLDefinition()->info['script'];
|
||||
if (isset($info->attr['src'])) {
|
||||
/** @var HTMLPurifier_AttrDef $srcAttrDef */
|
||||
$srcAttrDef = $info->attr['src'];
|
||||
|
||||
$result = $srcAttrDef->validate($src, $config, $context);
|
||||
$src = $result === true ? $src : $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $src;
|
||||
}
|
||||
}
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Definition for <time> element contents
|
||||
*
|
||||
* As a side effect for child validation it ensures that 'datetime' attribute
|
||||
* is present if text content of the children is not a valid datetime string.
|
||||
*
|
||||
* @see https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-time-element
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_HTML5_Time extends HTMLPurifier_ChildDef_HTML5_Abstract
|
||||
{
|
||||
public $type = 'time';
|
||||
|
||||
public $allow_empty = true;
|
||||
|
||||
public $elements = array(
|
||||
'Inline' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Node[] $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool|HTMLPurifier_Node[]
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
$currentNode = $context->get('CurrentNode', true);
|
||||
|
||||
if ($currentNode instanceof HTMLPurifier_Node_Element) {
|
||||
// Unfortunately at this point invalid 'datetime' attribute is not
|
||||
// yet removed, so we need to validate it here
|
||||
|
||||
/** @var HTMLPurifier_AttrDef $attr */
|
||||
$attr = $config->getHTMLDefinition()->info['time']->attr['datetime'];
|
||||
|
||||
$datetime = isset($currentNode->attr['datetime'])
|
||||
? $attr->validate($currentNode->attr['datetime'], $config, $context)
|
||||
: false;
|
||||
|
||||
// If datetime attribute is invalid, we need to check whether element's
|
||||
// contents are a valid datetime string. If not, add a dummy datetime
|
||||
// attribute with UNIX epoch date, to satisfy the spec requirements.
|
||||
// This can't be done in the AttrTransform step, because CurrentNode is
|
||||
// not available there.
|
||||
|
||||
if ($datetime === false) {
|
||||
$textContent = '';
|
||||
|
||||
foreach ($currentNode->children as $child) {
|
||||
if ($child instanceof HTMLPurifier_Node_Element) {
|
||||
$textContent = '';
|
||||
break;
|
||||
} elseif ($child instanceof HTMLPurifier_Node_Text) {
|
||||
$textContent .= $child->data;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$attr->validate($textContent, $config, $context)) {
|
||||
$currentNode->attr['datetime'] = '1970-01-01';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_HTML5_Media extends HTMLPurifier_ChildDef
|
||||
class HTMLPurifier_ChildDef_Media extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'media';
|
||||
|
||||
@@ -38,7 +38,7 @@ class HTMLPurifier_ChildDef_HTML5_Media extends HTMLPurifier_ChildDef
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array|bool
|
||||
* @return array
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_HTML5_Picture extends HTMLPurifier_ChildDef
|
||||
class HTMLPurifier_ChildDef_Picture extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'picture';
|
||||
|
||||
@@ -13,7 +13,7 @@ class HTMLPurifier_ChildDef_HTML5_Picture extends HTMLPurifier_ChildDef
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array|bool
|
||||
* @return array
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
@@ -27,14 +27,16 @@ class HTMLPurifier_ChildDef_HTML5_Picture extends HTMLPurifier_ChildDef
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowSource = isset($config->getHTMLDefinition()->info['source']);
|
||||
$hasImg = false;
|
||||
|
||||
$result = array();
|
||||
|
||||
// Content model:
|
||||
// Zero or more source elements, followed by one img element, optionally intermixed with script-supporting elements.
|
||||
// https://html.spec.whatwg.org/multipage/embedded-content.html#the-picture-element
|
||||
foreach ($children as $node) {
|
||||
if ($node->name === 'source' || $node->name === 'img') {
|
||||
if (($allowSource && $node->name === 'source') || $node->name === 'img') {
|
||||
$result[] = $node;
|
||||
}
|
||||
if ($node->name === 'img') {
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
class HTMLPurifier_HTML5Config extends HTMLPurifier_Config
|
||||
{
|
||||
const REVISION = 2019080701;
|
||||
const REVISION = 2018060702;
|
||||
|
||||
/**
|
||||
* @param string|array|HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_ConfigSchema $schema OPTIONAL
|
||||
* @return HTMLPurifier_HTML5Config
|
||||
* @param HTMLPurifier_ConfigSchema $schema
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
public static function create($config, $schema = null)
|
||||
{
|
||||
@@ -21,6 +21,9 @@ class HTMLPurifier_HTML5Config extends HTMLPurifier_Config
|
||||
}
|
||||
|
||||
$configObj = new self($schema);
|
||||
$configObj->set('Core.Encoding', 'UTF-8');
|
||||
$configObj->set('HTML.Doctype', 'HTML 4.01 Transitional');
|
||||
|
||||
$configObj->set('HTML.DefinitionID', __CLASS__);
|
||||
$configObj->set('HTML.DefinitionRev', self::REVISION);
|
||||
|
||||
@@ -37,49 +40,26 @@ class HTMLPurifier_HTML5Config extends HTMLPurifier_Config
|
||||
/**
|
||||
* Creates a configuration object using the default config schema instance
|
||||
*
|
||||
* @return HTMLPurifier_HTML5Config
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
public static function createDefault()
|
||||
{
|
||||
return self::create(null);
|
||||
$schema = HTMLPurifier_ConfigSchema::instance();
|
||||
$config = self::create(null, $schema);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new config object that inherits from a previous one
|
||||
*
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @return HTMLPurifier_HTML5Config
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
public static function inherit(HTMLPurifier_Config $config)
|
||||
{
|
||||
return new self($config->def, $config->plist);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_ConfigSchema $schema
|
||||
* @param HTMLPurifier_PropertyList $parent OPTIONAL
|
||||
*/
|
||||
public function __construct(HTMLPurifier_ConfigSchema $schema, HTMLPurifier_PropertyList $parent = null)
|
||||
{
|
||||
// ensure 'HTML5' is among allowed 'HTML.Doctype' values
|
||||
$doctypeConfig = $schema->info['HTML.Doctype'];
|
||||
|
||||
if (empty($doctypeConfig->allowed['HTML5'])) {
|
||||
$allowed = array_merge($doctypeConfig->allowed, array('HTML5' => true));
|
||||
$schema->addAllowedValues('HTML.Doctype', $allowed);
|
||||
}
|
||||
|
||||
if (empty($schema->info['HTML.IframeAllowFullscreen'])) {
|
||||
$schema->add('HTML.IframeAllowFullscreen', false, 'bool', false);
|
||||
}
|
||||
|
||||
parent::__construct($schema, $parent);
|
||||
|
||||
$this->set('HTML.Doctype', 'HTML5');
|
||||
$this->set('Attr.ID.HTML5', true);
|
||||
$this->set('Output.CommentScriptContents', false);
|
||||
}
|
||||
|
||||
public function getDefinition($type, $raw = false, $optimized = false)
|
||||
{
|
||||
// Setting HTML.* keys removes any previously instantiated HTML
|
||||
@@ -87,8 +67,7 @@ class HTMLPurifier_HTML5Config extends HTMLPurifier_Config
|
||||
$needSetup = $type === 'HTML' && !isset($this->definitions[$type]);
|
||||
if ($needSetup) {
|
||||
if ($def = parent::getDefinition($type, true, true)) {
|
||||
/** @var HTMLPurifier_HTMLDefinition $def */
|
||||
HTMLPurifier_HTML5Definition::setupDefinition($def);
|
||||
HTMLPurifier_HTML5Definition::setup($def);
|
||||
}
|
||||
}
|
||||
return parent::getDefinition($type, $raw, $optimized);
|
||||
|
||||
+98
-30
@@ -7,43 +7,111 @@ class HTMLPurifier_HTML5Definition
|
||||
*
|
||||
* @param HTMLPurifier_HTMLDefinition $def
|
||||
* @return HTMLPurifier_HTMLDefinition
|
||||
* @throws HTMLPurifier_Exception
|
||||
*/
|
||||
public static function setupDefinition(HTMLPurifier_HTMLDefinition $def)
|
||||
public static function setup(HTMLPurifier_HTMLDefinition $def)
|
||||
{
|
||||
$def->manager->doctypes->register(
|
||||
'HTML5',
|
||||
false,
|
||||
// Order of modules is important - the latter ones override the former.
|
||||
// Place common HTML5 modules at the end of the list
|
||||
array(
|
||||
'CommonAttributes', 'HTML5_Text', 'HTML5_Hypertext', 'HTML5_List',
|
||||
'Presentation', 'HTML5_Edit', 'HTML5_Bdo', 'Tables', 'Image',
|
||||
'StyleAttribute', 'HTML5_Media', 'HTML5_Ruby', 'Name',
|
||||
'NonXMLCommonAttributes',
|
||||
// Unsafe:
|
||||
'HTML5_Scripting', 'HTML5_Interactive', 'Object', 'HTML5_Forms',
|
||||
'HTML5_Iframe',
|
||||
),
|
||||
array('Tidy_Transitional', 'Tidy_Proprietary'),
|
||||
array()
|
||||
);
|
||||
|
||||
// override default SafeScripting module
|
||||
// Because how the built-in SafeScripting module is enabled in ModuleManager,
|
||||
// to override it exactly the same name must be provided (without HTML5_ prefix)
|
||||
$safeScripting = new HTMLPurifier_HTMLModule_HTML5_SafeScripting();
|
||||
$safeScripting->name = 'SafeScripting';
|
||||
$def->manager->registerModule($safeScripting);
|
||||
|
||||
// use fixed implementation of Boolean attributes, instead of a buggy
|
||||
// one provided with 4.6.0
|
||||
$def->manager->attrTypes->set('Bool', new HTMLPurifier_AttrDef_HTML_Bool2());
|
||||
|
||||
// add support for Floating point number attributes
|
||||
$def->manager->attrTypes->set('Float', new HTMLPurifier_AttrDef_Float());
|
||||
// http://developers.whatwg.org/sections.html
|
||||
$def->addElement('section', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('nav', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('article', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('aside', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('header', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('footer', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('main', 'Block', 'Flow', 'Common');
|
||||
|
||||
$def->manager->attrTypes->set('Datetime', new HTMLPurifier_AttrDef_HTML5_Datetime());
|
||||
// Content model actually excludes several tags, not modelled here
|
||||
$def->addElement('address', 'Block', 'Flow', 'Common');
|
||||
$def->addElement('hgroup', 'Block', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/grouping-content.html#the-figure-element
|
||||
$def->addElement('figure', 'Block', new HTMLPurifier_ChildDef_Figure(), 'Common');
|
||||
$def->addElement('figcaption', false, 'Flow', 'Common');
|
||||
|
||||
$mediaContent = new HTMLPurifier_ChildDef_Media();
|
||||
|
||||
// https://html.spec.whatwg.org/dev/media.html#the-video-element
|
||||
$def->addElement('video', 'Flow', $mediaContent, 'Common', array(
|
||||
'controls' => 'Bool',
|
||||
'height' => 'Length',
|
||||
'poster' => 'URI',
|
||||
'preload' => 'Enum#auto,metadata,none',
|
||||
'src' => 'URI',
|
||||
'width' => 'Length',
|
||||
));
|
||||
$def->getAnonymousModule()->addElementToContentSet('video', 'Inline');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/media.html#the-audio-element
|
||||
$def->addElement('audio', 'Flow', $mediaContent, 'Common', array(
|
||||
'controls' => 'Bool',
|
||||
'preload' => 'Enum#auto,metadata,none',
|
||||
'src' => 'URI',
|
||||
));
|
||||
$def->getAnonymousModule()->addElementToContentSet('audio', 'Inline');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/embedded-content.html#the-source-element
|
||||
$def->addElement('source', false, 'Empty', 'Common', array(
|
||||
'media' => 'Text',
|
||||
'sizes' => 'Text',
|
||||
'src' => 'URI',
|
||||
'srcset' => 'Text',
|
||||
'type' => 'Text',
|
||||
));
|
||||
|
||||
// https://html.spec.whatwg.org/dev/media.html#the-track-element
|
||||
$def->addElement('track', false, 'Empty', 'Common', array(
|
||||
'kind' => 'Enum#captions,chapters,descriptions,metadata,subtitles',
|
||||
'src' => 'URI',
|
||||
'srclang' => 'Text',
|
||||
'label' => 'Text',
|
||||
'default' => 'Bool',
|
||||
));
|
||||
|
||||
// https://html.spec.whatwg.org/dev/embedded-content.html#the-picture-element
|
||||
$def->addElement('picture', 'Flow', new HTMLPurifier_ChildDef_Picture(), 'Common');
|
||||
$def->getAnonymousModule()->addElementToContentSet('picture', 'Inline');
|
||||
|
||||
// http://developers.whatwg.org/text-level-semantics.html
|
||||
$def->addElement('s', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('var', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('sub', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('sup', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('mark', 'Inline', 'Inline', 'Common');
|
||||
$def->addElement('wbr', 'Inline', 'Empty', 'Core');
|
||||
|
||||
// http://developers.whatwg.org/edits.html
|
||||
$def->addElement('ins', 'Block', 'Flow', 'Common', array('cite' => 'URI', 'datetime' => 'Text'));
|
||||
$def->addElement('del', 'Block', 'Flow', 'Common', array('cite' => 'URI', 'datetime' => 'Text'));
|
||||
|
||||
// TIME
|
||||
$time = $def->addElement('time', 'Inline', 'Inline', 'Common', array('datetime' => 'Text', 'pubdate' => 'Bool'));
|
||||
$time->excludes = array('time' => true);
|
||||
|
||||
// https://html.spec.whatwg.org/dev/text-level-semantics.html#the-a-element
|
||||
$def->addElement('a', 'Flow', 'Flow', 'Common', array(
|
||||
'download' => 'Text',
|
||||
'hreflang' => 'Text',
|
||||
'rel' => 'Text',
|
||||
'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget(),
|
||||
'type' => 'Text',
|
||||
));
|
||||
|
||||
// IMG
|
||||
$def->addAttribute('img', 'srcset', 'Text');
|
||||
$def->addAttribute('img', 'sizes', 'Text');
|
||||
|
||||
// IFRAME
|
||||
$def->addAttribute('iframe', 'allowfullscreen', 'Bool');
|
||||
|
||||
// Interactive elements
|
||||
// https://html.spec.whatwg.org/dev/interactive-elements.html#the-details-element
|
||||
$def->addElement('details', 'Block', new HTMLPurifier_ChildDef_Details(), 'Common', array(
|
||||
'open' => 'Bool',
|
||||
));
|
||||
$def->addElement('summary', false, 'Flow', 'Common');
|
||||
|
||||
return $def;
|
||||
}
|
||||
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 Bi-directional text
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Bdo extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Bdo';
|
||||
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
public $attr_collections = array(
|
||||
'I18N' => array(
|
||||
'dir' => 'Enum#ltr,rtl,auto',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
// Bidirectional Text Override element
|
||||
// https://www.w3.org/TR/html50/text-level-semantics.html#the-bdo-element
|
||||
$bdo = $this->addElement(
|
||||
'bdo',
|
||||
'Inline',
|
||||
'Inline',
|
||||
array('Core', 'Lang'),
|
||||
array(
|
||||
'dir' => 'Enum#ltr,rtl', // required, the 'auto' value must not be specified
|
||||
)
|
||||
);
|
||||
$bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
|
||||
|
||||
// Bidirectional Isolate element
|
||||
// https://www.w3.org/TR/html50/text-level-semantics.html#the-bdi-element
|
||||
$this->addElement('bdi', 'Inline', 'Inline', 'Common');
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 extension to Edit Module
|
||||
* http://developers.whatwg.org/edits.html
|
||||
*
|
||||
* @property HTMLPurifier_ElementDef[] $info
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Edit extends HTMLPurifier_HTMLModule_Edit
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Edit';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @throws HTMLPurifier_Exception
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
parent::setup($config);
|
||||
|
||||
$editDatetime = new HTMLPurifier_AttrDef_HTML5_Datetime(array('Date', 'DatetimeGlobal'));
|
||||
|
||||
// https://html.spec.whatwg.org/dev/edits.html#the-ins-element
|
||||
$this->info['ins']->attr['datetime'] = $editDatetime;
|
||||
|
||||
// https://html.spec.whatwg.org/dev/edits.html#the-del-element
|
||||
$this->info['del']->attr['datetime'] = $editDatetime;
|
||||
}
|
||||
}
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 additions to built-in Forms module
|
||||
*
|
||||
* This module is marked as safe to support static elements like <progress>
|
||||
* out of the box. Only elements inherited from parent module are unsafe,
|
||||
* and enabled conditionally with %HTML.Trusted flag.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Forms extends HTMLPurifier_HTMLModule_Forms
|
||||
{
|
||||
public $name = 'HTML5_Forms';
|
||||
|
||||
public $safe = true;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
if ($config->get('HTML.Trusted')) {
|
||||
parent::setup($config);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/forms.html#the-form-element
|
||||
$form = $this->addElement(
|
||||
'form',
|
||||
'Form',
|
||||
'Flow',
|
||||
'Common',
|
||||
array(
|
||||
'accept-charset' => 'Charsets',
|
||||
'action' => 'URI',
|
||||
'method' => 'Enum#get,post',
|
||||
'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data,text/plain',
|
||||
'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget(),
|
||||
)
|
||||
);
|
||||
$form->excludes = array('form' => true);
|
||||
|
||||
$this->addElement(
|
||||
'fieldset',
|
||||
'Form',
|
||||
new HTMLPurifier_ChildDef_HTML5_Fieldset(),
|
||||
'Common',
|
||||
array(
|
||||
'name' => 'CDATA',
|
||||
'disabled' => 'Bool#disabled',
|
||||
// 'form' => 'IDREF', // IDREF not implemented, cannot allow
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/dev/form-elements.html#the-progress-element
|
||||
$progress = $this->addElement(
|
||||
'progress',
|
||||
'Flow',
|
||||
'Inline',
|
||||
'Common',
|
||||
array(
|
||||
'value' => 'Float#min:0',
|
||||
'max' => 'Float#min:0',
|
||||
)
|
||||
);
|
||||
$progress->excludes = array('progress' => true);
|
||||
$this->addElementToContentSet('progress', 'Inline');
|
||||
|
||||
$progress->attr_transform_post[] = new HTMLPurifier_AttrTransform_HTML5_Progress();
|
||||
}
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 compliant replacement for {@link HTMLPurifier_HTMLModule_Hypertext},
|
||||
* defining block-level hypertext links.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Hypertext extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Hypertext';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
// https://html.spec.whatwg.org/dev/text-level-semantics.html#the-a-element
|
||||
$a = $this->addElement('a', 'Flow', 'Flow', 'Common', array(
|
||||
'download' => 'Text',
|
||||
'href' => 'URI',
|
||||
'hreflang' => 'Text', // 'LanguageCode',
|
||||
'rel' => new HTMLPurifier_AttrDef_HTML5_ARel(),
|
||||
'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget(),
|
||||
'type' => 'Text',
|
||||
));
|
||||
$a->excludes = array('a' => true);
|
||||
$this->addElementToContentSet('a', 'Inline');
|
||||
}
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 compliant replacement for {@link HTMLPurifier_HTMLModule_Iframe}
|
||||
*
|
||||
* This module is not considered safe unless an Iframe whitelisting mechanism
|
||||
* is specified. Currently, the only such mechanism is %URL.SafeIframeRegexp
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Iframe extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
public $name = 'HTML5_Iframe';
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $safe = false;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
if ($config->get('HTML.SafeIframe')) {
|
||||
$this->safe = true;
|
||||
}
|
||||
|
||||
// HTML Living Standard does not allow content in iframes, whereas W3C
|
||||
// spec does. On the other hand W3C validator follows WHATWG spec.
|
||||
// See:
|
||||
// - https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element
|
||||
// - https://www.w3.org/TR/html52/semantics-embedded-content.html#the-iframe-element
|
||||
// - https://www.w3.org/TR/html50/embedded-content-0.html#the-iframe-element
|
||||
|
||||
// type must not be 'empty', otherwise <iframe> will not have an end tag
|
||||
$iframeContents = new HTMLPurifier_ChildDef_Empty();
|
||||
$iframeContents->type = 'iframe';
|
||||
|
||||
$iframe = $this->addElement(
|
||||
'iframe',
|
||||
'Inline',
|
||||
$iframeContents,
|
||||
'Common',
|
||||
array(
|
||||
'src' => 'URI#embedded',
|
||||
'width' => 'Length',
|
||||
'height' => 'Length',
|
||||
'name' => 'ID',
|
||||
// other attributes that are present in HTML4 / XHTML spec were
|
||||
// declared as non-conforming, and as such are not included here
|
||||
// https://www.w3.org/TR/2016/WD-html52-20161206/obsolete.html#non-conforming-features
|
||||
)
|
||||
);
|
||||
|
||||
if (isset($config->def->info['HTML.IframeAllowFullscreen']) &&
|
||||
$config->get('HTML.IframeAllowFullscreen')
|
||||
) {
|
||||
$iframe->attr['allowfullscreen'] = 'Bool#allowfullscreen';
|
||||
}
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 Interactive elements module
|
||||
* https://html.spec.whatwg.org/dev/interactive-elements.html
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Interactive extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Interactive';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
// https://html.spec.whatwg.org/dev/interactive-elements.html#the-details-element
|
||||
$this->addElement('details', 'Flow', new HTMLPurifier_ChildDef_HTML5_Details(), 'Common', array(
|
||||
'open' => 'Bool',
|
||||
));
|
||||
|
||||
// https://html.spec.whatwg.org/dev/interactive-elements.html#the-summary-element
|
||||
$this->addElement('summary', false, 'Flow', 'Common');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/interactive-elements.html#the-dialog-element
|
||||
$dialog = $this->addElement('dialog', 'Flow', 'Flow', 'Common', array(
|
||||
'open' => 'Bool',
|
||||
));
|
||||
|
||||
$dialog->attr_transform_pre[] = new HTMLPurifier_AttrTransform_HTML5_Dialog();
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 extension to {@link HTMLPurifier_HTMLModule_List}
|
||||
*
|
||||
* @property HTMLPurifier_ElementDef[] $info
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_List extends HTMLPurifier_HTMLModule_List
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_List';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
parent::setup($config);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-ol-element
|
||||
$ol = $this->info['ol'];
|
||||
$ol->attr['reversed'] = 'Bool#reversed';
|
||||
|
||||
// Attributes that were deprecated in HTML4, but reintroduced in HTML5
|
||||
$ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
|
||||
$ol->attr['type'] = 'Enum#s:1,a,A,i,I';
|
||||
}
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML5 Multimedia and embedded content
|
||||
*
|
||||
* https://html.spec.whatwg.org/dev/media.html
|
||||
* https://html.spec.whatwg.org/dev/embedded-content.html
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Media extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Media';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
$mediaContent = new HTMLPurifier_ChildDef_HTML5_Media();
|
||||
|
||||
// https://html.spec.whatwg.org/dev/media.html#the-video-element
|
||||
$this->addElement('video', 'Flow', $mediaContent, 'Common', array(
|
||||
'controls' => 'Bool',
|
||||
'height' => 'Length',
|
||||
'poster' => 'URI',
|
||||
'preload' => 'Enum#auto,metadata,none',
|
||||
'src' => 'URI',
|
||||
'width' => 'Length',
|
||||
));
|
||||
$this->addElementToContentSet('video', 'Inline');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/media.html#the-audio-element
|
||||
$this->addElement('audio', 'Flow', $mediaContent, 'Common', array(
|
||||
'controls' => 'Bool',
|
||||
'preload' => 'Enum#auto,metadata,none',
|
||||
'src' => 'URI',
|
||||
));
|
||||
$this->addElementToContentSet('audio', 'Inline');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/embedded-content.html#the-source-element
|
||||
$this->addElement('source', false, 'Empty', 'Common', array(
|
||||
'media' => 'Text',
|
||||
'sizes' => 'Text',
|
||||
'src' => 'URI',
|
||||
'srcset' => 'Text',
|
||||
'type' => 'Text',
|
||||
));
|
||||
|
||||
// https://html.spec.whatwg.org/dev/media.html#the-track-element
|
||||
$this->addElement('track', false, 'Empty', 'Common', array(
|
||||
'kind' => 'Enum#captions,chapters,descriptions,metadata,subtitles',
|
||||
'src' => 'URI',
|
||||
'srclang' => 'Text',
|
||||
'label' => 'Text',
|
||||
'default' => 'Bool',
|
||||
));
|
||||
|
||||
// https://html.spec.whatwg.org/dev/embedded-content.html#the-picture-element
|
||||
$this->addElement('picture', 'Flow', new HTMLPurifier_ChildDef_HTML5_Picture(), 'Common');
|
||||
$this->addElementToContentSet('picture', 'Inline');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/embedded-content.html#the-img-element
|
||||
$img = $this->addBlankElement('img');
|
||||
$img->attr = array(
|
||||
'srcset' => 'Text',
|
||||
'sizes' => 'Text',
|
||||
);
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* HTML 5.2 Ruby markup
|
||||
* https://html.spec.whatwg.org/multipage/text-level-semantics.html#the-ruby-element
|
||||
*
|
||||
* Note: {@link HTMLPurifier_HTMLModule_Ruby} implementation is based on
|
||||
* XHTML 1.1 Ruby Annotation module which differs from HTML5 spec.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Ruby extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Ruby';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
$this->addElement(
|
||||
'ruby',
|
||||
'Inline',
|
||||
'Custom: ((rb | Inline | #PCDATA)*, (rt | (rp, rt, rp) | rtc))+',
|
||||
'Common'
|
||||
);
|
||||
$this->addElement('rtc', false, 'Custom: (rt | rp | Inline | #PCDATA)*', 'Common');
|
||||
$this->addElement('rb', false, 'Custom: (Inline | #PCDATA)*', 'Common');
|
||||
$this->addElement('rt', false, 'Custom: (Inline | #PCDATA)*', 'Common');
|
||||
$this->addElement('rp', false, 'Custom: (Inline | #PCDATA)*', 'Common');
|
||||
|
||||
// <ruby> elements can be nested as children of <rtc>, <rb>, <rt> and <rp>
|
||||
// https://www.w3.org/TR/2014/NOTE-html-ruby-extensions-20140204/#changes-compared-to-the-current-ruby-model
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A "safe" script module. No inline JS is allowed, and pointed to JS
|
||||
* files must match whitelist.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_SafeScripting extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_SafeScripting';
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
// These definitions are not intrinsically safe: the attribute transforms
|
||||
// are a vital part of ensuring safety.
|
||||
|
||||
$allowed = $config->get('HTML.SafeScripting');
|
||||
|
||||
$scriptContents = new HTMLPurifier_ChildDef_HTML5_Script();
|
||||
$scriptContents->allow_children = false;
|
||||
|
||||
$script = $this->addElement(
|
||||
'script',
|
||||
'Inline',
|
||||
$scriptContents,
|
||||
null,
|
||||
array(
|
||||
'src' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed), true),
|
||||
'type' => new HTMLPurifier_AttrDef_Enum(array(
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#textjavascript
|
||||
'text/javascript',
|
||||
)),
|
||||
'async' => new HTMLPurifier_AttrDef_HTML_Bool2(),
|
||||
'defer' => new HTMLPurifier_AttrDef_HTML_Bool2(),
|
||||
'charset' => 'Enum#utf-8',
|
||||
)
|
||||
);
|
||||
|
||||
$script->attr_transform_pre[] = new HTMLPurifier_AttrTransform_HTML5_Script();
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* HTML5 Scripting
|
||||
*
|
||||
* WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
|
||||
* INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!
|
||||
*
|
||||
* https://www.w3.org/TR/html50/scripting-1.html
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Scripting extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Scripting';
|
||||
|
||||
/**
|
||||
* @type bool
|
||||
*/
|
||||
public $safe = false;
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
$noscript = $this->addElement('noscript', 'Flow', 'Required: Flow | #PCDATA', 'Common');
|
||||
$noscript->excludes = array('noscript' => true);
|
||||
$this->addElementToContentSet('noscript', 'Inline');
|
||||
|
||||
$scriptContents = new HTMLPurifier_ChildDef_HTML5_Script();
|
||||
$script = $this->addElement('script', 'Flow', $scriptContents, null, array(
|
||||
'src' => new HTMLPurifier_AttrDef_URI(true),
|
||||
'type' => new HTMLPurifier_AttrDef_Enum(array(
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types#textjavascript
|
||||
'text/javascript',
|
||||
)),
|
||||
'async' => new HTMLPurifier_AttrDef_HTML_Bool2(),
|
||||
'defer' => new HTMLPurifier_AttrDef_HTML_Bool2(),
|
||||
// If present, its value must be an ASCII case-insensitive match for "utf-8"
|
||||
// Deprecated: https://html.spec.whatwg.org/multipage/scripting.html#the-script-element
|
||||
'charset' => 'Enum#utf-8',
|
||||
));
|
||||
$this->addElementToContentSet('script', 'Inline');
|
||||
|
||||
$script->attr_transform_pre[] = new HTMLPurifier_AttrTransform_HTML5_Script();
|
||||
}
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Extension to {@link HTMLPurifier_HTMLModule_Text} defining HTML5 text-level
|
||||
* and grouping elements.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_HTML5_Text extends HTMLPurifier_HTMLModule_Text
|
||||
{
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
public $name = 'HTML5_Text';
|
||||
|
||||
public $content_sets = array(
|
||||
'Flow' => 'Heading | Block | Inline | Sectioning'
|
||||
);
|
||||
|
||||
/**
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @throws HTMLPurifier_Exception
|
||||
*/
|
||||
public function setup($config)
|
||||
{
|
||||
parent::setup($config);
|
||||
|
||||
// http://developers.whatwg.org/sections.html
|
||||
$this->addElement('section', 'Sectioning', 'Flow', 'Common');
|
||||
$this->addElement('nav', 'Sectioning', 'Flow', 'Common');
|
||||
$this->addElement('article', 'Sectioning', 'Flow', 'Common');
|
||||
$this->addElement('aside', 'Sectioning', 'Flow', 'Common');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/sections.html#the-header-element
|
||||
$header = $this->addElement('header', 'Block', 'Flow', 'Common');
|
||||
$header->excludes = $this->makeLookup('header', 'footer', 'main');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/sections.html#the-footer-element
|
||||
$footer = $this->addElement('footer', 'Block', 'Flow', 'Common');
|
||||
$footer->excludes = $this->makeLookup('header', 'footer', 'main');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/sections.html#the-address-element
|
||||
$address = $this->addElement('address', 'Block', 'Flow', 'Common');
|
||||
$address->excludes = $this->makeLookup(
|
||||
// no heading content
|
||||
// https://html.spec.whatwg.org/dev/dom.html#heading-content
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hgroup',
|
||||
// no sectioning content
|
||||
// https://html.spec.whatwg.org/dev/dom.html#sectioning-content
|
||||
'article', 'aside', 'nav', 'section',
|
||||
// no header, footer and address
|
||||
'address', 'footer', 'header'
|
||||
);
|
||||
|
||||
// https://html.spec.whatwg.org/dev/sections.html#the-hgroup-element
|
||||
$this->addElement('hgroup', 'Heading', 'Required: h1 | h2 | h3 | h4 | h5 | h6', 'Common');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/grouping-content.html#the-main-element
|
||||
$this->addElement('main', 'Block', 'Flow', 'Common');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/grouping-content.html#the-figure-element
|
||||
$this->addElement('figure', 'Block', new HTMLPurifier_ChildDef_HTML5_Figure(), 'Common');
|
||||
$this->addElement('figcaption', false, 'Flow', 'Common');
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-blockquote-element
|
||||
$this->addElement('blockquote', 'Block', 'Flow', 'Common', array(
|
||||
'cite' => 'URI',
|
||||
));
|
||||
|
||||
// http://developers.whatwg.org/text-level-semantics.html
|
||||
$this->addElement('s', 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('u', 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('var', 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('sub', 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('sup', 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('mark', 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('wbr', 'Inline', 'Empty', 'Core');
|
||||
|
||||
// https://html.spec.whatwg.org/dev/text-level-semantics.html#the-time-element
|
||||
// https://w3c.github.io/html-reference/datatypes.html#common.data.time-datetime-def
|
||||
// Composite attr def is sufficiently general to be used in non-CSS contexts
|
||||
$timeDatetime = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_HTML5_Datetime(),
|
||||
new HTMLPurifier_AttrDef_HTML5_YearlessDate(),
|
||||
new HTMLPurifier_AttrDef_HTML5_Week(),
|
||||
new HTMLPurifier_AttrDef_HTML5_Duration(),
|
||||
));
|
||||
$timeContents = new HTMLPurifier_ChildDef_HTML5_Time();
|
||||
$this->addElement('time', 'Inline', $timeContents, 'Common', array(
|
||||
'datetime' => $timeDatetime,
|
||||
));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user