html purifier 보강
This commit is contained in:
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates a boolean attribute
|
||||
*
|
||||
* HTMLPurifier 4.6.0 has broken support for boolean attributes, as reported
|
||||
* in: http://htmlpurifier.org/phorum/read.php?3,7631,7631
|
||||
|
||||
* This issue has almost been fixed in 4.7.0, boolean attributes are properly
|
||||
* parsed, but their values are not validated.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Bool2 extends HTMLPurifier_AttrDef_HTML_Bool
|
||||
{
|
||||
/**
|
||||
* @param string $string
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return bool
|
||||
*/
|
||||
public function validate($string, $config, $context)
|
||||
{
|
||||
$name = strlen($this->name) ? $this->name : $context->get('CurrentAttr');
|
||||
// boolean attribute validates if its value is either empty
|
||||
// or case-insensitively equal to attribute name
|
||||
return $string === '' || strcasecmp($name, $string) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $string Name of attribute
|
||||
* @return HTMLPurifier_AttrDef_HTML_Bool2
|
||||
*/
|
||||
public function make($string)
|
||||
{
|
||||
return new self($string);
|
||||
}
|
||||
}
|
||||
|
||||
// vim: et sw=4 sts=4
|
||||
@@ -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
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_Details extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'details';
|
||||
|
||||
public $elements = array(
|
||||
'summary' => 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
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
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 = null;
|
||||
$result = array();
|
||||
|
||||
// Content model:
|
||||
// One summary element followed by flow content
|
||||
foreach ($children as $node) {
|
||||
if (!$summary && $node->name === 'summary') {
|
||||
$summary = $node;
|
||||
continue;
|
||||
}
|
||||
if ($node->name === 'summary') {
|
||||
// duplicated summary, add only its children
|
||||
$result = array_merge($result, (array) $node->children);
|
||||
} else {
|
||||
$result[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
$whitespaceOnly = true;
|
||||
foreach ($result as $node) {
|
||||
$whitespaceOnly = $whitespaceOnly && !empty($node->is_whitespace);
|
||||
}
|
||||
|
||||
if (!$summary) {
|
||||
// remove parent node if there are no children or all children are whitespace-only
|
||||
if ($whitespaceOnly) {
|
||||
return false;
|
||||
}
|
||||
$summary = new HTMLPurifier_Node_Element('summary');
|
||||
}
|
||||
|
||||
array_unshift($result, $summary);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_Figure extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'figure';
|
||||
|
||||
public $elements = array(
|
||||
'figcaption' => 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 'figure' 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
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
$allowFigcaption = isset($config->getHTMLDefinition()->info['figcaption']);
|
||||
$hasFigcaption = false;
|
||||
$figcaptionPos = -1;
|
||||
|
||||
$result = array();
|
||||
|
||||
// Content model:
|
||||
// Either: one figcaption element followed by flow content.
|
||||
// Or: flow content followed by one figcaption element.
|
||||
// Or: flow content.
|
||||
|
||||
// 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 ($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;
|
||||
}
|
||||
|
||||
// Figcaption must be a first or last child of a figure element.
|
||||
// If it's not first, then we ignore all siblings that come after.
|
||||
if ($hasFigcaption && $figcaptionPos > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_Media extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'media';
|
||||
|
||||
public $elements = array(
|
||||
'source' => true,
|
||||
'track' => 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
|
||||
);
|
||||
unset(
|
||||
$this->allowedElements['audio'],
|
||||
$this->allowedElements['video']
|
||||
);
|
||||
}
|
||||
return $this->allowedElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
// Content model:
|
||||
// If the element has a src attribute: zero or more track elements,
|
||||
// then transparent, but with no media element descendants.
|
||||
// If the element does not have a src attribute: zero or more source
|
||||
// elements, then zero or more track elements, then transparent, but
|
||||
// with no media element descendants.
|
||||
|
||||
$allowSource = isset($config->getHTMLDefinition()->info['source']);
|
||||
$allowTrack = isset($config->getHTMLDefinition()->info['track']);
|
||||
|
||||
$sources = array();
|
||||
$tracks = array();
|
||||
$content = array();
|
||||
|
||||
foreach ($children as $node) {
|
||||
switch ($node->name) {
|
||||
case 'source':
|
||||
if ($allowSource) {
|
||||
$sources[] = $node;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'track':
|
||||
if ($allowTrack) {
|
||||
$tracks[] = $node;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$content[] = $node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$currentNode = $context->get('CurrentNode');
|
||||
$hasSrcAttr = $currentNode instanceof HTMLPurifier_Node_Element && isset($currentNode->attr['src']);
|
||||
|
||||
if ($hasSrcAttr) {
|
||||
$result = array_merge($tracks, $content);
|
||||
} else {
|
||||
$result = array_merge($sources, $tracks, $content);
|
||||
}
|
||||
|
||||
if (empty($result) && !$hasSrcAttr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_ChildDef_Picture extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'picture';
|
||||
|
||||
public $elements = array(
|
||||
'img' => true,
|
||||
'source' => true,
|
||||
);
|
||||
|
||||
/**
|
||||
* @param array $children
|
||||
* @param HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_Context $context
|
||||
* @return array
|
||||
*/
|
||||
public function validateChildren($children, $config, $context)
|
||||
{
|
||||
// if there are no tokens, delete parent node
|
||||
if (empty($children)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($config->getHTMLDefinition()->info['img'])) {
|
||||
trigger_error("Cannot allow picture without allowing img", E_USER_WARNING);
|
||||
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 (($allowSource && $node->name === 'source') || $node->name === 'img') {
|
||||
$result[] = $node;
|
||||
}
|
||||
if ($node->name === 'img') {
|
||||
$hasImg = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasImg || empty($result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_HTML5Config extends HTMLPurifier_Config
|
||||
{
|
||||
const REVISION = 2018060702;
|
||||
|
||||
/**
|
||||
* @param string|array|HTMLPurifier_Config $config
|
||||
* @param HTMLPurifier_ConfigSchema $schema
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
public static function create($config, $schema = null)
|
||||
{
|
||||
if ($config instanceof HTMLPurifier_Config) {
|
||||
$schema = $config->def;
|
||||
$config = null;
|
||||
}
|
||||
|
||||
if (!$schema instanceof HTMLPurifier_ConfigSchema) {
|
||||
$schema = HTMLPurifier_ConfigSchema::instance();
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
if (is_string($config)) {
|
||||
$configObj->loadIni($config);
|
||||
|
||||
} elseif (is_array($config)) {
|
||||
$configObj->loadArray($config);
|
||||
}
|
||||
|
||||
return $configObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a configuration object using the default config schema instance
|
||||
*
|
||||
* @return HTMLPurifier_Config
|
||||
*/
|
||||
public static function createDefault()
|
||||
{
|
||||
$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_Config
|
||||
*/
|
||||
public static function inherit(HTMLPurifier_Config $config)
|
||||
{
|
||||
return new self($config->def, $config->plist);
|
||||
}
|
||||
|
||||
public function getDefinition($type, $raw = false, $optimized = false)
|
||||
{
|
||||
// Setting HTML.* keys removes any previously instantiated HTML
|
||||
// definition object, so set up HTML5 definition as late as possible
|
||||
$needSetup = $type === 'HTML' && !isset($this->definitions[$type]);
|
||||
if ($needSetup) {
|
||||
if ($def = parent::getDefinition($type, true, true)) {
|
||||
HTMLPurifier_HTML5Definition::setup($def);
|
||||
}
|
||||
}
|
||||
return parent::getDefinition($type, $raw, $optimized);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
class HTMLPurifier_HTML5Definition
|
||||
{
|
||||
/**
|
||||
* Adds HTML5 element and attributes to a provided definition object.
|
||||
*
|
||||
* @param HTMLPurifier_HTMLDefinition $def
|
||||
* @return HTMLPurifier_HTMLDefinition
|
||||
*/
|
||||
public static function setup(HTMLPurifier_HTMLDefinition $def)
|
||||
{
|
||||
// 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());
|
||||
|
||||
// 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');
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user