Initial commit.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTypes.php';
|
||||
|
||||
/**
|
||||
* Defines common attribute collections that modules reference
|
||||
*/
|
||||
|
||||
class HTMLPurifier_AttrCollections
|
||||
{
|
||||
|
||||
/**
|
||||
* Associative array of attribute collections, indexed by name
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Performs all expansions on internal data for use by other inclusions
|
||||
* It also collects all attribute collection extensions from
|
||||
* modules
|
||||
* @param $attr_types HTMLPurifier_AttrTypes instance
|
||||
* @param $modules Hash array of HTMLPurifier_HTMLModule members
|
||||
*/
|
||||
public function __construct($attr_types, $modules) {
|
||||
// load extensions from the modules
|
||||
foreach ($modules as $module) {
|
||||
foreach ($module->attr_collections as $coll_i => $coll) {
|
||||
if (!isset($this->info[$coll_i])) {
|
||||
$this->info[$coll_i] = array();
|
||||
}
|
||||
foreach ($coll as $attr_i => $attr) {
|
||||
if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
|
||||
// merge in includes
|
||||
$this->info[$coll_i][$attr_i] = array_merge(
|
||||
$this->info[$coll_i][$attr_i], $attr);
|
||||
continue;
|
||||
}
|
||||
$this->info[$coll_i][$attr_i] = $attr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// perform internal expansions and inclusions
|
||||
foreach ($this->info as $name => $attr) {
|
||||
// merge attribute collections that include others
|
||||
$this->performInclusions($this->info[$name]);
|
||||
// replace string identifiers with actual attribute objects
|
||||
$this->expandIdentifiers($this->info[$name], $attr_types);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a reference to an attribute associative array and performs
|
||||
* all inclusions specified by the zero index.
|
||||
* @param &$attr Reference to attribute array
|
||||
*/
|
||||
public function performInclusions(&$attr) {
|
||||
if (!isset($attr[0])) return;
|
||||
$merge = $attr[0];
|
||||
$seen = array(); // recursion guard
|
||||
// loop through all the inclusions
|
||||
for ($i = 0; isset($merge[$i]); $i++) {
|
||||
if (isset($seen[$merge[$i]])) continue;
|
||||
$seen[$merge[$i]] = true;
|
||||
// foreach attribute of the inclusion, copy it over
|
||||
if (!isset($this->info[$merge[$i]])) continue;
|
||||
foreach ($this->info[$merge[$i]] as $key => $value) {
|
||||
if (isset($attr[$key])) continue; // also catches more inclusions
|
||||
$attr[$key] = $value;
|
||||
}
|
||||
if (isset($this->info[$merge[$i]][0])) {
|
||||
// recursion
|
||||
$merge = array_merge($merge, $this->info[$merge[$i]][0]);
|
||||
}
|
||||
}
|
||||
unset($attr[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands all string identifiers in an attribute array by replacing
|
||||
* them with the appropriate values inside HTMLPurifier_AttrTypes
|
||||
* @param &$attr Reference to attribute array
|
||||
* @param $attr_types HTMLPurifier_AttrTypes instance
|
||||
*/
|
||||
public function expandIdentifiers(&$attr, $attr_types) {
|
||||
|
||||
// because foreach will process new elements we add, make sure we
|
||||
// skip duplicates
|
||||
$processed = array();
|
||||
|
||||
foreach ($attr as $def_i => $def) {
|
||||
// skip inclusions
|
||||
if ($def_i === 0) continue;
|
||||
|
||||
if (isset($processed[$def_i])) continue;
|
||||
|
||||
// determine whether or not attribute is required
|
||||
if ($required = (strpos($def_i, '*') !== false)) {
|
||||
// rename the definition
|
||||
unset($attr[$def_i]);
|
||||
$def_i = trim($def_i, '*');
|
||||
$attr[$def_i] = $def;
|
||||
}
|
||||
|
||||
$processed[$def_i] = true;
|
||||
|
||||
// if we've already got a literal object, move on
|
||||
if (is_object($def)) {
|
||||
// preserve previous required
|
||||
$attr[$def_i]->required = ($required || $attr[$def_i]->required);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($def === false) {
|
||||
unset($attr[$def_i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($t = $attr_types->get($def)) {
|
||||
$attr[$def_i] = $t;
|
||||
$attr[$def_i]->required = $required;
|
||||
} else {
|
||||
unset($attr[$def_i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base class for all validating attribute definitions.
|
||||
*
|
||||
* This family of classes forms the core for not only HTML attribute validation,
|
||||
* but also any sort of string that needs to be validated or cleaned (which
|
||||
* means CSS properties and composite definitions are defined here too).
|
||||
* Besides defining (through code) what precisely makes the string valid,
|
||||
* subclasses are also responsible for cleaning the code if possible.
|
||||
*/
|
||||
|
||||
abstract class HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Tells us whether or not an HTML attribute is minimized. Has no
|
||||
* meaning in other contexts.
|
||||
*/
|
||||
public $minimized = false;
|
||||
|
||||
/**
|
||||
* Tells us whether or not an HTML attribute is required. Has no
|
||||
* meaning in other contexts
|
||||
*/
|
||||
public $required = false;
|
||||
|
||||
/**
|
||||
* Validates and cleans passed string according to a definition.
|
||||
*
|
||||
* @param $string String to be validated and cleaned.
|
||||
* @param $config Mandatory HTMLPurifier_Config object.
|
||||
* @param $context Mandatory HTMLPurifier_AttrContext object.
|
||||
*/
|
||||
abstract public function validate($string, $config, $context);
|
||||
|
||||
/**
|
||||
* Convenience method that parses a string as if it were CDATA.
|
||||
*
|
||||
* This method process a string in the manner specified at
|
||||
* <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
|
||||
* leading and trailing whitespace, ignoring line feeds, and replacing
|
||||
* carriage returns and tabs with spaces. While most useful for HTML
|
||||
* attributes specified as CDATA, it can also be applied to most CSS
|
||||
* values.
|
||||
*
|
||||
* @note This method is not entirely standards compliant, as trim() removes
|
||||
* more types of whitespace than specified in the spec. In practice,
|
||||
* this is rarely a problem, as those extra characters usually have
|
||||
* already been removed by HTMLPurifier_Encoder.
|
||||
*
|
||||
* @warning This processing is inconsistent with XML's whitespace handling
|
||||
* as specified by section 3.3.3 and referenced XHTML 1.0 section
|
||||
* 4.7. Compliant processing requires all line breaks normalized
|
||||
* to "\n", so the fix is not as simple as fixing it in this
|
||||
* function. Trim and whitespace collapsing are supposed to only
|
||||
* occur in NMTOKENs. However, note that we are NOT necessarily
|
||||
* parsing XML, thus, this behavior may still be correct.
|
||||
*/
|
||||
public function parseCDATA($string) {
|
||||
$string = trim($string);
|
||||
$string = str_replace("\n", '', $string);
|
||||
$string = str_replace(array("\r", "\t"), ' ', $string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for creating this class from a string.
|
||||
* @param $string String construction info
|
||||
* @return Created AttrDef object corresponding to $string
|
||||
*/
|
||||
public function make($string) {
|
||||
// default implementation, return flyweight of this object
|
||||
// if overloaded, it is *necessary* for you to clone the
|
||||
// object (usually by instantiating a new copy) and return that
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/CSSDefinition.php';
|
||||
|
||||
/**
|
||||
* Validates the HTML attribute style, otherwise known as CSS.
|
||||
* @note We don't implement the whole CSS specification, so it might be
|
||||
* difficult to reuse this component in the context of validating
|
||||
* actual stylesheet declarations.
|
||||
* @note If we were really serious about validating the CSS, we would
|
||||
* tokenize the styles and then parse the tokens. Obviously, we
|
||||
* are not doing that. Doing that could seriously harm performance,
|
||||
* but would make these components a lot more viable for a CSS
|
||||
* filtering solution.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($css, $config, $context) {
|
||||
|
||||
$css = $this->parseCDATA($css);
|
||||
|
||||
$definition = $config->getCSSDefinition();
|
||||
|
||||
// we're going to break the spec and explode by semicolons.
|
||||
// This is because semicolon rarely appears in escaped form
|
||||
// Doing this is generally flaky but fast
|
||||
// IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
|
||||
// for details
|
||||
|
||||
$declarations = explode(';', $css);
|
||||
$propvalues = array();
|
||||
|
||||
foreach ($declarations as $declaration) {
|
||||
if (!$declaration) continue;
|
||||
if (!strpos($declaration, ':')) continue;
|
||||
list($property, $value) = explode(':', $declaration, 2);
|
||||
$property = trim($property);
|
||||
$value = trim($value);
|
||||
$ok = false;
|
||||
do {
|
||||
if (isset($definition->info[$property])) {
|
||||
$ok = true;
|
||||
break;
|
||||
}
|
||||
if (ctype_lower($property)) break;
|
||||
$property = strtolower($property);
|
||||
if (isset($definition->info[$property])) {
|
||||
$ok = true;
|
||||
break;
|
||||
}
|
||||
} while(0);
|
||||
if (!$ok) continue;
|
||||
// inefficient call, since the validator will do this again
|
||||
if (strtolower(trim($value)) !== 'inherit') {
|
||||
// inherit works for everything (but only on the base property)
|
||||
$result = $definition->info[$property]->validate(
|
||||
$value, $config, $context );
|
||||
} else {
|
||||
$result = 'inherit';
|
||||
}
|
||||
if ($result === false) continue;
|
||||
$propvalues[$property] = $result;
|
||||
}
|
||||
|
||||
// procedure does not write the new CSS simultaneously, so it's
|
||||
// slightly inefficient, but it's the only way of getting rid of
|
||||
// duplicates. Perhaps config to optimize it, but not now.
|
||||
|
||||
$new_declarations = '';
|
||||
foreach ($propvalues as $prop => $value) {
|
||||
$new_declarations .= "$prop:$value;";
|
||||
}
|
||||
|
||||
return $new_declarations ? $new_declarations : false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Number.php';
|
||||
|
||||
class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(false); // opacity is non-negative, but we will clamp it
|
||||
}
|
||||
|
||||
public function validate($number, $config, $context) {
|
||||
$result = parent::validate($number, $config, $context);
|
||||
if ($result === false) return $result;
|
||||
$float = (float) $result;
|
||||
if ($float < 0.0) $result = '0';
|
||||
if ($float > 1.0) $result = '1';
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/CSSDefinition.php';
|
||||
|
||||
/**
|
||||
* Validates shorthand CSS property background.
|
||||
* @warning Does not support url tokens that have internal spaces.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Local copy of component validators.
|
||||
* @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
|
||||
*/
|
||||
protected $info;
|
||||
|
||||
public function __construct($config) {
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['background-color'] = $def->info['background-color'];
|
||||
$this->info['background-image'] = $def->info['background-image'];
|
||||
$this->info['background-repeat'] = $def->info['background-repeat'];
|
||||
$this->info['background-attachment'] = $def->info['background-attachment'];
|
||||
$this->info['background-position'] = $def->info['background-position'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
// assumes URI doesn't have spaces in it
|
||||
$bits = explode(' ', strtolower($string)); // bits to process
|
||||
|
||||
$caught = array();
|
||||
$caught['color'] = false;
|
||||
$caught['image'] = false;
|
||||
$caught['repeat'] = false;
|
||||
$caught['attachment'] = false;
|
||||
$caught['position'] = false;
|
||||
|
||||
$i = 0; // number of catches
|
||||
$none = false;
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($bit === '') continue;
|
||||
foreach ($caught as $key => $status) {
|
||||
if ($key != 'position') {
|
||||
if ($status !== false) continue;
|
||||
$r = $this->info['background-' . $key]->validate($bit, $config, $context);
|
||||
} else {
|
||||
$r = $bit;
|
||||
}
|
||||
if ($r === false) continue;
|
||||
if ($key == 'position') {
|
||||
if ($caught[$key] === false) $caught[$key] = '';
|
||||
$caught[$key] .= $r . ' ';
|
||||
} else {
|
||||
$caught[$key] = $r;
|
||||
}
|
||||
$i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$i) return false;
|
||||
if ($caught['position'] !== false) {
|
||||
$caught['position'] = $this->info['background-position']->
|
||||
validate($caught['position'], $config, $context);
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
foreach ($caught as $value) {
|
||||
if ($value === false) continue;
|
||||
$ret[] = $value;
|
||||
}
|
||||
|
||||
if (empty($ret)) return false;
|
||||
return implode(' ', $ret);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Length.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Percentage.php';
|
||||
|
||||
/* W3C says:
|
||||
[ // adjective and number must be in correct order, even if
|
||||
// you could switch them without introducing ambiguity.
|
||||
// some browsers support that syntax
|
||||
[
|
||||
<percentage> | <length> | left | center | right
|
||||
]
|
||||
[
|
||||
<percentage> | <length> | top | center | bottom
|
||||
]?
|
||||
] |
|
||||
[ // this signifies that the vertical and horizontal adjectives
|
||||
// can be arbitrarily ordered, however, there can only be two,
|
||||
// one of each, or none at all
|
||||
[
|
||||
left | center | right
|
||||
] ||
|
||||
[
|
||||
top | center | bottom
|
||||
]
|
||||
]
|
||||
top, left = 0%
|
||||
center, (none) = 50%
|
||||
bottom, right = 100%
|
||||
*/
|
||||
|
||||
/* QuirksMode says:
|
||||
keyword + length/percentage must be ordered correctly, as per W3C
|
||||
|
||||
Internet Explorer and Opera, however, support arbitrary ordering. We
|
||||
should fix it up.
|
||||
|
||||
Minor issue though, not strictly necessary.
|
||||
*/
|
||||
|
||||
// control freaks may appreciate the ability to convert these to
|
||||
// percentages or something, but it's not necessary
|
||||
|
||||
/**
|
||||
* Validates the value of background-position.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $length;
|
||||
protected $percentage;
|
||||
|
||||
public function __construct() {
|
||||
$this->length = new HTMLPurifier_AttrDef_CSS_Length();
|
||||
$this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$string = $this->parseCDATA($string);
|
||||
$bits = explode(' ', $string);
|
||||
|
||||
$keywords = array();
|
||||
$keywords['h'] = false; // left, right
|
||||
$keywords['v'] = false; // top, bottom
|
||||
$keywords['c'] = false; // center
|
||||
$measures = array();
|
||||
|
||||
$i = 0;
|
||||
|
||||
$lookup = array(
|
||||
'top' => 'v',
|
||||
'bottom' => 'v',
|
||||
'left' => 'h',
|
||||
'right' => 'h',
|
||||
'center' => 'c'
|
||||
);
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($bit === '') continue;
|
||||
|
||||
// test for keyword
|
||||
$lbit = ctype_lower($bit) ? $bit : strtolower($bit);
|
||||
if (isset($lookup[$lbit])) {
|
||||
$status = $lookup[$lbit];
|
||||
$keywords[$status] = $lbit;
|
||||
$i++;
|
||||
}
|
||||
|
||||
// test for length
|
||||
$r = $this->length->validate($bit, $config, $context);
|
||||
if ($r !== false) {
|
||||
$measures[] = $r;
|
||||
$i++;
|
||||
}
|
||||
|
||||
// test for percentage
|
||||
$r = $this->percentage->validate($bit, $config, $context);
|
||||
if ($r !== false) {
|
||||
$measures[] = $r;
|
||||
$i++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!$i) return false; // no valid values were caught
|
||||
|
||||
|
||||
$ret = array();
|
||||
|
||||
// first keyword
|
||||
if ($keywords['h']) $ret[] = $keywords['h'];
|
||||
elseif (count($measures)) $ret[] = array_shift($measures);
|
||||
elseif ($keywords['c']) {
|
||||
$ret[] = $keywords['c'];
|
||||
$keywords['c'] = false; // prevent re-use: center = center center
|
||||
}
|
||||
|
||||
if ($keywords['v']) $ret[] = $keywords['v'];
|
||||
elseif (count($measures)) $ret[] = array_shift($measures);
|
||||
elseif ($keywords['c']) $ret[] = $keywords['c'];
|
||||
|
||||
if (empty($ret)) return false;
|
||||
return implode(' ', $ret);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates the border property as defined by CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Local copy of properties this property is shorthand for.
|
||||
*/
|
||||
protected $info = array();
|
||||
|
||||
public function __construct($config) {
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['border-width'] = $def->info['border-width'];
|
||||
$this->info['border-style'] = $def->info['border-style'];
|
||||
$this->info['border-top-color'] = $def->info['border-top-color'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$string = $this->parseCDATA($string);
|
||||
// we specifically will not support rgb() syntax with spaces
|
||||
$bits = explode(' ', $string);
|
||||
$done = array(); // segments we've finished
|
||||
$ret = ''; // return value
|
||||
foreach ($bits as $bit) {
|
||||
foreach ($this->info as $propname => $validator) {
|
||||
if (isset($done[$propname])) continue;
|
||||
$r = $validator->validate($bit, $config, $context);
|
||||
if ($r !== false) {
|
||||
$ret .= $r . ' ';
|
||||
$done[$propname] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rtrim($ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'ColorKeywords', array(
|
||||
'maroon' => '#800000',
|
||||
'red' => '#FF0000',
|
||||
'orange' => '#FFA500',
|
||||
'yellow' => '#FFFF00',
|
||||
'olive' => '#808000',
|
||||
'purple' => '#800080',
|
||||
'fuchsia' => '#FF00FF',
|
||||
'white' => '#FFFFFF',
|
||||
'lime' => '#00FF00',
|
||||
'green' => '#008000',
|
||||
'navy' => '#000080',
|
||||
'blue' => '#0000FF',
|
||||
'aqua' => '#00FFFF',
|
||||
'teal' => '#008080',
|
||||
'black' => '#000000',
|
||||
'silver' => '#C0C0C0',
|
||||
'gray' => '#808080'
|
||||
), 'hash', '
|
||||
Lookup array of color names to six digit hexadecimal number corresponding
|
||||
to color, with preceding hash mark. Used when parsing colors.
|
||||
This directive has been available since 2.0.0.
|
||||
');
|
||||
|
||||
/**
|
||||
* Validates Color as defined by CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($color, $config, $context) {
|
||||
|
||||
static $colors = null;
|
||||
if ($colors === null) $colors = $config->get('Core', 'ColorKeywords');
|
||||
|
||||
$color = trim($color);
|
||||
if ($color === '') return false;
|
||||
|
||||
$lower = strtolower($color);
|
||||
if (isset($colors[$lower])) return $colors[$lower];
|
||||
|
||||
if (strpos($color, 'rgb(') !== false) {
|
||||
// rgb literal handling
|
||||
$length = strlen($color);
|
||||
if (strpos($color, ')') !== $length - 1) return false;
|
||||
$triad = substr($color, 4, $length - 4 - 1);
|
||||
$parts = explode(',', $triad);
|
||||
if (count($parts) !== 3) return false;
|
||||
$type = false; // to ensure that they're all the same type
|
||||
$new_parts = array();
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
if ($part === '') return false;
|
||||
$length = strlen($part);
|
||||
if ($part[$length - 1] === '%') {
|
||||
// handle percents
|
||||
if (!$type) {
|
||||
$type = 'percentage';
|
||||
} elseif ($type !== 'percentage') {
|
||||
return false;
|
||||
}
|
||||
$num = (float) substr($part, 0, $length - 1);
|
||||
if ($num < 0) $num = 0;
|
||||
if ($num > 100) $num = 100;
|
||||
$new_parts[] = "$num%";
|
||||
} else {
|
||||
// handle integers
|
||||
if (!$type) {
|
||||
$type = 'integer';
|
||||
} elseif ($type !== 'integer') {
|
||||
return false;
|
||||
}
|
||||
$num = (int) $part;
|
||||
if ($num < 0) $num = 0;
|
||||
if ($num > 255) $num = 255;
|
||||
$new_parts[] = (string) $num;
|
||||
}
|
||||
}
|
||||
$new_triad = implode(',', $new_parts);
|
||||
$color = "rgb($new_triad)";
|
||||
} else {
|
||||
// hexadecimal handling
|
||||
if ($color[0] === '#') {
|
||||
$hex = substr($color, 1);
|
||||
} else {
|
||||
$hex = $color;
|
||||
$color = '#' . $color;
|
||||
}
|
||||
$length = strlen($hex);
|
||||
if ($length !== 3 && $length !== 6) return false;
|
||||
if (!ctype_xdigit($hex)) return false;
|
||||
}
|
||||
|
||||
return $color;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Allows multiple validators to attempt to validate attribute.
|
||||
*
|
||||
* Composite is just what it sounds like: a composite of many validators.
|
||||
* This means that multiple HTMLPurifier_AttrDef objects will have a whack
|
||||
* at the string. If one of them passes, that's what is returned. This is
|
||||
* especially useful for CSS values, which often are a choice between
|
||||
* an enumerated set of predefined values or a flexible data type.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* List of HTMLPurifier_AttrDef objects that may process strings
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $defs;
|
||||
|
||||
/**
|
||||
* @param $defs List of HTMLPurifier_AttrDef objects
|
||||
*/
|
||||
public function __construct($defs) {
|
||||
$this->defs = $defs;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
foreach ($this->defs as $i => $def) {
|
||||
$result = $this->defs[$i]->validate($string, $config, $context);
|
||||
if ($result !== false) return $result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/Integer.php';
|
||||
|
||||
/**
|
||||
* Microsoft's proprietary filter: CSS property
|
||||
* @note Currently supports the alpha filter. In the future, this will
|
||||
* probably need an extensible framework
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $intValidator;
|
||||
|
||||
public function __construct() {
|
||||
$this->intValidator = new HTMLPurifier_AttrDef_Integer();
|
||||
}
|
||||
|
||||
public function validate($value, $config, $context) {
|
||||
$value = $this->parseCDATA($value);
|
||||
if ($value === 'none') return $value;
|
||||
// if we looped this we could support multiple filters
|
||||
$function_length = strcspn($value, '(');
|
||||
$function = trim(substr($value, 0, $function_length));
|
||||
if ($function !== 'alpha' &&
|
||||
$function !== 'Alpha' &&
|
||||
$function !== 'progid:DXImageTransform.Microsoft.Alpha'
|
||||
) return false;
|
||||
$cursor = $function_length + 1;
|
||||
$parameters_length = strcspn($value, ')', $cursor);
|
||||
$parameters = substr($value, $cursor, $parameters_length);
|
||||
$params = explode(',', $parameters);
|
||||
$ret_params = array();
|
||||
$lookup = array();
|
||||
foreach ($params as $param) {
|
||||
list($key, $value) = explode('=', $param);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
if (isset($lookup[$key])) continue;
|
||||
if ($key !== 'opacity') continue;
|
||||
$value = $this->intValidator->validate($value, $config, $context);
|
||||
if ($value === false) continue;
|
||||
$int = (int) $value;
|
||||
if ($int > 100) $value = '100';
|
||||
if ($int < 0) $value = '0';
|
||||
$ret_params[] = "$key=$value";
|
||||
$lookup[$key] = true;
|
||||
}
|
||||
$ret_parameters = implode(',', $ret_params);
|
||||
$ret_function = "$function($ret_parameters)";
|
||||
return $ret_function;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates shorthand CSS property font.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Local copy of component validators.
|
||||
*
|
||||
* @note If we moved specific CSS property definitions to their own
|
||||
* classes instead of having them be assembled at run time by
|
||||
* CSSDefinition, this wouldn't be necessary. We'd instantiate
|
||||
* our own copies.
|
||||
*/
|
||||
protected $info = array();
|
||||
|
||||
public function __construct($config) {
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['font-style'] = $def->info['font-style'];
|
||||
$this->info['font-variant'] = $def->info['font-variant'];
|
||||
$this->info['font-weight'] = $def->info['font-weight'];
|
||||
$this->info['font-size'] = $def->info['font-size'];
|
||||
$this->info['line-height'] = $def->info['line-height'];
|
||||
$this->info['font-family'] = $def->info['font-family'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
static $system_fonts = array(
|
||||
'caption' => true,
|
||||
'icon' => true,
|
||||
'menu' => true,
|
||||
'message-box' => true,
|
||||
'small-caption' => true,
|
||||
'status-bar' => true
|
||||
);
|
||||
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
// check if it's one of the keywords
|
||||
$lowercase_string = strtolower($string);
|
||||
if (isset($system_fonts[$lowercase_string])) {
|
||||
return $lowercase_string;
|
||||
}
|
||||
|
||||
$bits = explode(' ', $string); // bits to process
|
||||
$stage = 0; // this indicates what we're looking for
|
||||
$caught = array(); // which stage 0 properties have we caught?
|
||||
$stage_1 = array('font-style', 'font-variant', 'font-weight');
|
||||
$final = ''; // output
|
||||
|
||||
for ($i = 0, $size = count($bits); $i < $size; $i++) {
|
||||
if ($bits[$i] === '') continue;
|
||||
switch ($stage) {
|
||||
|
||||
// attempting to catch font-style, font-variant or font-weight
|
||||
case 0:
|
||||
foreach ($stage_1 as $validator_name) {
|
||||
if (isset($caught[$validator_name])) continue;
|
||||
$r = $this->info[$validator_name]->validate(
|
||||
$bits[$i], $config, $context);
|
||||
if ($r !== false) {
|
||||
$final .= $r . ' ';
|
||||
$caught[$validator_name] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// all three caught, continue on
|
||||
if (count($caught) >= 3) $stage = 1;
|
||||
if ($r !== false) break;
|
||||
|
||||
// attempting to catch font-size and perhaps line-height
|
||||
case 1:
|
||||
$found_slash = false;
|
||||
if (strpos($bits[$i], '/') !== false) {
|
||||
list($font_size, $line_height) =
|
||||
explode('/', $bits[$i]);
|
||||
if ($line_height === '') {
|
||||
// ooh, there's a space after the slash!
|
||||
$line_height = false;
|
||||
$found_slash = true;
|
||||
}
|
||||
} else {
|
||||
$font_size = $bits[$i];
|
||||
$line_height = false;
|
||||
}
|
||||
$r = $this->info['font-size']->validate(
|
||||
$font_size, $config, $context);
|
||||
if ($r !== false) {
|
||||
$final .= $r;
|
||||
// attempt to catch line-height
|
||||
if ($line_height === false) {
|
||||
// we need to scroll forward
|
||||
for ($j = $i + 1; $j < $size; $j++) {
|
||||
if ($bits[$j] === '') continue;
|
||||
if ($bits[$j] === '/') {
|
||||
if ($found_slash) {
|
||||
return false;
|
||||
} else {
|
||||
$found_slash = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$line_height = $bits[$j];
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// slash already found
|
||||
$found_slash = true;
|
||||
$j = $i;
|
||||
}
|
||||
if ($found_slash) {
|
||||
$i = $j;
|
||||
$r = $this->info['line-height']->validate(
|
||||
$line_height, $config, $context);
|
||||
if ($r !== false) {
|
||||
$final .= '/' . $r;
|
||||
}
|
||||
}
|
||||
$final .= ' ';
|
||||
$stage = 2;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
|
||||
// attempting to catch font-family
|
||||
case 2:
|
||||
$font_family =
|
||||
implode(' ', array_slice($bits, $i, $size - $i));
|
||||
$r = $this->info['font-family']->validate(
|
||||
$font_family, $config, $context);
|
||||
if ($r !== false) {
|
||||
$final .= $r . ' ';
|
||||
// processing completed successfully
|
||||
return rtrim($final);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
// whitelisting allowed fonts would be nice
|
||||
|
||||
/**
|
||||
* Validates a font family list according to CSS spec
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
static $generic_names = array(
|
||||
'serif' => true,
|
||||
'sans-serif' => true,
|
||||
'monospace' => true,
|
||||
'fantasy' => true,
|
||||
'cursive' => true
|
||||
);
|
||||
|
||||
$string = $this->parseCDATA($string);
|
||||
// assume that no font names contain commas in them
|
||||
$fonts = explode(',', $string);
|
||||
$final = '';
|
||||
foreach($fonts as $font) {
|
||||
$font = trim($font);
|
||||
if ($font === '') continue;
|
||||
// match a generic name
|
||||
if (isset($generic_names[$font])) {
|
||||
$final .= $font . ', ';
|
||||
continue;
|
||||
}
|
||||
// match a quoted name
|
||||
if ($font[0] === '"' || $font[0] === "'") {
|
||||
$length = strlen($font);
|
||||
if ($length <= 2) continue;
|
||||
$quote = $font[0];
|
||||
if ($font[$length - 1] !== $quote) continue;
|
||||
$font = substr($font, 1, $length - 2);
|
||||
// double-backslash processing is buggy
|
||||
$font = str_replace("\\$quote", $quote, $font); // de-escape quote
|
||||
$font = str_replace("\\\n", "\n", $font); // de-escape newlines
|
||||
}
|
||||
// $font is a pure representation of the font name
|
||||
|
||||
if (ctype_alnum($font)) {
|
||||
// very simple font, allow it in unharmed
|
||||
$final .= $font . ', ';
|
||||
continue;
|
||||
}
|
||||
|
||||
// complicated font, requires quoting
|
||||
|
||||
// armor single quotes and new lines
|
||||
$font = str_replace("'", "\\'", $font);
|
||||
$font = str_replace("\n", "\\\n", $font);
|
||||
$final .= "'$font', ";
|
||||
}
|
||||
$final = rtrim($final, ', ');
|
||||
if ($final === '') return false;
|
||||
return $final;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Number.php';
|
||||
|
||||
/**
|
||||
* Represents a Length as defined by CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Valid unit lookup table.
|
||||
* @warning The code assumes all units are two characters long. Be careful
|
||||
* if we have to change this behavior!
|
||||
*/
|
||||
protected $units = array('em' => true, 'ex' => true, 'px' => true, 'in' => true,
|
||||
'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true);
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_Number to defer number validation to
|
||||
*/
|
||||
protected $number_def;
|
||||
|
||||
/**
|
||||
* @param $non_negative Bool indication whether or not negative values are
|
||||
* allowed.
|
||||
*/
|
||||
public function __construct($non_negative = false) {
|
||||
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
|
||||
}
|
||||
|
||||
public function validate($length, $config, $context) {
|
||||
|
||||
$length = $this->parseCDATA($length);
|
||||
if ($length === '') return false;
|
||||
if ($length === '0') return '0';
|
||||
$strlen = strlen($length);
|
||||
if ($strlen === 1) return false; // impossible!
|
||||
|
||||
// we assume all units are two characters
|
||||
$unit = substr($length, $strlen - 2);
|
||||
if (!ctype_lower($unit)) $unit = strtolower($unit);
|
||||
$number = substr($length, 0, $strlen - 2);
|
||||
|
||||
if (!isset($this->units[$unit])) return false;
|
||||
|
||||
$number = $this->number_def->validate($number, $config, $context);
|
||||
if ($number === false) return false;
|
||||
|
||||
return $number . $unit;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates shorthand CSS property list-style.
|
||||
* @warning Does not support url tokens that have internal spaces.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Local copy of component validators.
|
||||
* @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
|
||||
*/
|
||||
protected $info;
|
||||
|
||||
public function __construct($config) {
|
||||
$def = $config->getCSSDefinition();
|
||||
$this->info['list-style-type'] = $def->info['list-style-type'];
|
||||
$this->info['list-style-position'] = $def->info['list-style-position'];
|
||||
$this->info['list-style-image'] = $def->info['list-style-image'];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
// regular pre-processing
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
// assumes URI doesn't have spaces in it
|
||||
$bits = explode(' ', strtolower($string)); // bits to process
|
||||
|
||||
$caught = array();
|
||||
$caught['type'] = false;
|
||||
$caught['position'] = false;
|
||||
$caught['image'] = false;
|
||||
|
||||
$i = 0; // number of catches
|
||||
$none = false;
|
||||
|
||||
foreach ($bits as $bit) {
|
||||
if ($i >= 3) return; // optimization bit
|
||||
if ($bit === '') continue;
|
||||
foreach ($caught as $key => $status) {
|
||||
if ($status !== false) continue;
|
||||
$r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
|
||||
if ($r === false) continue;
|
||||
if ($r === 'none') {
|
||||
if ($none) continue;
|
||||
else $none = true;
|
||||
if ($key == 'image') continue;
|
||||
}
|
||||
$caught[$key] = $r;
|
||||
$i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$i) return false;
|
||||
|
||||
$ret = array();
|
||||
|
||||
// construct type
|
||||
if ($caught['type']) $ret[] = $caught['type'];
|
||||
|
||||
// construct image
|
||||
if ($caught['image']) $ret[] = $caught['image'];
|
||||
|
||||
// construct position
|
||||
if ($caught['position']) $ret[] = $caught['position'];
|
||||
|
||||
if (empty($ret)) return false;
|
||||
return implode(' ', $ret);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Framework class for strings that involve multiple values.
|
||||
*
|
||||
* Certain CSS properties such as border-width and margin allow multiple
|
||||
* lengths to be specified. This class can take a vanilla border-width
|
||||
* definition and multiply it, usually into a max of four.
|
||||
*
|
||||
* @note Even though the CSS specification isn't clear about it, inherit
|
||||
* can only be used alone: it will never manifest as part of a multi
|
||||
* shorthand declaration. Thus, this class does not allow inherit.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of component definition to defer validation to.
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $single;
|
||||
|
||||
/**
|
||||
* Max number of values allowed.
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $max;
|
||||
|
||||
/**
|
||||
* @param $single HTMLPurifier_AttrDef to multiply
|
||||
* @param $max Max number of values allowed (usually four)
|
||||
*/
|
||||
public function __construct($single, $max = 4) {
|
||||
$this->single = $single;
|
||||
$this->max = $max;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$string = $this->parseCDATA($string);
|
||||
if ($string === '') return false;
|
||||
$parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
|
||||
$length = count($parts);
|
||||
$final = '';
|
||||
for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
|
||||
if (ctype_space($parts[$i])) continue;
|
||||
$result = $this->single->validate($parts[$i], $config, $context);
|
||||
if ($result !== false) {
|
||||
$final .= $result . ' ';
|
||||
$num++;
|
||||
}
|
||||
}
|
||||
if ($final === '') return false;
|
||||
return rtrim($final);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates a number as defined by the CSS spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not only positive values allowed.
|
||||
*/
|
||||
protected $non_negative = false;
|
||||
|
||||
/**
|
||||
* @param $non_negative Bool indicating whether negatives are forbidden
|
||||
*/
|
||||
public function __construct($non_negative = false) {
|
||||
$this->non_negative = $non_negative;
|
||||
}
|
||||
|
||||
public function validate($number, $config, $context) {
|
||||
|
||||
$number = $this->parseCDATA($number);
|
||||
|
||||
if ($number === '') return false;
|
||||
if ($number === '0') return '0';
|
||||
|
||||
$sign = '';
|
||||
switch ($number[0]) {
|
||||
case '-':
|
||||
if ($this->non_negative) return false;
|
||||
$sign = '-';
|
||||
case '+':
|
||||
$number = substr($number, 1);
|
||||
}
|
||||
|
||||
if (ctype_digit($number)) {
|
||||
$number = ltrim($number, '0');
|
||||
return $number ? $sign . $number : '0';
|
||||
}
|
||||
|
||||
// Period is the only non-numeric character allowed
|
||||
if (strpos($number, '.') === false) return false;
|
||||
|
||||
list($left, $right) = explode('.', $number, 2);
|
||||
|
||||
if ($left === '' && $right === '') return false;
|
||||
if ($left !== '' && !ctype_digit($left)) return false;
|
||||
|
||||
$left = ltrim($left, '0');
|
||||
$right = rtrim($right, '0');
|
||||
|
||||
if ($right === '') {
|
||||
return $left ? $sign . $left : '0';
|
||||
} elseif (!ctype_digit($right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $sign . $left . '.' . $right;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Number.php';
|
||||
|
||||
/**
|
||||
* Validates a Percentage as defined by the CSS spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_CSS_Number to defer number validation
|
||||
*/
|
||||
protected $number_def;
|
||||
|
||||
/**
|
||||
* @param Bool indicating whether to forbid negative values
|
||||
*/
|
||||
public function __construct($non_negative = false) {
|
||||
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = $this->parseCDATA($string);
|
||||
|
||||
if ($string === '') return false;
|
||||
$length = strlen($string);
|
||||
if ($length === 1) return false;
|
||||
if ($string[$length - 1] !== '%') return false;
|
||||
|
||||
$number = substr($string, 0, $length - 1);
|
||||
$number = $this->number_def->validate($number, $config, $context);
|
||||
|
||||
if ($number === false) return false;
|
||||
return "$number%";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates the value for the CSS property text-decoration
|
||||
* @note This class could be generalized into a version that acts sort of
|
||||
* like Enum except you can compound the allowed values.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
static $allowed_values = array(
|
||||
'line-through' => true,
|
||||
'overline' => true,
|
||||
'underline' => true
|
||||
);
|
||||
|
||||
$string = strtolower($this->parseCDATA($string));
|
||||
$parts = explode(' ', $string);
|
||||
$final = '';
|
||||
foreach ($parts as $part) {
|
||||
if (isset($allowed_values[$part])) {
|
||||
$final .= $part . ' ';
|
||||
}
|
||||
}
|
||||
$final = rtrim($final);
|
||||
if ($final === '') return false;
|
||||
return $final;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/URI.php';
|
||||
|
||||
/**
|
||||
* Validates a URI in CSS syntax, which uses url('http://example.com')
|
||||
* @note While theoretically speaking a URI in a CSS document could
|
||||
* be non-embedded, as of CSS2 there is no such usage so we're
|
||||
* generalizing it. This may need to be changed in the future.
|
||||
* @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
|
||||
* the separator, you cannot put a literal semicolon in
|
||||
* in the URI. Try percent encoding it, in that case.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(true); // always embedded
|
||||
}
|
||||
|
||||
public function validate($uri_string, $config, $context) {
|
||||
// parse the URI out of the string and then pass it onto
|
||||
// the parent object
|
||||
|
||||
$uri_string = $this->parseCDATA($uri_string);
|
||||
if (strpos($uri_string, 'url(') !== 0) return false;
|
||||
$uri_string = substr($uri_string, 4);
|
||||
$new_length = strlen($uri_string) - 1;
|
||||
if ($uri_string[$new_length] != ')') return false;
|
||||
$uri = trim(substr($uri_string, 0, $new_length));
|
||||
|
||||
if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
|
||||
$quote = $uri[0];
|
||||
$new_length = strlen($uri) - 1;
|
||||
if ($uri[$new_length] !== $quote) return false;
|
||||
$uri = substr($uri, 1, $new_length - 1);
|
||||
}
|
||||
|
||||
$keys = array( '(', ')', ',', ' ', '"', "'");
|
||||
$values = array('\\(', '\\)', '\\,', '\\ ', '\\"', "\\'");
|
||||
$uri = str_replace($values, $keys, $uri);
|
||||
|
||||
$result = parent::validate($uri, $config, $context);
|
||||
|
||||
if ($result === false) return false;
|
||||
|
||||
// escape necessary characters according to CSS spec
|
||||
// except for the comma, none of these should appear in the
|
||||
// URI at all
|
||||
$result = str_replace($keys, $values, $result);
|
||||
|
||||
return "url($result)";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
// Enum = Enumerated
|
||||
/**
|
||||
* Validates a keyword against a list of valid values.
|
||||
* @warning The case-insensitive compare of this function uses PHP's
|
||||
* built-in strtolower and ctype_lower functions, which may
|
||||
* cause problems with international comparisons
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Lookup table of valid values.
|
||||
* @todo Make protected
|
||||
*/
|
||||
public $valid_values = array();
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not enumeration is case sensitive.
|
||||
* @note In general this is always case insensitive.
|
||||
*/
|
||||
protected $case_sensitive = false; // values according to W3C spec
|
||||
|
||||
/**
|
||||
* @param $valid_values List of valid values
|
||||
* @param $case_sensitive Bool indicating whether or not case sensitive
|
||||
*/
|
||||
public function __construct(
|
||||
$valid_values = array(), $case_sensitive = false
|
||||
) {
|
||||
$this->valid_values = array_flip($valid_values);
|
||||
$this->case_sensitive = $case_sensitive;
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$string = trim($string);
|
||||
if (!$this->case_sensitive) {
|
||||
// we may want to do full case-insensitive libraries
|
||||
$string = ctype_lower($string) ? $string : strtolower($string);
|
||||
}
|
||||
$result = isset($this->valid_values[$string]);
|
||||
|
||||
return $result ? $string : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string In form of comma-delimited list of case-insensitive
|
||||
* valid values. Example: "foo,bar,baz". Prepend "s:" to make
|
||||
* case sensitive
|
||||
*/
|
||||
public function make($string) {
|
||||
if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
|
||||
$string = substr($string, 2);
|
||||
$sensitive = true;
|
||||
} else {
|
||||
$sensitive = false;
|
||||
}
|
||||
$values = explode(',', $string);
|
||||
return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates a boolean attribute
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $name;
|
||||
public $minimized = true;
|
||||
|
||||
public function __construct($name = false) {$this->name = $name;}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
if (empty($string)) return false;
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string Name of attribute
|
||||
*/
|
||||
public function make($string) {
|
||||
return new HTMLPurifier_AttrDef_HTML_Bool($string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Color.php'; // for %Core.ColorKeywords
|
||||
|
||||
/**
|
||||
* Validates a color according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
static $colors = null;
|
||||
if ($colors === null) $colors = $config->get('Core', 'ColorKeywords');
|
||||
|
||||
$string = trim($string);
|
||||
|
||||
if (empty($string)) return false;
|
||||
if (isset($colors[$string])) return $colors[$string];
|
||||
if ($string[0] === '#') $hex = substr($string, 1);
|
||||
else $hex = $string;
|
||||
|
||||
$length = strlen($hex);
|
||||
if ($length !== 3 && $length !== 6) return false;
|
||||
if (!ctype_xdigit($hex)) return false;
|
||||
if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
|
||||
|
||||
return "#$hex";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'AllowedFrameTargets', array(), 'lookup',
|
||||
'Lookup table of all allowed link frame targets. Some commonly used '.
|
||||
'link targets include _blank, _self, _parent and _top. Values should '.
|
||||
'be lowercase, as validation will be done in a case-sensitive manner '.
|
||||
'despite W3C\'s recommendation. XHTML 1.0 Strict does not permit '.
|
||||
'the target attribute so this directive will have no effect in that '.
|
||||
'doctype. XHTML 1.1 does not enable the Target module by default, you '.
|
||||
'will have to manually enable it (see the module documentation for more details.)'
|
||||
);
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/Enum.php';
|
||||
|
||||
/**
|
||||
* Special-case enum attribute definition that lazy loads allowed frame targets
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
|
||||
{
|
||||
|
||||
public $valid_values = false; // uninitialized value
|
||||
protected $case_sensitive = false;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
if ($this->valid_values === false) $this->valid_values = $config->get('Attr', 'AllowedFrameTargets');
|
||||
return parent::validate($string, $config, $context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/IDAccumulator.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'EnableID', false, 'bool',
|
||||
'Allows the ID attribute in HTML. This is disabled by default '.
|
||||
'due to the fact that without proper configuration user input can '.
|
||||
'easily break the validation of a webpage by specifying an ID that is '.
|
||||
'already on the surrounding HTML. If you don\'t mind throwing caution to '.
|
||||
'the wind, enable this directive, but I strongly recommend you also '.
|
||||
'consider blacklisting IDs you use (%Attr.IDBlacklist) or prefixing all '.
|
||||
'user supplied IDs (%Attr.IDPrefix). This directive has been available '.
|
||||
'since 1.2.0, and when set to true reverts to the behavior of pre-1.2.0 '.
|
||||
'versions.'
|
||||
);
|
||||
HTMLPurifier_ConfigSchema::defineAlias(
|
||||
'HTML', 'EnableAttrID', 'Attr', 'EnableID'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'IDPrefix', '', 'string',
|
||||
'String to prefix to IDs. If you have no idea what IDs your pages '.
|
||||
'may use, you may opt to simply add a prefix to all user-submitted ID '.
|
||||
'attributes so that they are still usable, but will not conflict with '.
|
||||
'core page IDs. Example: setting the directive to \'user_\' will result in '.
|
||||
'a user submitted \'foo\' to become \'user_foo\' Be sure to set '.
|
||||
'%HTML.EnableAttrID to true before using '.
|
||||
'this. This directive was available since 1.2.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'IDPrefixLocal', '', 'string',
|
||||
'Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If '.
|
||||
'you need to allow multiple sets of '.
|
||||
'user content on web page, you may need to have a seperate prefix that '.
|
||||
'changes with each iteration. This way, seperately submitted user content '.
|
||||
'displayed on the same page doesn\'t clobber each other. Ideal values '.
|
||||
'are unique identifiers for the content it represents (i.e. the id of '.
|
||||
'the row in the database). Be sure to add a seperator (like an underscore) '.
|
||||
'at the end. Warning: this directive will not work unless %Attr.IDPrefix '.
|
||||
'is set to a non-empty value! This directive was available since 1.2.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'IDBlacklistRegexp', null, 'string/null',
|
||||
'PCRE regular expression to be matched against all IDs. If the expression '.
|
||||
'is matches, the ID is rejected. Use this with care: may cause '.
|
||||
'significant degradation. ID matching is done after all other '.
|
||||
'validation. This directive was available since 1.6.0.'
|
||||
);
|
||||
|
||||
/**
|
||||
* Validates the HTML attribute ID.
|
||||
* @warning Even though this is the id processor, it
|
||||
* will ignore the directive Attr:IDBlacklist, since it will only
|
||||
* go according to the ID accumulator. Since the accumulator is
|
||||
* automatically generated, it will have already absorbed the
|
||||
* blacklist. If you're hacking around, make sure you use load()!
|
||||
*/
|
||||
|
||||
class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
// ref functionality disabled, since we also have to verify
|
||||
// whether or not the ID it refers to exists
|
||||
|
||||
public function validate($id, $config, $context) {
|
||||
|
||||
if (!$config->get('Attr', 'EnableID')) return false;
|
||||
|
||||
$id = trim($id); // trim it first
|
||||
|
||||
if ($id === '') return false;
|
||||
|
||||
$prefix = $config->get('Attr', 'IDPrefix');
|
||||
if ($prefix !== '') {
|
||||
$prefix .= $config->get('Attr', 'IDPrefixLocal');
|
||||
// prevent re-appending the prefix
|
||||
if (strpos($id, $prefix) !== 0) $id = $prefix . $id;
|
||||
} elseif ($config->get('Attr', 'IDPrefixLocal') !== '') {
|
||||
trigger_error('%Attr.IDPrefixLocal cannot be used unless '.
|
||||
'%Attr.IDPrefix is set', E_USER_WARNING);
|
||||
}
|
||||
|
||||
//if (!$this->ref) {
|
||||
$id_accumulator =& $context->get('IDAccumulator');
|
||||
if (isset($id_accumulator->ids[$id])) return false;
|
||||
//}
|
||||
|
||||
// we purposely avoid using regex, hopefully this is faster
|
||||
|
||||
if (ctype_alpha($id)) {
|
||||
$result = true;
|
||||
} else {
|
||||
if (!ctype_alpha(@$id[0])) return false;
|
||||
$trim = trim( // primitive style of regexps, I suppose
|
||||
$id,
|
||||
'A..Za..z0..9:-._'
|
||||
);
|
||||
$result = ($trim === '');
|
||||
}
|
||||
|
||||
$regexp = $config->get('Attr', 'IDBlacklistRegexp');
|
||||
if ($regexp && preg_match($regexp, $id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/*!$this->ref && */$result) $id_accumulator->add($id);
|
||||
|
||||
// if no change was made to the ID, return the result
|
||||
// else, return the new id if stripping whitespace made it
|
||||
// valid, or return false.
|
||||
return $result ? $id : false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Pixels.php';
|
||||
|
||||
/**
|
||||
* Validates the HTML type length (not to be confused with CSS's length).
|
||||
*
|
||||
* This accepts integer pixels or percentages as lengths for certain
|
||||
* HTML attributes.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
$parent_result = parent::validate($string, $config, $context);
|
||||
if ($parent_result !== false) return $parent_result;
|
||||
|
||||
$length = strlen($string);
|
||||
$last_char = $string[$length - 1];
|
||||
|
||||
if ($last_char !== '%') return false;
|
||||
|
||||
$points = substr($string, 0, $length - 1);
|
||||
|
||||
if (!is_numeric($points)) return false;
|
||||
|
||||
$points = (int) $points;
|
||||
|
||||
if ($points < 0) return '0%';
|
||||
if ($points > 100) return '100%';
|
||||
|
||||
return ((string) $points) . '%';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'AllowedRel', array(), 'lookup',
|
||||
'List of allowed forward document relationships in the rel attribute. '.
|
||||
'Common values may be nofollow or print. By default, this is empty, '.
|
||||
'meaning that no document relationships are allowed. This directive '.
|
||||
'was available since 1.6.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'AllowedRev', array(), 'lookup',
|
||||
'List of allowed reverse document relationships in the rev attribute. '.
|
||||
'This attribute is a bit of an edge-case; if you don\'t know what it '.
|
||||
'is for, stay away. This directive was available since 1.6.0.'
|
||||
);
|
||||
|
||||
/**
|
||||
* Validates a rel/rev link attribute against a directive of allowed values
|
||||
* @note We cannot use Enum because link types allow multiple
|
||||
* values.
|
||||
* @note Assumes link types are ASCII text
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/** Name config attribute to pull. */
|
||||
protected $name;
|
||||
|
||||
public function __construct($name) {
|
||||
$configLookup = array(
|
||||
'rel' => 'AllowedRel',
|
||||
'rev' => 'AllowedRev'
|
||||
);
|
||||
if (!isset($configLookup[$name])) {
|
||||
trigger_error('Unrecognized attribute name for link '.
|
||||
'relationship.', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$this->name = $configLookup[$name];
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$allowed = $config->get('Attr', $this->name);
|
||||
if (empty($allowed)) return false;
|
||||
|
||||
$string = $this->parseCDATA($string);
|
||||
$parts = explode(' ', $string);
|
||||
|
||||
// lookup to prevent duplicates
|
||||
$ret_lookup = array();
|
||||
foreach ($parts as $part) {
|
||||
$part = strtolower(trim($part));
|
||||
if (!isset($allowed[$part])) continue;
|
||||
$ret_lookup[$part] = true;
|
||||
}
|
||||
|
||||
if (empty($ret_lookup)) return false;
|
||||
|
||||
$ret_array = array();
|
||||
foreach ($ret_lookup as $part => $bool) $ret_array[] = $part;
|
||||
$string = implode(' ', $ret_array);
|
||||
|
||||
return $string;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Length.php';
|
||||
|
||||
/**
|
||||
* Validates a MultiLength as defined by the HTML spec.
|
||||
*
|
||||
* A multilength is either a integer (pixel count), a percentage, or
|
||||
* a relative number.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if ($string === '') return false;
|
||||
|
||||
$parent_result = parent::validate($string, $config, $context);
|
||||
if ($parent_result !== false) return $parent_result;
|
||||
|
||||
$length = strlen($string);
|
||||
$last_char = $string[$length - 1];
|
||||
|
||||
if ($last_char !== '*') return false;
|
||||
|
||||
$int = substr($string, 0, $length - 1);
|
||||
|
||||
if ($int == '') return '*';
|
||||
if (!is_numeric($int)) return false;
|
||||
|
||||
$int = (int) $int;
|
||||
|
||||
if ($int < 0) return false;
|
||||
if ($int == 0) return '0';
|
||||
if ($int == 1) return '*';
|
||||
return ((string) $int) . '*';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/Config.php';
|
||||
|
||||
/**
|
||||
* Validates contents based on NMTOKENS attribute type.
|
||||
* @note The only current use for this is the class attribute in HTML
|
||||
* @note Could have some functionality factored out into Nmtoken class
|
||||
* @warning We cannot assume this class will be used only for 'class'
|
||||
* attributes. Not sure how to hook in magic behavior, then.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
|
||||
// early abort: '' and '0' (strings that convert to false) are invalid
|
||||
if (!$string) return false;
|
||||
|
||||
// OPTIMIZABLE!
|
||||
// do the preg_match, capture all subpatterns for reformulation
|
||||
|
||||
// we don't support U+00A1 and up codepoints or
|
||||
// escaping because I don't know how to do that with regexps
|
||||
// and plus it would complicate optimization efforts (you never
|
||||
// see that anyway).
|
||||
$matches = array();
|
||||
$pattern = '/(?:(?<=\s)|\A)'. // look behind for space or string start
|
||||
'((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)'.
|
||||
'(?:(?=\s)|\z)/'; // look ahead for space or string end
|
||||
preg_match_all($pattern, $string, $matches);
|
||||
|
||||
if (empty($matches[1])) return false;
|
||||
|
||||
// reconstruct string
|
||||
$new_string = '';
|
||||
foreach ($matches[1] as $token) {
|
||||
$new_string .= $token . ' ';
|
||||
}
|
||||
$new_string = rtrim($new_string);
|
||||
|
||||
return $new_string;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates an integer representation of pixels according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if ($string === '0') return $string;
|
||||
if ($string === '') return false;
|
||||
$length = strlen($string);
|
||||
if (substr($string, $length - 2) == 'px') {
|
||||
$string = substr($string, 0, $length - 2);
|
||||
}
|
||||
if (!is_numeric($string)) return false;
|
||||
$int = (int) $string;
|
||||
|
||||
if ($int < 0) return '0';
|
||||
|
||||
// upper-bound value, extremely high values can
|
||||
// crash operating systems, see <http://ha.ckers.org/imagecrash.html>
|
||||
// WARNING, above link WILL crash you if you're using Windows
|
||||
|
||||
if ($int > 1200) return '1200';
|
||||
|
||||
return (string) $int;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates an integer.
|
||||
* @note While this class was modeled off the CSS definition, no currently
|
||||
* allowed CSS uses this type. The properties that do are: widows,
|
||||
* orphans, z-index, counter-increment, counter-reset. Some of the
|
||||
* HTML attributes, however, find use for a non-negative version of this.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not negative values are allowed
|
||||
*/
|
||||
protected $negative = true;
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not zero is allowed
|
||||
*/
|
||||
protected $zero = true;
|
||||
|
||||
/**
|
||||
* Bool indicating whether or not positive values are allowed
|
||||
*/
|
||||
protected $positive = true;
|
||||
|
||||
/**
|
||||
* @param $negative Bool indicating whether or not negative values are allowed
|
||||
* @param $zero Bool indicating whether or not zero is allowed
|
||||
* @param $positive Bool indicating whether or not positive values are allowed
|
||||
*/
|
||||
public function __construct(
|
||||
$negative = true, $zero = true, $positive = true
|
||||
) {
|
||||
$this->negative = $negative;
|
||||
$this->zero = $zero;
|
||||
$this->positive = $positive;
|
||||
}
|
||||
|
||||
public function validate($integer, $config, $context) {
|
||||
|
||||
$integer = $this->parseCDATA($integer);
|
||||
if ($integer === '') return false;
|
||||
|
||||
// we could possibly simply typecast it to integer, but there are
|
||||
// certain fringe cases that must not return an integer.
|
||||
|
||||
// clip leading sign
|
||||
if ( $this->negative && $integer[0] === '-' ) {
|
||||
$digits = substr($integer, 1);
|
||||
if ($digits === '0') $integer = '0'; // rm minus sign for zero
|
||||
} elseif( $this->positive && $integer[0] === '+' ) {
|
||||
$digits = $integer = substr($integer, 1); // rm unnecessary plus
|
||||
} else {
|
||||
$digits = $integer;
|
||||
}
|
||||
|
||||
// test if it's numeric
|
||||
if (!ctype_digit($digits)) return false;
|
||||
|
||||
// perform scope tests
|
||||
if (!$this->zero && $integer == 0) return false;
|
||||
if (!$this->positive && $integer > 0) return false;
|
||||
if (!$this->negative && $integer < 0) return false;
|
||||
|
||||
return $integer;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates the HTML attribute lang, effectively a language code.
|
||||
* @note Built according to RFC 3066, which obsoleted RFC 1766
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
|
||||
$string = trim($string);
|
||||
if (!$string) return false;
|
||||
|
||||
$subtags = explode('-', $string);
|
||||
$num_subtags = count($subtags);
|
||||
|
||||
if ($num_subtags == 0) return false; // sanity check
|
||||
|
||||
// process primary subtag : $subtags[0]
|
||||
$length = strlen($subtags[0]);
|
||||
switch ($length) {
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
if (! ($subtags[0] == 'x' || $subtags[0] == 'i') ) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
if (! ctype_alpha($subtags[0]) ) {
|
||||
return false;
|
||||
} elseif (! ctype_lower($subtags[0]) ) {
|
||||
$subtags[0] = strtolower($subtags[0]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
$new_string = $subtags[0];
|
||||
if ($num_subtags == 1) return $new_string;
|
||||
|
||||
// process second subtag : $subtags[1]
|
||||
$length = strlen($subtags[1]);
|
||||
if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
|
||||
return $new_string;
|
||||
}
|
||||
if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]);
|
||||
|
||||
$new_string .= '-' . $subtags[1];
|
||||
if ($num_subtags == 2) return $new_string;
|
||||
|
||||
// process all other subtags, index 2 and up
|
||||
for ($i = 2; $i < $num_subtags; $i++) {
|
||||
$length = strlen($subtags[$i]);
|
||||
if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
|
||||
return $new_string;
|
||||
}
|
||||
if (!ctype_lower($subtags[$i])) {
|
||||
$subtags[$i] = strtolower($subtags[$i]);
|
||||
}
|
||||
$new_string .= '-' . $subtags[$i];
|
||||
}
|
||||
|
||||
return $new_string;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates arbitrary text according to the HTML spec.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
return $this->parseCDATA($string);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/URIParser.php';
|
||||
require_once 'HTMLPurifier/URIScheme.php';
|
||||
require_once 'HTMLPurifier/URISchemeRegistry.php';
|
||||
require_once 'HTMLPurifier/AttrDef/URI/Host.php';
|
||||
require_once 'HTMLPurifier/PercentEncoder.php';
|
||||
require_once 'HTMLPurifier/AttrDef/URI/Email.php';
|
||||
|
||||
// special case filtering directives
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Munge', null, 'string/null', '
|
||||
<p>
|
||||
Munges all browsable (usually http, https and ftp)
|
||||
absolute URI\'s into another URI, usually a URI redirection service.
|
||||
This directive accepts a URI, formatted with a <code>%s</code> where
|
||||
the url-encoded original URI should be inserted (sample:
|
||||
<code>http://www.google.com/url?q=%s</code>).
|
||||
</p>
|
||||
<p>
|
||||
Uses for this directive:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Prevent PageRank leaks, while being fairly transparent
|
||||
to users (you may also want to add some client side JavaScript to
|
||||
override the text in the statusbar). <strong>Notice</strong>:
|
||||
Many security experts believe that this form of protection does not deter spam-bots.
|
||||
</li>
|
||||
<li>
|
||||
Redirect users to a splash page telling them they are leaving your
|
||||
website. While this is poor usability practice, it is often mandated
|
||||
in corporate environments.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
// disabling directives
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'Disable', false, 'bool', '
|
||||
<p>
|
||||
Disables all URIs in all forms. Not sure why you\'d want to do that
|
||||
(after all, the Internet\'s founded on the notion of a hyperlink).
|
||||
This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
HTMLPurifier_ConfigSchema::defineAlias('Attr', 'DisableURI', 'URI', 'Disable');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'URI', 'DisableResources', false, 'bool', '
|
||||
<p>
|
||||
Disables embedding resources, essentially meaning no pictures. You can
|
||||
still link to them though. See %URI.DisableExternalResources for why
|
||||
this might be a good idea. This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
/**
|
||||
* Validates a URI as defined by RFC 3986.
|
||||
* @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
protected $parser, $percentEncoder;
|
||||
protected $embedsResource;
|
||||
|
||||
/**
|
||||
* @param $embeds_resource_resource Does the URI here result in an extra HTTP request?
|
||||
*/
|
||||
public function __construct($embeds_resource = false) {
|
||||
$this->parser = new HTMLPurifier_URIParser();
|
||||
$this->percentEncoder = new HTMLPurifier_PercentEncoder();
|
||||
$this->embedsResource = (bool) $embeds_resource;
|
||||
}
|
||||
|
||||
public function validate($uri, $config, $context) {
|
||||
|
||||
if ($config->get('URI', 'Disable')) return false;
|
||||
|
||||
// initial operations
|
||||
$uri = $this->parseCDATA($uri);
|
||||
$uri = $this->percentEncoder->normalize($uri);
|
||||
|
||||
// parse the URI
|
||||
$uri = $this->parser->parse($uri);
|
||||
if ($uri === false) return false;
|
||||
|
||||
// add embedded flag to context for validators
|
||||
$context->register('EmbeddedURI', $this->embedsResource);
|
||||
|
||||
$ok = false;
|
||||
do {
|
||||
|
||||
// generic validation
|
||||
$result = $uri->validate($config, $context);
|
||||
if (!$result) break;
|
||||
|
||||
// chained filtering
|
||||
$uri_def =& $config->getDefinition('URI');
|
||||
$result = $uri_def->filter($uri, $config, $context);
|
||||
if (!$result) break;
|
||||
|
||||
// scheme-specific validation
|
||||
$scheme_obj = $uri->getSchemeObj($config, $context);
|
||||
if (!$scheme_obj) break;
|
||||
if ($this->embedsResource && !$scheme_obj->browsable) break;
|
||||
$result = $scheme_obj->validate($uri, $config, $context);
|
||||
if (!$result) break;
|
||||
|
||||
// survived gauntlet
|
||||
$ok = true;
|
||||
|
||||
} while (false);
|
||||
|
||||
$context->destroy('EmbeddedURI');
|
||||
if (!$ok) return false;
|
||||
|
||||
// munge scheme off if necessary (this must be last)
|
||||
if (!is_null($uri->scheme) && is_null($uri->host)) {
|
||||
if ($uri_def->defaultScheme == $uri->scheme) {
|
||||
$uri->scheme = null;
|
||||
}
|
||||
}
|
||||
|
||||
// back to string
|
||||
$result = $uri->toString();
|
||||
|
||||
// munge entire URI if necessary
|
||||
if (
|
||||
!is_null($uri->host) && // indicator for authority
|
||||
!empty($scheme_obj->browsable) &&
|
||||
!is_null($munge = $config->get('URI', 'Munge'))
|
||||
) {
|
||||
$result = str_replace('%s', rawurlencode($result), $munge);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Unpacks a mailbox into its display-name and address
|
||||
*/
|
||||
function unpack($string) {
|
||||
// needs to be implemented
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// sub-implementations
|
||||
require_once 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/URI/Email.php';
|
||||
|
||||
/**
|
||||
* Primitive email validation class based on the regexp found at
|
||||
* http://www.regular-expressions.info/email.html
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
|
||||
{
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
// no support for named mailboxes i.e. "Bob <bob@example.com>"
|
||||
// that needs more percent encoding to be done
|
||||
if ($string == '') return false;
|
||||
$string = trim($string);
|
||||
$result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
|
||||
return $result ? $string : false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
require_once 'HTMLPurifier/AttrDef/URI/IPv4.php';
|
||||
require_once 'HTMLPurifier/AttrDef/URI/IPv6.php';
|
||||
|
||||
/**
|
||||
* Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_URI_IPv4 sub-validator
|
||||
*/
|
||||
protected $ipv4;
|
||||
|
||||
/**
|
||||
* Instance of HTMLPurifier_AttrDef_URI_IPv6 sub-validator
|
||||
*/
|
||||
protected $ipv6;
|
||||
|
||||
public function __construct() {
|
||||
$this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
|
||||
$this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
|
||||
}
|
||||
|
||||
public function validate($string, $config, $context) {
|
||||
$length = strlen($string);
|
||||
if ($string === '') return '';
|
||||
if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') {
|
||||
//IPv6
|
||||
$ip = substr($string, 1, $length - 2);
|
||||
$valid = $this->ipv6->validate($ip, $config, $context);
|
||||
if ($valid === false) return false;
|
||||
return '['. $valid . ']';
|
||||
}
|
||||
|
||||
// need to do checks on unusual encodings too
|
||||
$ipv4 = $this->ipv4->validate($string, $config, $context);
|
||||
if ($ipv4 !== false) return $ipv4;
|
||||
|
||||
// validate a domain name here, do filtering, etc etc etc
|
||||
|
||||
// We could use this, but it would break I18N domain names
|
||||
//$match = preg_match('/^[a-z0-9][\w\-\.]*[a-z0-9]$/i', $string);
|
||||
//if (!$match) return false;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef.php';
|
||||
|
||||
/**
|
||||
* Validates an IPv4 address
|
||||
* @author Feyd @ forums.devnetwork.net (public domain)
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
|
||||
{
|
||||
|
||||
/**
|
||||
* IPv4 regex, protected so that IPv6 can reuse it
|
||||
*/
|
||||
protected $ip4;
|
||||
|
||||
public function validate($aIP, $config, $context) {
|
||||
|
||||
if (!$this->ip4) $this->_loadRegex();
|
||||
|
||||
if (preg_match('#^' . $this->ip4 . '$#s', $aIP))
|
||||
{
|
||||
return $aIP;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy load function to prevent regex from being stuffed in
|
||||
* cache.
|
||||
*/
|
||||
protected function _loadRegex() {
|
||||
$oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
|
||||
$this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/URI/IPv4.php';
|
||||
|
||||
/**
|
||||
* Validates an IPv6 address.
|
||||
* @author Feyd @ forums.devnetwork.net (public domain)
|
||||
* @note This function requires brackets to have been removed from address
|
||||
* in URI.
|
||||
*/
|
||||
class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
|
||||
{
|
||||
|
||||
public function validate($aIP, $config, $context) {
|
||||
|
||||
if (!$this->ip4) $this->_loadRegex();
|
||||
|
||||
$original = $aIP;
|
||||
|
||||
$hex = '[0-9a-fA-F]';
|
||||
$blk = '(?:' . $hex . '{1,4})';
|
||||
$pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128
|
||||
|
||||
// prefix check
|
||||
if (strpos($aIP, '/') !== false)
|
||||
{
|
||||
if (preg_match('#' . $pre . '$#s', $aIP, $find))
|
||||
{
|
||||
$aIP = substr($aIP, 0, 0-strlen($find[0]));
|
||||
unset($find);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// IPv4-compatiblity check
|
||||
if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find))
|
||||
{
|
||||
$aIP = substr($aIP, 0, 0-strlen($find[0]));
|
||||
$ip = explode('.', $find[0]);
|
||||
$ip = array_map('dechex', $ip);
|
||||
$aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
|
||||
unset($find, $ip);
|
||||
}
|
||||
|
||||
// compression check
|
||||
$aIP = explode('::', $aIP);
|
||||
$c = count($aIP);
|
||||
if ($c > 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
elseif ($c == 2)
|
||||
{
|
||||
list($first, $second) = $aIP;
|
||||
$first = explode(':', $first);
|
||||
$second = explode(':', $second);
|
||||
|
||||
if (count($first) + count($second) > 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while(count($first) < 8)
|
||||
{
|
||||
array_push($first, '0');
|
||||
}
|
||||
|
||||
array_splice($first, 8 - count($second), 8, $second);
|
||||
$aIP = $first;
|
||||
unset($first,$second);
|
||||
}
|
||||
else
|
||||
{
|
||||
$aIP = explode(':', $aIP[0]);
|
||||
}
|
||||
$c = count($aIP);
|
||||
|
||||
if ($c != 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// All the pieces should be 16-bit hex strings. Are they?
|
||||
foreach ($aIP as $piece)
|
||||
{
|
||||
if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $original;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Processes an entire attribute array for corrections needing multiple values.
|
||||
*
|
||||
* Occasionally, a certain attribute will need to be removed and popped onto
|
||||
* another value. Instead of creating a complex return syntax for
|
||||
* HTMLPurifier_AttrDef, we just pass the whole attribute array to a
|
||||
* specialized object and have that do the special work. That is the
|
||||
* family of HTMLPurifier_AttrTransform.
|
||||
*
|
||||
* An attribute transformation can be assigned to run before or after
|
||||
* HTMLPurifier_AttrDef validation. See HTMLPurifier_HTMLDefinition for
|
||||
* more details.
|
||||
*/
|
||||
|
||||
abstract class HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
/**
|
||||
* Abstract: makes changes to the attributes dependent on multiple values.
|
||||
*
|
||||
* @param $attr Assoc array of attributes, usually from
|
||||
* HTMLPurifier_Token_Tag::$attr
|
||||
* @param $config Mandatory HTMLPurifier_Config object.
|
||||
* @param $context Mandatory HTMLPurifier_Context object
|
||||
* @returns Processed attribute array.
|
||||
*/
|
||||
abstract public function transform($attr, $config, $context);
|
||||
|
||||
/**
|
||||
* Prepends CSS properties to the style attribute, creating the
|
||||
* attribute if it doesn't exist.
|
||||
* @param $attr Attribute array to process (passed by reference)
|
||||
* @param $css CSS to prepend
|
||||
*/
|
||||
public function prependCSS(&$attr, $css) {
|
||||
$attr['style'] = isset($attr['style']) ? $attr['style'] : '';
|
||||
$attr['style'] = $css . $attr['style'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves and removes an attribute
|
||||
* @param $attr Attribute array to process (passed by reference)
|
||||
* @param $key Key of attribute to confiscate
|
||||
*/
|
||||
public function confiscateAttr(&$attr, $key) {
|
||||
if (!isset($attr[$key])) return null;
|
||||
$value = $attr[$key];
|
||||
unset($attr[$key]);
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
// this MUST be placed in post, as it assumes that any value in dir is valid
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'DefaultTextDir', 'ltr', 'string',
|
||||
'Defines the default text direction (ltr or rtl) of the document '.
|
||||
'being parsed. This generally is the same as the value of the dir '.
|
||||
'attribute in HTML, or ltr if that is not specified.'
|
||||
);
|
||||
HTMLPurifier_ConfigSchema::defineAllowedValues(
|
||||
'Attr', 'DefaultTextDir', array( 'ltr', 'rtl' )
|
||||
);
|
||||
|
||||
/**
|
||||
* Post-trasnform that ensures that bdo tags have the dir attribute set.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (isset($attr['dir'])) return $attr;
|
||||
$attr['dir'] = $config->get('Attr', 'DefaultTextDir');
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Pre-transform that changes deprecated bgcolor attribute to CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform {
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
|
||||
if (!isset($attr['bgcolor'])) return $attr;
|
||||
|
||||
$bgcolor = $this->confiscateAttr($attr, 'bgcolor');
|
||||
// some validation should happen here
|
||||
|
||||
$this->prependCSS($attr, "background-color:$bgcolor;");
|
||||
|
||||
return $attr;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Pre-transform that changes converts a boolean attribute to fixed CSS
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_BoolToCSS
|
||||
extends HTMLPurifier_AttrTransform {
|
||||
|
||||
/**
|
||||
* Name of boolean attribute that is trigger
|
||||
*/
|
||||
protected $attr;
|
||||
|
||||
/**
|
||||
* CSS declarations to add to style, needs trailing semicolon
|
||||
*/
|
||||
protected $css;
|
||||
|
||||
/**
|
||||
* @param $attr string attribute name to convert from
|
||||
* @param $css string CSS declarations to add to style (needs semicolon)
|
||||
*/
|
||||
public function __construct($attr, $css) {
|
||||
$this->attr = $attr;
|
||||
$this->css = $css;
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr[$this->attr])) return $attr;
|
||||
unset($attr[$this->attr]);
|
||||
$this->prependCSS($attr, $this->css);
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Pre-transform that changes deprecated border attribute to CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform {
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr['border'])) return $attr;
|
||||
$border_width = $this->confiscateAttr($attr, 'border');
|
||||
// some validation should happen here
|
||||
$this->prependCSS($attr, "border:{$border_width}px solid;");
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Generic pre-transform that converts an attribute with a fixed number of
|
||||
* values (enumerated) to CSS.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform {
|
||||
|
||||
/**
|
||||
* Name of attribute to transform from
|
||||
*/
|
||||
protected $attr;
|
||||
|
||||
/**
|
||||
* Lookup array of attribute values to CSS
|
||||
*/
|
||||
protected $enumToCSS = array();
|
||||
|
||||
/**
|
||||
* Case sensitivity of the matching
|
||||
* @warning Currently can only be guaranteed to work with ASCII
|
||||
* values.
|
||||
*/
|
||||
protected $caseSensitive = false;
|
||||
|
||||
/**
|
||||
* @param $attr String attribute name to transform from
|
||||
* @param $enumToCSS Lookup array of attribute values to CSS
|
||||
* @param $case_sensitive Boolean case sensitivity indicator, default false
|
||||
*/
|
||||
public function __construct($attr, $enum_to_css, $case_sensitive = false) {
|
||||
$this->attr = $attr;
|
||||
$this->enumToCSS = $enum_to_css;
|
||||
$this->caseSensitive = (bool) $case_sensitive;
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
|
||||
if (!isset($attr[$this->attr])) return $attr;
|
||||
|
||||
$value = trim($attr[$this->attr]);
|
||||
unset($attr[$this->attr]);
|
||||
|
||||
if (!$this->caseSensitive) $value = strtolower($value);
|
||||
|
||||
if (!isset($this->enumToCSS[$value])) {
|
||||
return $attr;
|
||||
}
|
||||
|
||||
$this->prependCSS($attr, $this->enumToCSS[$value]);
|
||||
|
||||
return $attr;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
// must be called POST validation
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'DefaultInvalidImage', '', 'string',
|
||||
'This is the default image an img tag will be pointed to if it does '.
|
||||
'not have a valid src attribute. In future versions, we may allow the '.
|
||||
'image tag to be removed completely, but due to design issues, this is '.
|
||||
'not possible right now.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Attr', 'DefaultInvalidImageAlt', 'Invalid image', 'string',
|
||||
'This is the content of the alt tag of an invalid image if the user '.
|
||||
'had not previously specified an alt attribute. It has no effect when the '.
|
||||
'image is valid but there was no alt attribute present.'
|
||||
);
|
||||
|
||||
/**
|
||||
* Transform that supplies default values for the src and alt attributes
|
||||
* in img tags, as well as prevents the img tag from being removed
|
||||
* because of a missing alt tag. This needs to be registered as both
|
||||
* a pre and post attribute transform.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
|
||||
$src = true;
|
||||
if (!isset($attr['src'])) {
|
||||
if ($config->get('Core', 'RemoveInvalidImg')) return $attr;
|
||||
$attr['src'] = $config->get('Attr', 'DefaultInvalidImage');
|
||||
$src = false;
|
||||
}
|
||||
|
||||
if (!isset($attr['alt'])) {
|
||||
if ($src) {
|
||||
$attr['alt'] = basename($attr['src']);
|
||||
} else {
|
||||
$attr['alt'] = $config->get('Attr', 'DefaultInvalidImageAlt');
|
||||
}
|
||||
}
|
||||
|
||||
return $attr;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Pre-transform that changes deprecated hspace and vspace attributes to CSS
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform {
|
||||
|
||||
protected $attr;
|
||||
protected $css = array(
|
||||
'hspace' => array('left', 'right'),
|
||||
'vspace' => array('top', 'bottom')
|
||||
);
|
||||
|
||||
public function __construct($attr) {
|
||||
$this->attr = $attr;
|
||||
if (!isset($this->css[$attr])) {
|
||||
trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
|
||||
}
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
|
||||
if (!isset($attr[$this->attr])) return $attr;
|
||||
|
||||
$width = $this->confiscateAttr($attr, $this->attr);
|
||||
// some validation could happen here
|
||||
|
||||
if (!isset($this->css[$this->attr])) return $attr;
|
||||
|
||||
$style = '';
|
||||
foreach ($this->css[$this->attr] as $suffix) {
|
||||
$property = "margin-$suffix";
|
||||
$style .= "$property:{$width}px;";
|
||||
}
|
||||
|
||||
$this->prependCSS($attr, $style);
|
||||
|
||||
return $attr;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Post-transform that copies lang's value to xml:lang (and vice-versa)
|
||||
* @note Theoretically speaking, this could be a pre-transform, but putting
|
||||
* post is more efficient.
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
|
||||
$lang = isset($attr['lang']) ? $attr['lang'] : false;
|
||||
$xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
|
||||
|
||||
if ($lang !== false && $xml_lang === false) {
|
||||
$attr['xml:lang'] = $lang;
|
||||
} elseif ($xml_lang !== false) {
|
||||
$attr['lang'] = $xml_lang;
|
||||
}
|
||||
|
||||
return $attr;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Class for handling width/height length attribute transformations to CSS
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
protected $name;
|
||||
protected $cssName;
|
||||
|
||||
public function __construct($name, $css_name = null) {
|
||||
$this->name = $name;
|
||||
$this->cssName = $css_name ? $css_name : $name;
|
||||
}
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr[$this->name])) return $attr;
|
||||
$length = $this->confiscateAttr($attr, $this->name);
|
||||
if(ctype_digit($length)) $length .= 'px';
|
||||
$this->prependCSS($attr, $this->cssName . ":$length;");
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrTransform.php';
|
||||
|
||||
/**
|
||||
* Pre-transform that changes deprecated name attribute to ID if necessary
|
||||
*/
|
||||
class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
|
||||
{
|
||||
|
||||
public function transform($attr, $config, $context) {
|
||||
if (!isset($attr['name'])) return $attr;
|
||||
$id = $this->confiscateAttr($attr, 'name');
|
||||
if ( isset($attr['id'])) return $attr;
|
||||
$attr['id'] = $id;
|
||||
return $attr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/Lang.php';
|
||||
require_once 'HTMLPurifier/AttrDef/Enum.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Bool.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/ID.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Length.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/MultiLength.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Nmtokens.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Pixels.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Color.php';
|
||||
require_once 'HTMLPurifier/AttrDef/Integer.php';
|
||||
require_once 'HTMLPurifier/AttrDef/Text.php';
|
||||
require_once 'HTMLPurifier/AttrDef/URI.php';
|
||||
|
||||
/**
|
||||
* Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
|
||||
*/
|
||||
class HTMLPurifier_AttrTypes
|
||||
{
|
||||
/**
|
||||
* Lookup array of attribute string identifiers to concrete implementations
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Constructs the info array, supplying default implementations for attribute
|
||||
* types.
|
||||
*/
|
||||
public function __construct() {
|
||||
// pseudo-types, must be instantiated via shorthand
|
||||
$this->info['Enum'] = new HTMLPurifier_AttrDef_Enum();
|
||||
$this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool();
|
||||
|
||||
$this->info['CDATA'] = new HTMLPurifier_AttrDef_Text();
|
||||
$this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID();
|
||||
$this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length();
|
||||
$this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
|
||||
$this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
|
||||
$this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels();
|
||||
$this->info['Text'] = new HTMLPurifier_AttrDef_Text();
|
||||
$this->info['URI'] = new HTMLPurifier_AttrDef_URI();
|
||||
$this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
|
||||
$this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color();
|
||||
|
||||
// unimplemented aliases
|
||||
$this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
|
||||
|
||||
// number is really a positive integer (one or more digits)
|
||||
// FIXME: ^^ not always, see start and value of list items
|
||||
$this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a type
|
||||
* @param $type String type name
|
||||
* @return Object AttrDef for type
|
||||
*/
|
||||
public function get($type) {
|
||||
|
||||
// determine if there is any extra info tacked on
|
||||
if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2);
|
||||
else $string = '';
|
||||
|
||||
if (!isset($this->info[$type])) {
|
||||
trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
return $this->info[$type]->make($string);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new implementation for a type
|
||||
* @param $type String type name
|
||||
* @param $impl Object AttrDef for type
|
||||
*/
|
||||
public function set($type, $impl) {
|
||||
$this->info[$type] = $impl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Validates the attributes of a token. Doesn't manage required attributes
|
||||
* very well. The only reason we factored this out was because RemoveForeignElements
|
||||
* also needed it besides ValidateAttributes.
|
||||
*/
|
||||
class HTMLPurifier_AttrValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Validates the attributes of a token, returning a modified token
|
||||
* that has valid tokens
|
||||
* @param $token Reference to token to validate. We require a reference
|
||||
* because the operation this class performs on the token are
|
||||
* not atomic, so the context CurrentToken to be updated
|
||||
* throughout
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
*/
|
||||
public function validateToken(&$token, &$config, $context) {
|
||||
|
||||
$definition = $config->getHTMLDefinition();
|
||||
$e =& $context->get('ErrorCollector', true);
|
||||
|
||||
// initialize IDAccumulator if necessary
|
||||
$ok =& $context->get('IDAccumulator', true);
|
||||
if (!$ok) {
|
||||
$id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
|
||||
$context->register('IDAccumulator', $id_accumulator);
|
||||
}
|
||||
|
||||
// initialize CurrentToken if necessary
|
||||
$current_token =& $context->get('CurrentToken', true);
|
||||
if (!$current_token) $context->register('CurrentToken', $token);
|
||||
|
||||
if ($token->type !== 'start' && $token->type !== 'empty') return $token;
|
||||
|
||||
// create alias to global definition array, see also $defs
|
||||
// DEFINITION CALL
|
||||
$d_defs = $definition->info_global_attr;
|
||||
|
||||
// reference attributes for easy manipulation
|
||||
$attr =& $token->attr;
|
||||
|
||||
// do global transformations (pre)
|
||||
// nothing currently utilizes this
|
||||
foreach ($definition->info_attr_transform_pre as $transform) {
|
||||
$attr = $transform->transform($o = $attr, $config, $context);
|
||||
if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
|
||||
}
|
||||
|
||||
// do local transformations only applicable to this element (pre)
|
||||
// ex. <p align="right"> to <p style="text-align:right;">
|
||||
foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
|
||||
$attr = $transform->transform($o = $attr, $config, $context);
|
||||
if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
|
||||
}
|
||||
|
||||
// create alias to this element's attribute definition array, see
|
||||
// also $d_defs (global attribute definition array)
|
||||
// DEFINITION CALL
|
||||
$defs = $definition->info[$token->name]->attr;
|
||||
|
||||
$attr_key = false;
|
||||
$context->register('CurrentAttr', $attr_key);
|
||||
|
||||
// iterate through all the attribute keypairs
|
||||
// Watch out for name collisions: $key has previously been used
|
||||
foreach ($attr as $attr_key => $value) {
|
||||
|
||||
// call the definition
|
||||
if ( isset($defs[$attr_key]) ) {
|
||||
// there is a local definition defined
|
||||
if ($defs[$attr_key] === false) {
|
||||
// We've explicitly been told not to allow this element.
|
||||
// This is usually when there's a global definition
|
||||
// that must be overridden.
|
||||
// Theoretically speaking, we could have a
|
||||
// AttrDef_DenyAll, but this is faster!
|
||||
$result = false;
|
||||
} else {
|
||||
// validate according to the element's definition
|
||||
$result = $defs[$attr_key]->validate(
|
||||
$value, $config, $context
|
||||
);
|
||||
}
|
||||
} elseif ( isset($d_defs[$attr_key]) ) {
|
||||
// there is a global definition defined, validate according
|
||||
// to the global definition
|
||||
$result = $d_defs[$attr_key]->validate(
|
||||
$value, $config, $context
|
||||
);
|
||||
} else {
|
||||
// system never heard of the attribute? DELETE!
|
||||
$result = false;
|
||||
}
|
||||
|
||||
// put the results into effect
|
||||
if ($result === false || $result === null) {
|
||||
// this is a generic error message that should replaced
|
||||
// with more specific ones when possible
|
||||
if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed');
|
||||
|
||||
// remove the attribute
|
||||
unset($attr[$attr_key]);
|
||||
} elseif (is_string($result)) {
|
||||
// generally, if a substitution is happening, there
|
||||
// was some sort of implicit correction going on. We'll
|
||||
// delegate it to the attribute classes to say exactly what.
|
||||
|
||||
// simple substitution
|
||||
$attr[$attr_key] = $result;
|
||||
}
|
||||
|
||||
// we'd also want slightly more complicated substitution
|
||||
// involving an array as the return value,
|
||||
// although we're not sure how colliding attributes would
|
||||
// resolve (certain ones would be completely overriden,
|
||||
// others would prepend themselves).
|
||||
}
|
||||
|
||||
$context->destroy('CurrentAttr');
|
||||
|
||||
// post transforms
|
||||
|
||||
// global (error reporting untested)
|
||||
foreach ($definition->info_attr_transform_post as $transform) {
|
||||
$attr = $transform->transform($o = $attr, $config, $context);
|
||||
if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
|
||||
}
|
||||
|
||||
// local (error reporting untested)
|
||||
foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
|
||||
$attr = $transform->transform($o = $attr, $config, $context);
|
||||
if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
|
||||
}
|
||||
|
||||
// destroy CurrentToken if we made it ourselves
|
||||
if (!$current_token) $context->destroy('CurrentToken');
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Definition.php';
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/AlphaValue.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Background.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Border.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Color.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Composite.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Filter.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Font.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/FontFamily.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Length.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/ListStyle.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Multiple.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/Percentage.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/TextDecoration.php';
|
||||
require_once 'HTMLPurifier/AttrDef/CSS/URI.php';
|
||||
require_once 'HTMLPurifier/AttrDef/Enum.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'CSS', 'DefinitionRev', 1, 'int', '
|
||||
<p>
|
||||
Revision identifier for your custom definition. See
|
||||
%HTML.DefinitionRev for details. This directive has been available
|
||||
since 2.0.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'CSS', 'Proprietary', false, 'bool', '
|
||||
<p>
|
||||
Whether or not to allow safe, proprietary CSS values. This directive
|
||||
has been available since 3.0.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
/**
|
||||
* Defines allowed CSS attributes and what their values are.
|
||||
* @see HTMLPurifier_HTMLDefinition
|
||||
*/
|
||||
class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
|
||||
{
|
||||
|
||||
public $type = 'CSS';
|
||||
|
||||
/**
|
||||
* Assoc array of attribute name to definition object.
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Constructs the info array. The meat of this class.
|
||||
*/
|
||||
protected function doSetup($config) {
|
||||
|
||||
$this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('left', 'right', 'center', 'justify'), false);
|
||||
|
||||
$border_style =
|
||||
$this->info['border-bottom-style'] =
|
||||
$this->info['border-right-style'] =
|
||||
$this->info['border-left-style'] =
|
||||
$this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
|
||||
'groove', 'ridge', 'inset', 'outset'), false);
|
||||
|
||||
$this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
|
||||
|
||||
$this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('none', 'left', 'right', 'both'), false);
|
||||
$this->info['float'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('none', 'left', 'right'), false);
|
||||
$this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('normal', 'italic', 'oblique'), false);
|
||||
$this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('normal', 'small-caps'), false);
|
||||
|
||||
$uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
|
||||
array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('none')),
|
||||
new HTMLPurifier_AttrDef_CSS_URI()
|
||||
)
|
||||
);
|
||||
|
||||
$this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('inside', 'outside'), false);
|
||||
$this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('disc', 'circle', 'square', 'decimal', 'lower-roman',
|
||||
'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false);
|
||||
$this->info['list-style-image'] = $uri_or_none;
|
||||
|
||||
$this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
|
||||
|
||||
$this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('capitalize', 'uppercase', 'lowercase', 'none'), false);
|
||||
$this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
|
||||
$this->info['background-image'] = $uri_or_none;
|
||||
$this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
|
||||
);
|
||||
$this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('scroll', 'fixed')
|
||||
);
|
||||
$this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
|
||||
|
||||
$border_color =
|
||||
$this->info['border-top-color'] =
|
||||
$this->info['border-bottom-color'] =
|
||||
$this->info['border-left-color'] =
|
||||
$this->info['border-right-color'] =
|
||||
$this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('transparent')),
|
||||
new HTMLPurifier_AttrDef_CSS_Color()
|
||||
));
|
||||
|
||||
$this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
|
||||
|
||||
$this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
|
||||
|
||||
$border_width =
|
||||
$this->info['border-top-width'] =
|
||||
$this->info['border-bottom-width'] =
|
||||
$this->info['border-left-width'] =
|
||||
$this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length(true) //disallow negative
|
||||
));
|
||||
|
||||
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
|
||||
|
||||
$this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('normal')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length()
|
||||
));
|
||||
|
||||
$this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('normal')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length()
|
||||
));
|
||||
|
||||
$this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small',
|
||||
'small', 'medium', 'large', 'x-large', 'xx-large',
|
||||
'larger', 'smaller')),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(),
|
||||
new HTMLPurifier_AttrDef_CSS_Length()
|
||||
));
|
||||
|
||||
$this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('normal')),
|
||||
new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
|
||||
new HTMLPurifier_AttrDef_CSS_Length(true),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(true)
|
||||
));
|
||||
|
||||
$margin =
|
||||
$this->info['margin-top'] =
|
||||
$this->info['margin-bottom'] =
|
||||
$this->info['margin-left'] =
|
||||
$this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(),
|
||||
new HTMLPurifier_AttrDef_Enum(array('auto'))
|
||||
));
|
||||
|
||||
$this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
|
||||
|
||||
// non-negative
|
||||
$padding =
|
||||
$this->info['padding-top'] =
|
||||
$this->info['padding-bottom'] =
|
||||
$this->info['padding-left'] =
|
||||
$this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length(true),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(true)
|
||||
));
|
||||
|
||||
$this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
|
||||
|
||||
$this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage()
|
||||
));
|
||||
|
||||
$this->info['width'] =
|
||||
$this->info['height'] =
|
||||
new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_CSS_Length(true),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage(true),
|
||||
new HTMLPurifier_AttrDef_Enum(array('auto'))
|
||||
));
|
||||
|
||||
$this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
|
||||
|
||||
$this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
|
||||
|
||||
// this could use specialized code
|
||||
$this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
|
||||
array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300',
|
||||
'400', '500', '600', '700', '800', '900'), false);
|
||||
|
||||
// MUST be called after other font properties, as it references
|
||||
// a CSSDefinition object
|
||||
$this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
|
||||
|
||||
// same here
|
||||
$this->info['border'] =
|
||||
$this->info['border-bottom'] =
|
||||
$this->info['border-top'] =
|
||||
$this->info['border-left'] =
|
||||
$this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
|
||||
|
||||
$this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'collapse', 'separate'));
|
||||
|
||||
$this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'top', 'bottom'));
|
||||
|
||||
$this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array(
|
||||
'auto', 'fixed'));
|
||||
|
||||
$this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
|
||||
new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super',
|
||||
'top', 'text-top', 'middle', 'bottom', 'text-bottom')),
|
||||
new HTMLPurifier_AttrDef_CSS_Length(),
|
||||
new HTMLPurifier_AttrDef_CSS_Percentage()
|
||||
));
|
||||
|
||||
$this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
|
||||
|
||||
// partial support
|
||||
$this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap'));
|
||||
|
||||
if ($config->get('CSS', 'Proprietary')) {
|
||||
$this->doSetupProprietary($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function doSetupProprietary($config) {
|
||||
// Internet Explorer only scrollbar colors
|
||||
$this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
$this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color();
|
||||
|
||||
// technically not proprietary, but CSS3, and no one supports it
|
||||
$this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
|
||||
$this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
|
||||
$this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue();
|
||||
|
||||
// only opacity, for now
|
||||
$this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
// HTMLPurifier_ChildDef and inheritance have three types of output:
|
||||
// true = leave nodes as is
|
||||
// false = delete parent node and all children
|
||||
// array(...) = replace children nodes with these
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'EscapeInvalidChildren', false, 'bool',
|
||||
'When true, a child is found that is not allowed in the context of the '.
|
||||
'parent element will be transformed into text as if it were ASCII. When '.
|
||||
'false, that element and all internal tags will be dropped, though text '.
|
||||
'will be preserved. There is no option for dropping the element but '.
|
||||
'preserving child nodes.'
|
||||
);
|
||||
|
||||
/**
|
||||
* Defines allowed child nodes and validates tokens against it.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef
|
||||
{
|
||||
/**
|
||||
* Type of child definition, usually right-most part of class name lowercase.
|
||||
* Used occasionally in terms of context.
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Bool that indicates whether or not an empty array of children is okay
|
||||
*
|
||||
* This is necessary for redundant checking when changes affecting
|
||||
* a child node may cause a parent node to now be disallowed.
|
||||
*/
|
||||
public $allow_empty;
|
||||
|
||||
/**
|
||||
* Lookup array of all elements that this definition could possibly allow
|
||||
*/
|
||||
public $elements = array();
|
||||
|
||||
/**
|
||||
* Validates nodes according to definition and returns modification.
|
||||
*
|
||||
* @param $tokens_of_children Array of HTMLPurifier_Token
|
||||
* @param $config HTMLPurifier_Config object
|
||||
* @param $context HTMLPurifier_Context object
|
||||
* @return bool true to leave nodes as is
|
||||
* @return bool false to remove parent node
|
||||
* @return array of replacement child tokens
|
||||
*/
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
trigger_error('Call to abstract function', E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef.php';
|
||||
|
||||
/**
|
||||
* Definition that uses different definitions depending on context.
|
||||
*
|
||||
* The del and ins tags are notable because they allow different types of
|
||||
* elements depending on whether or not they're in a block or inline context.
|
||||
* Chameleon allows this behavior to happen by using two different
|
||||
* definitions depending on context. While this somewhat generalized,
|
||||
* it is specifically intended for those two tags.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Instance of the definition object to use when inline. Usually stricter.
|
||||
*/
|
||||
public $inline;
|
||||
|
||||
/**
|
||||
* Instance of the definition object to use when block.
|
||||
*/
|
||||
public $block;
|
||||
|
||||
public $type = 'chameleon';
|
||||
|
||||
/**
|
||||
* @param $inline List of elements to allow when inline.
|
||||
* @param $block List of elements to allow when block.
|
||||
*/
|
||||
public function __construct($inline, $block) {
|
||||
$this->inline = new HTMLPurifier_ChildDef_Optional($inline);
|
||||
$this->block = new HTMLPurifier_ChildDef_Optional($block);
|
||||
$this->elements = $this->block->elements;
|
||||
}
|
||||
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
if ($context->get('IsInline') === false) {
|
||||
return $this->block->validateChildren(
|
||||
$tokens_of_children, $config, $context);
|
||||
} else {
|
||||
return $this->inline->validateChildren(
|
||||
$tokens_of_children, $config, $context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef.php';
|
||||
|
||||
/**
|
||||
* Custom validation class, accepts DTD child definitions
|
||||
*
|
||||
* @warning Currently this class is an all or nothing proposition, that is,
|
||||
* it will only give a bool return value.
|
||||
* @note This class is currently not used by any code, although it is unit
|
||||
* tested.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $type = 'custom';
|
||||
public $allow_empty = false;
|
||||
/**
|
||||
* Allowed child pattern as defined by the DTD
|
||||
*/
|
||||
public $dtd_regex;
|
||||
/**
|
||||
* PCRE regex derived from $dtd_regex
|
||||
* @private
|
||||
*/
|
||||
private $_pcre_regex;
|
||||
/**
|
||||
* @param $dtd_regex Allowed child pattern from the DTD
|
||||
*/
|
||||
public function __construct($dtd_regex) {
|
||||
$this->dtd_regex = $dtd_regex;
|
||||
$this->_compileRegex();
|
||||
}
|
||||
/**
|
||||
* Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
|
||||
*/
|
||||
protected function _compileRegex() {
|
||||
$raw = str_replace(' ', '', $this->dtd_regex);
|
||||
if ($raw{0} != '(') {
|
||||
$raw = "($raw)";
|
||||
}
|
||||
$el = '[#a-zA-Z0-9_.-]+';
|
||||
$reg = $raw;
|
||||
|
||||
// COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
|
||||
// DOING! Seriously: if there's problems, please report them.
|
||||
|
||||
// collect all elements into the $elements array
|
||||
preg_match_all("/$el/", $reg, $matches);
|
||||
foreach ($matches[0] as $match) {
|
||||
$this->elements[$match] = true;
|
||||
}
|
||||
|
||||
// setup all elements as parentheticals with leading commas
|
||||
$reg = preg_replace("/$el/", '(,\\0)', $reg);
|
||||
|
||||
// remove commas when they were not solicited
|
||||
$reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
|
||||
|
||||
// remove all non-paranthetical commas: they are handled by first regex
|
||||
$reg = preg_replace("/,\(/", '(', $reg);
|
||||
|
||||
$this->_pcre_regex = $reg;
|
||||
}
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
$list_of_children = '';
|
||||
$nesting = 0; // depth into the nest
|
||||
foreach ($tokens_of_children as $token) {
|
||||
if (!empty($token->is_whitespace)) continue;
|
||||
|
||||
$is_child = ($nesting == 0); // direct
|
||||
|
||||
if ($token->type == 'start') {
|
||||
$nesting++;
|
||||
} elseif ($token->type == 'end') {
|
||||
$nesting--;
|
||||
}
|
||||
|
||||
if ($is_child) {
|
||||
$list_of_children .= $token->name . ',';
|
||||
}
|
||||
}
|
||||
// add leading comma to deal with stray comma declarations
|
||||
$list_of_children = ',' . rtrim($list_of_children, ',');
|
||||
$okay =
|
||||
preg_match(
|
||||
'/^,?'.$this->_pcre_regex.'$/',
|
||||
$list_of_children
|
||||
);
|
||||
|
||||
return (bool) $okay;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef.php';
|
||||
|
||||
/**
|
||||
* Definition that disallows all elements.
|
||||
* @warning validateChildren() in this class is actually never called, because
|
||||
* empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
|
||||
* before child definitions are parsed in earnest by
|
||||
* HTMLPurifier_Strategy_FixNesting.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $allow_empty = true;
|
||||
public $type = 'empty';
|
||||
public function __construct() {}
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef/Required.php';
|
||||
|
||||
/**
|
||||
* Definition that allows a set of elements, and allows no children.
|
||||
* @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
|
||||
* really, one shouldn't inherit from the other. Only altered behavior
|
||||
* is to overload a returned false with an array. Thus, it will never
|
||||
* return false.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
|
||||
{
|
||||
public $allow_empty = true;
|
||||
public $type = 'optional';
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
$result = parent::validateChildren($tokens_of_children, $config, $context);
|
||||
if ($result === false) {
|
||||
if (empty($tokens_of_children)) return true;
|
||||
else return array();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef.php';
|
||||
|
||||
/**
|
||||
* Definition that allows a set of elements, but disallows empty children.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
|
||||
{
|
||||
/**
|
||||
* Lookup table of allowed elements.
|
||||
* @public
|
||||
*/
|
||||
public $elements = array();
|
||||
/**
|
||||
* @param $elements List of allowed element names (lowercase).
|
||||
*/
|
||||
public function __construct($elements) {
|
||||
if (is_string($elements)) {
|
||||
$elements = str_replace(' ', '', $elements);
|
||||
$elements = explode('|', $elements);
|
||||
}
|
||||
$keys = array_keys($elements);
|
||||
if ($keys == array_keys($keys)) {
|
||||
$elements = array_flip($elements);
|
||||
foreach ($elements as $i => $x) {
|
||||
$elements[$i] = true;
|
||||
if (empty($i)) unset($elements[$i]); // remove blank
|
||||
}
|
||||
}
|
||||
$this->elements = $elements;
|
||||
}
|
||||
public $allow_empty = false;
|
||||
public $type = 'required';
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
// if there are no tokens, delete parent node
|
||||
if (empty($tokens_of_children)) return false;
|
||||
|
||||
// the new set of children
|
||||
$result = array();
|
||||
|
||||
// current depth into the nest
|
||||
$nesting = 0;
|
||||
|
||||
// whether or not we're deleting a node
|
||||
$is_deleting = false;
|
||||
|
||||
// whether or not parsed character data is allowed
|
||||
// this controls whether or not we silently drop a tag
|
||||
// or generate escaped HTML from it
|
||||
$pcdata_allowed = isset($this->elements['#PCDATA']);
|
||||
|
||||
// a little sanity check to make sure it's not ALL whitespace
|
||||
$all_whitespace = true;
|
||||
|
||||
// some configuration
|
||||
$escape_invalid_children = $config->get('Core', 'EscapeInvalidChildren');
|
||||
|
||||
// generator
|
||||
static $gen = null;
|
||||
if ($gen === null) {
|
||||
$gen = new HTMLPurifier_Generator();
|
||||
}
|
||||
|
||||
foreach ($tokens_of_children as $token) {
|
||||
if (!empty($token->is_whitespace)) {
|
||||
$result[] = $token;
|
||||
continue;
|
||||
}
|
||||
$all_whitespace = false; // phew, we're not talking about whitespace
|
||||
|
||||
$is_child = ($nesting == 0);
|
||||
|
||||
if ($token->type == 'start') {
|
||||
$nesting++;
|
||||
} elseif ($token->type == 'end') {
|
||||
$nesting--;
|
||||
}
|
||||
|
||||
if ($is_child) {
|
||||
$is_deleting = false;
|
||||
if (!isset($this->elements[$token->name])) {
|
||||
$is_deleting = true;
|
||||
if ($pcdata_allowed && $token->type == 'text') {
|
||||
$result[] = $token;
|
||||
} elseif ($pcdata_allowed && $escape_invalid_children) {
|
||||
$result[] = new HTMLPurifier_Token_Text(
|
||||
$gen->generateFromToken($token, $config)
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!$is_deleting || ($pcdata_allowed && $token->type == 'text')) {
|
||||
$result[] = $token;
|
||||
} elseif ($pcdata_allowed && $escape_invalid_children) {
|
||||
$result[] =
|
||||
new HTMLPurifier_Token_Text(
|
||||
$gen->generateFromToken( $token, $config )
|
||||
);
|
||||
} else {
|
||||
// drop silently
|
||||
}
|
||||
}
|
||||
if (empty($result)) return false;
|
||||
if ($all_whitespace) return false;
|
||||
if ($tokens_of_children == $result) return true;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef/Required.php';
|
||||
|
||||
/**
|
||||
* Takes the contents of blockquote when in strict and reformats for validation.
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_StrictBlockquote
|
||||
extends HTMLPurifier_ChildDef_Required
|
||||
{
|
||||
protected $real_elements;
|
||||
protected $fake_elements;
|
||||
public $allow_empty = true;
|
||||
public $type = 'strictblockquote';
|
||||
protected $init = false;
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
|
||||
$def = $config->getHTMLDefinition();
|
||||
if (!$this->init) {
|
||||
// allow all inline elements
|
||||
$this->real_elements = $this->elements;
|
||||
$this->fake_elements = $def->info_content_sets['Flow'];
|
||||
$this->fake_elements['#PCDATA'] = true;
|
||||
$this->init = true;
|
||||
}
|
||||
|
||||
// trick the parent class into thinking it allows more
|
||||
$this->elements = $this->fake_elements;
|
||||
$result = parent::validateChildren($tokens_of_children, $config, $context);
|
||||
$this->elements = $this->real_elements;
|
||||
|
||||
if ($result === false) return array();
|
||||
if ($result === true) $result = $tokens_of_children;
|
||||
|
||||
$block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper);
|
||||
$block_wrap_end = new HTMLPurifier_Token_End( $def->info_block_wrapper);
|
||||
$is_inline = false;
|
||||
$depth = 0;
|
||||
$ret = array();
|
||||
|
||||
// assuming that there are no comment tokens
|
||||
foreach ($result as $i => $token) {
|
||||
$token = $result[$i];
|
||||
// ifs are nested for readability
|
||||
if (!$is_inline) {
|
||||
if (!$depth) {
|
||||
if (
|
||||
($token->type == 'text' && !$token->is_whitespace) ||
|
||||
($token->type != 'text' && !isset($this->elements[$token->name]))
|
||||
) {
|
||||
$is_inline = true;
|
||||
$ret[] = $block_wrap_start;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!$depth) {
|
||||
// starting tokens have been inline text / empty
|
||||
if ($token->type == 'start' || $token->type == 'empty') {
|
||||
if (isset($this->elements[$token->name])) {
|
||||
// ended
|
||||
$ret[] = $block_wrap_end;
|
||||
$is_inline = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret[] = $token;
|
||||
if ($token->type == 'start') $depth++;
|
||||
if ($token->type == 'end') $depth--;
|
||||
}
|
||||
if ($is_inline) $ret[] = $block_wrap_end;
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ChildDef.php';
|
||||
|
||||
/**
|
||||
* Definition for tables
|
||||
*/
|
||||
class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
|
||||
{
|
||||
public $allow_empty = false;
|
||||
public $type = 'table';
|
||||
public $elements = array('tr' => true, 'tbody' => true, 'thead' => true,
|
||||
'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true);
|
||||
public function __construct() {}
|
||||
public function validateChildren($tokens_of_children, $config, $context) {
|
||||
if (empty($tokens_of_children)) return false;
|
||||
|
||||
// this ensures that the loop gets run one last time before closing
|
||||
// up. It's a little bit of a hack, but it works! Just make sure you
|
||||
// get rid of the token later.
|
||||
$tokens_of_children[] = false;
|
||||
|
||||
// only one of these elements is allowed in a table
|
||||
$caption = false;
|
||||
$thead = false;
|
||||
$tfoot = false;
|
||||
|
||||
// as many of these as you want
|
||||
$cols = array();
|
||||
$content = array();
|
||||
|
||||
$nesting = 0; // current depth so we can determine nodes
|
||||
$is_collecting = false; // are we globbing together tokens to package
|
||||
// into one of the collectors?
|
||||
$collection = array(); // collected nodes
|
||||
$tag_index = 0; // the first node might be whitespace,
|
||||
// so this tells us where the start tag is
|
||||
|
||||
foreach ($tokens_of_children as $token) {
|
||||
$is_child = ($nesting == 0);
|
||||
|
||||
if ($token === false) {
|
||||
// terminating sequence started
|
||||
} elseif ($token->type == 'start') {
|
||||
$nesting++;
|
||||
} elseif ($token->type == 'end') {
|
||||
$nesting--;
|
||||
}
|
||||
|
||||
// handle node collection
|
||||
if ($is_collecting) {
|
||||
if ($is_child) {
|
||||
// okay, let's stash the tokens away
|
||||
// first token tells us the type of the collection
|
||||
switch ($collection[$tag_index]->name) {
|
||||
case 'tr':
|
||||
case 'tbody':
|
||||
$content[] = $collection;
|
||||
break;
|
||||
case 'caption':
|
||||
if ($caption !== false) break;
|
||||
$caption = $collection;
|
||||
break;
|
||||
case 'thead':
|
||||
case 'tfoot':
|
||||
// access the appropriate variable, $thead or $tfoot
|
||||
$var = $collection[$tag_index]->name;
|
||||
if ($$var === false) {
|
||||
$$var = $collection;
|
||||
} else {
|
||||
// transmutate the first and less entries into
|
||||
// tbody tags, and then put into content
|
||||
$collection[$tag_index]->name = 'tbody';
|
||||
$collection[count($collection)-1]->name = 'tbody';
|
||||
$content[] = $collection;
|
||||
}
|
||||
break;
|
||||
case 'colgroup':
|
||||
$cols[] = $collection;
|
||||
break;
|
||||
}
|
||||
$collection = array();
|
||||
$is_collecting = false;
|
||||
$tag_index = 0;
|
||||
} else {
|
||||
// add the node to the collection
|
||||
$collection[] = $token;
|
||||
}
|
||||
}
|
||||
|
||||
// terminate
|
||||
if ($token === false) break;
|
||||
|
||||
if ($is_child) {
|
||||
// determine what we're dealing with
|
||||
if ($token->name == 'col') {
|
||||
// the only empty tag in the possie, we can handle it
|
||||
// immediately
|
||||
$cols[] = array_merge($collection, array($token));
|
||||
$collection = array();
|
||||
$tag_index = 0;
|
||||
continue;
|
||||
}
|
||||
switch($token->name) {
|
||||
case 'caption':
|
||||
case 'colgroup':
|
||||
case 'thead':
|
||||
case 'tfoot':
|
||||
case 'tbody':
|
||||
case 'tr':
|
||||
$is_collecting = true;
|
||||
$collection[] = $token;
|
||||
continue;
|
||||
default:
|
||||
if ($token->type == 'text' && $token->is_whitespace) {
|
||||
$collection[] = $token;
|
||||
$tag_index++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($content)) return false;
|
||||
|
||||
$ret = array();
|
||||
if ($caption !== false) $ret = array_merge($ret, $caption);
|
||||
if ($cols !== false) foreach ($cols as $token_array) $ret = array_merge($ret, $token_array);
|
||||
if ($thead !== false) $ret = array_merge($ret, $thead);
|
||||
if ($tfoot !== false) $ret = array_merge($ret, $tfoot);
|
||||
foreach ($content as $token_array) $ret = array_merge($ret, $token_array);
|
||||
if (!empty($collection) && $is_collecting == false){
|
||||
// grab the trailing space
|
||||
$ret = array_merge($ret, $collection);
|
||||
}
|
||||
|
||||
array_pop($tokens_of_children); // remove phantom token
|
||||
|
||||
return ($ret === $tokens_of_children) ? true : $ret;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ConfigSchema.php';
|
||||
|
||||
// member variables
|
||||
require_once 'HTMLPurifier/HTMLDefinition.php';
|
||||
require_once 'HTMLPurifier/CSSDefinition.php';
|
||||
require_once 'HTMLPurifier/URIDefinition.php';
|
||||
require_once 'HTMLPurifier/Doctype.php';
|
||||
require_once 'HTMLPurifier/DefinitionCacheFactory.php';
|
||||
|
||||
// accomodations for versions earlier than 4.3.10 and 5.0.2
|
||||
// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan@php.net>
|
||||
if (!defined('PHP_EOL')) {
|
||||
switch (strtoupper(substr(PHP_OS, 0, 3))) {
|
||||
case 'WIN':
|
||||
define('PHP_EOL', "\r\n");
|
||||
break;
|
||||
case 'DAR':
|
||||
define('PHP_EOL', "\r");
|
||||
break;
|
||||
default:
|
||||
define('PHP_EOL', "\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration object that triggers customizable behavior.
|
||||
*
|
||||
* @warning This class is strongly defined: that means that the class
|
||||
* will fail if an undefined directive is retrieved or set.
|
||||
*
|
||||
* @note Many classes that could (although many times don't) use the
|
||||
* configuration object make it a mandatory parameter. This is
|
||||
* because a configuration object should always be forwarded,
|
||||
* otherwise, you run the risk of missing a parameter and then
|
||||
* being stumped when a configuration directive doesn't work.
|
||||
*
|
||||
* @todo Reconsider some of the public member variables
|
||||
*/
|
||||
class HTMLPurifier_Config
|
||||
{
|
||||
|
||||
/**
|
||||
* HTML Purifier's version
|
||||
*/
|
||||
public $version = '3.0.0';
|
||||
|
||||
/**
|
||||
* Bool indicator whether or not to automatically finalize
|
||||
* the object if a read operation is done
|
||||
*/
|
||||
public $autoFinalize = true;
|
||||
|
||||
// protected member variables
|
||||
|
||||
/**
|
||||
* Namespace indexed array of serials for specific namespaces (see
|
||||
* getSerial() for more info).
|
||||
*/
|
||||
protected $serials = array();
|
||||
|
||||
/**
|
||||
* Serial for entire configuration object
|
||||
*/
|
||||
protected $serial;
|
||||
|
||||
/**
|
||||
* Two-level associative array of configuration directives
|
||||
*/
|
||||
protected $conf;
|
||||
|
||||
/**
|
||||
* Reference HTMLPurifier_ConfigSchema for value checking
|
||||
* @note This is public for introspective purposes. Please don't
|
||||
* abuse!
|
||||
*/
|
||||
public $def;
|
||||
|
||||
/**
|
||||
* Indexed array of definitions
|
||||
*/
|
||||
protected $definitions;
|
||||
|
||||
/**
|
||||
* Bool indicator whether or not config is finalized
|
||||
*/
|
||||
protected $finalized = false;
|
||||
|
||||
/**
|
||||
* @param $definition HTMLPurifier_ConfigSchema that defines what directives
|
||||
* are allowed.
|
||||
*/
|
||||
public function __construct(&$definition) {
|
||||
$this->conf = $definition->defaults; // set up, copy in defaults
|
||||
$this->def = $definition; // keep a copy around for checking
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor that creates a config object based on a mixed var
|
||||
* @param mixed $config Variable that defines the state of the config
|
||||
* object. Can be: a HTMLPurifier_Config() object,
|
||||
* an array of directives based on loadArray(),
|
||||
* or a string filename of an ini file.
|
||||
* @return Configured HTMLPurifier_Config object
|
||||
*/
|
||||
public static function create($config) {
|
||||
if ($config instanceof HTMLPurifier_Config) {
|
||||
// pass-through
|
||||
return $config;
|
||||
}
|
||||
$ret = HTMLPurifier_Config::createDefault();
|
||||
if (is_string($config)) $ret->loadIni($config);
|
||||
elseif (is_array($config)) $ret->loadArray($config);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor that creates a default configuration object.
|
||||
* @return Default HTMLPurifier_Config object.
|
||||
*/
|
||||
public static function createDefault() {
|
||||
$definition =& HTMLPurifier_ConfigSchema::instance();
|
||||
$config = new HTMLPurifier_Config($definition);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives a value from the configuration.
|
||||
* @param $namespace String namespace
|
||||
* @param $key String key
|
||||
*/
|
||||
public function get($namespace, $key) {
|
||||
if (!$this->finalized && $this->autoFinalize) $this->finalize();
|
||||
if (!isset($this->def->info[$namespace][$key])) {
|
||||
// can't add % due to SimpleTest bug
|
||||
trigger_error('Cannot retrieve value of undefined directive ' . htmlspecialchars("$namespace.$key"),
|
||||
E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
if ($this->def->info[$namespace][$key]->class == 'alias') {
|
||||
$d = $this->def->info[$namespace][$key];
|
||||
trigger_error('Cannot get value from aliased directive, use real name ' . $d->namespace . '.' . $d->name,
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
return $this->conf[$namespace][$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreives an array of directives to values from a given namespace
|
||||
* @param $namespace String namespace
|
||||
*/
|
||||
public function getBatch($namespace) {
|
||||
if (!$this->finalized && $this->autoFinalize) $this->finalize();
|
||||
if (!isset($this->def->info[$namespace])) {
|
||||
trigger_error('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
|
||||
E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
return $this->conf[$namespace];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a md5 signature of a segment of the configuration object
|
||||
* that uniquely identifies that particular configuration
|
||||
* @note Revision is handled specially and is removed from the batch
|
||||
* before processing!
|
||||
* @param $namespace Namespace to get serial for
|
||||
*/
|
||||
public function getBatchSerial($namespace) {
|
||||
if (empty($this->serials[$namespace])) {
|
||||
$batch = $this->getBatch($namespace);
|
||||
unset($batch['DefinitionRev']);
|
||||
$this->serials[$namespace] = md5(serialize($batch));
|
||||
}
|
||||
return $this->serials[$namespace];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a md5 signature for the entire configuration object
|
||||
* that uniquely identifies that particular configuration
|
||||
*/
|
||||
public function getSerial() {
|
||||
if (empty($this->serial)) {
|
||||
$this->serial = md5(serialize($this->getAll()));
|
||||
}
|
||||
return $this->serial;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all directives, organized by namespace
|
||||
*/
|
||||
public function getAll() {
|
||||
if (!$this->finalized && $this->autoFinalize) $this->finalize();
|
||||
return $this->conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a value to configuration.
|
||||
* @param $namespace String namespace
|
||||
* @param $key String key
|
||||
* @param $value Mixed value
|
||||
*/
|
||||
public function set($namespace, $key, $value, $from_alias = false) {
|
||||
if ($this->isFinalized('Cannot set directive after finalization')) return;
|
||||
if (!isset($this->def->info[$namespace][$key])) {
|
||||
trigger_error('Cannot set undefined directive ' . htmlspecialchars("$namespace.$key") . ' to value',
|
||||
E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
if ($this->def->info[$namespace][$key]->class == 'alias') {
|
||||
if ($from_alias) {
|
||||
trigger_error('Double-aliases not allowed, please fix '.
|
||||
'ConfigSchema bug with' . "$namespace.$key");
|
||||
}
|
||||
$this->set($this->def->info[$namespace][$key]->namespace,
|
||||
$this->def->info[$namespace][$key]->name,
|
||||
$value, true);
|
||||
return;
|
||||
}
|
||||
$value = $this->def->validate(
|
||||
$value,
|
||||
$type = $this->def->info[$namespace][$key]->type,
|
||||
$this->def->info[$namespace][$key]->allow_null
|
||||
);
|
||||
if (is_string($value)) {
|
||||
// resolve value alias if defined
|
||||
if (isset($this->def->info[$namespace][$key]->aliases[$value])) {
|
||||
$value = $this->def->info[$namespace][$key]->aliases[$value];
|
||||
}
|
||||
if ($this->def->info[$namespace][$key]->allowed !== true) {
|
||||
// check to see if the value is allowed
|
||||
if (!isset($this->def->info[$namespace][$key]->allowed[$value])) {
|
||||
trigger_error('Value not supported, valid values are: ' .
|
||||
$this->_listify($this->def->info[$namespace][$key]->allowed), E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->def->isError($value)) {
|
||||
trigger_error('Value for ' . "$namespace.$key" . ' is of invalid type, should be ' . $type, E_USER_WARNING);
|
||||
return;
|
||||
}
|
||||
$this->conf[$namespace][$key] = $value;
|
||||
|
||||
// reset definitions if the directives they depend on changed
|
||||
// this is a very costly process, so it's discouraged
|
||||
// with finalization
|
||||
if ($namespace == 'HTML' || $namespace == 'CSS') {
|
||||
$this->definitions[$namespace] = null;
|
||||
}
|
||||
|
||||
$this->serials[$namespace] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for error reporting
|
||||
*/
|
||||
private function _listify($lookup) {
|
||||
$list = array();
|
||||
foreach ($lookup as $name => $b) $list[] = $name;
|
||||
return implode(', ', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves reference to the HTML definition.
|
||||
* @param $raw Return a copy that has not been setup yet. Must be
|
||||
* called before it's been setup, otherwise won't work.
|
||||
*/
|
||||
public function &getHTMLDefinition($raw = false) {
|
||||
$def =& $this->getDefinition('HTML', $raw);
|
||||
return $def; // prevent PHP 4.4.0 from complaining
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves reference to the CSS definition
|
||||
*/
|
||||
public function &getCSSDefinition($raw = false) {
|
||||
$def =& $this->getDefinition('CSS', $raw);
|
||||
return $def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a definition
|
||||
* @param $type Type of definition: HTML, CSS, etc
|
||||
* @param $raw Whether or not definition should be returned raw
|
||||
*/
|
||||
public function &getDefinition($type, $raw = false) {
|
||||
if (!$this->finalized && $this->autoFinalize) $this->finalize();
|
||||
$factory = HTMLPurifier_DefinitionCacheFactory::instance();
|
||||
$cache = $factory->create($type, $this);
|
||||
if (!$raw) {
|
||||
// see if we can quickly supply a definition
|
||||
if (!empty($this->definitions[$type])) {
|
||||
if (!$this->definitions[$type]->setup) {
|
||||
$this->definitions[$type]->setup($this);
|
||||
$cache->set($this->definitions[$type], $this);
|
||||
}
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
// memory check missed, try cache
|
||||
$this->definitions[$type] = $cache->get($this);
|
||||
if ($this->definitions[$type]) {
|
||||
// definition in cache, return it
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
} elseif (
|
||||
!empty($this->definitions[$type]) &&
|
||||
!$this->definitions[$type]->setup
|
||||
) {
|
||||
// raw requested, raw in memory, quick return
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
// quick checks failed, let's create the object
|
||||
if ($type == 'HTML') {
|
||||
$this->definitions[$type] = new HTMLPurifier_HTMLDefinition();
|
||||
} elseif ($type == 'CSS') {
|
||||
$this->definitions[$type] = new HTMLPurifier_CSSDefinition();
|
||||
} elseif ($type == 'URI') {
|
||||
$this->definitions[$type] = new HTMLPurifier_URIDefinition();
|
||||
} else {
|
||||
trigger_error("Definition of $type type not supported");
|
||||
$false = false;
|
||||
return $false;
|
||||
}
|
||||
// quick abort if raw
|
||||
if ($raw) {
|
||||
if (is_null($this->get($type, 'DefinitionID'))) {
|
||||
// fatally error out if definition ID not set
|
||||
trigger_error("Cannot retrieve raw version without specifying %$type.DefinitionID", E_USER_ERROR);
|
||||
$false = new HTMLPurifier_Error();
|
||||
return $false;
|
||||
}
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
// set it up
|
||||
$this->definitions[$type]->setup($this);
|
||||
// save in cache
|
||||
$cache->set($this->definitions[$type], $this);
|
||||
return $this->definitions[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration values from an array with the following structure:
|
||||
* Namespace.Directive => Value
|
||||
* @param $config_array Configuration associative array
|
||||
*/
|
||||
public function loadArray($config_array) {
|
||||
if ($this->isFinalized('Cannot load directives after finalization')) return;
|
||||
foreach ($config_array as $key => $value) {
|
||||
$key = str_replace('_', '.', $key);
|
||||
if (strpos($key, '.') !== false) {
|
||||
// condensed form
|
||||
list($namespace, $directive) = explode('.', $key);
|
||||
$this->set($namespace, $directive, $value);
|
||||
} else {
|
||||
$namespace = $key;
|
||||
$namespace_values = $value;
|
||||
foreach ($namespace_values as $directive => $value) {
|
||||
$this->set($namespace, $directive, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of array(namespace, directive) for all directives
|
||||
* that are allowed in a web-form context as per an allowed
|
||||
* namespaces/directives list.
|
||||
* @param $allowed List of allowed namespaces/directives
|
||||
*/
|
||||
public static function getAllowedDirectivesForForm($allowed) {
|
||||
$schema = HTMLPurifier_ConfigSchema::instance();
|
||||
if ($allowed !== true) {
|
||||
if (is_string($allowed)) $allowed = array($allowed);
|
||||
$allowed_ns = array();
|
||||
$allowed_directives = array();
|
||||
$blacklisted_directives = array();
|
||||
foreach ($allowed as $ns_or_directive) {
|
||||
if (strpos($ns_or_directive, '.') !== false) {
|
||||
// directive
|
||||
if ($ns_or_directive[0] == '-') {
|
||||
$blacklisted_directives[substr($ns_or_directive, 1)] = true;
|
||||
} else {
|
||||
$allowed_directives[$ns_or_directive] = true;
|
||||
}
|
||||
} else {
|
||||
// namespace
|
||||
$allowed_ns[$ns_or_directive] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$ret = array();
|
||||
foreach ($schema->info as $ns => $keypairs) {
|
||||
foreach ($keypairs as $directive => $def) {
|
||||
if ($allowed !== true) {
|
||||
if (isset($blacklisted_directives["$ns.$directive"])) continue;
|
||||
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
|
||||
}
|
||||
if ($def->class == 'alias') continue;
|
||||
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
|
||||
$ret[] = array($ns, $directive);
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration values from $_GET/$_POST that were posted
|
||||
* via ConfigForm
|
||||
* @param $array $_GET or $_POST array to import
|
||||
* @param $index Index/name that the config variables are in
|
||||
* @param $allowed List of allowed namespaces/directives
|
||||
* @param $mq_fix Boolean whether or not to enable magic quotes fix
|
||||
*/
|
||||
public static function loadArrayFromForm($array, $index, $allowed = true, $mq_fix = true) {
|
||||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix);
|
||||
$config = HTMLPurifier_Config::create($ret);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
|
||||
* @note Same parameters as loadArrayFromForm
|
||||
*/
|
||||
public function mergeArrayFromForm($array, $index, $allowed = true, $mq_fix = true) {
|
||||
$ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix);
|
||||
$this->loadArray($ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an array from a form into something usable for the more
|
||||
* strict parts of HTMLPurifier_Config
|
||||
*/
|
||||
public static function prepareArrayFromForm($array, $index, $allowed = true, $mq_fix = true) {
|
||||
$array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
|
||||
$mq = get_magic_quotes_gpc() && $mq_fix;
|
||||
|
||||
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed);
|
||||
$ret = array();
|
||||
foreach ($allowed as $key) {
|
||||
list($ns, $directive) = $key;
|
||||
$skey = "$ns.$directive";
|
||||
if (!empty($array["Null_$skey"])) {
|
||||
$ret[$ns][$directive] = null;
|
||||
continue;
|
||||
}
|
||||
if (!isset($array[$skey])) continue;
|
||||
$value = $mq ? stripslashes($array[$skey]) : $array[$skey];
|
||||
$ret[$ns][$directive] = $value;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration values from an ini file
|
||||
* @param $filename Name of ini file
|
||||
*/
|
||||
public function loadIni($filename) {
|
||||
if ($this->isFinalized('Cannot load directives after finalization')) return;
|
||||
$array = parse_ini_file($filename, true);
|
||||
$this->loadArray($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the configuration object is finalized.
|
||||
* @param $error String error message, or false for no error
|
||||
*/
|
||||
public function isFinalized($error = false) {
|
||||
if ($this->finalized && $error) {
|
||||
trigger_error($error, E_USER_ERROR);
|
||||
}
|
||||
return $this->finalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes configuration only if auto finalize is on and not
|
||||
* already finalized
|
||||
*/
|
||||
public function autoFinalize() {
|
||||
if (!$this->finalized && $this->autoFinalize) $this->finalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes a configuration object, prohibiting further change
|
||||
*/
|
||||
public function finalize() {
|
||||
$this->finalized = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Base class for configuration entity
|
||||
*/
|
||||
class HTMLPurifier_ConfigDef {
|
||||
public $class = false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ConfigDef.php';
|
||||
|
||||
/**
|
||||
* Structure object containing definition of a directive.
|
||||
* @note This structure does not contain default values
|
||||
*/
|
||||
class HTMLPurifier_ConfigDef_Directive extends HTMLPurifier_ConfigDef
|
||||
{
|
||||
|
||||
public $class = 'directive';
|
||||
|
||||
public function __construct(
|
||||
$type = null,
|
||||
$descriptions = null,
|
||||
$allow_null = null,
|
||||
$allowed = null,
|
||||
$aliases = null
|
||||
) {
|
||||
if ( $type !== null) $this->type = $type;
|
||||
if ($descriptions !== null) $this->descriptions = $descriptions;
|
||||
if ( $allow_null !== null) $this->allow_null = $allow_null;
|
||||
if ( $allowed !== null) $this->allowed = $allowed;
|
||||
if ( $aliases !== null) $this->aliases = $aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed type of the directive. Values are:
|
||||
* - string
|
||||
* - istring (case insensitive string)
|
||||
* - int
|
||||
* - float
|
||||
* - bool
|
||||
* - lookup (array of value => true)
|
||||
* - list (regular numbered index array)
|
||||
* - hash (array of key => value)
|
||||
* - mixed (anything goes)
|
||||
*/
|
||||
public $type = 'mixed';
|
||||
|
||||
/**
|
||||
* Plaintext descriptions of the configuration entity is. Organized by
|
||||
* file and line number, so multiple descriptions are allowed.
|
||||
*/
|
||||
public $descriptions = array();
|
||||
|
||||
/**
|
||||
* Is null allowed? Has no effect for mixed type.
|
||||
* @bool
|
||||
*/
|
||||
public $allow_null = false;
|
||||
|
||||
/**
|
||||
* Lookup table of allowed values of the element, bool true if all allowed.
|
||||
*/
|
||||
public $allowed = true;
|
||||
|
||||
/**
|
||||
* Hash of value aliases, i.e. values that are equivalent.
|
||||
*/
|
||||
public $aliases = array();
|
||||
|
||||
/**
|
||||
* Advisory list of directive aliases, i.e. other directives that
|
||||
* redirect here
|
||||
*/
|
||||
public $directiveAliases = array();
|
||||
|
||||
/**
|
||||
* Adds a description to the array
|
||||
*/
|
||||
public function addDescription($file, $line, $description) {
|
||||
if (!isset($this->descriptions[$file])) $this->descriptions[$file] = array();
|
||||
$this->descriptions[$file][$line] = $description;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ConfigDef.php';
|
||||
|
||||
/**
|
||||
* Structure object describing a directive alias
|
||||
*/
|
||||
class HTMLPurifier_ConfigDef_DirectiveAlias extends HTMLPurifier_ConfigDef
|
||||
{
|
||||
public $class = 'alias';
|
||||
|
||||
/**
|
||||
* Namespace being aliased to
|
||||
*/
|
||||
public $namespace;
|
||||
/**
|
||||
* Directive being aliased to
|
||||
*/
|
||||
public $name;
|
||||
|
||||
public function __construct($namespace, $name) {
|
||||
$this->namespace = $namespace;
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/ConfigDef.php';
|
||||
|
||||
/**
|
||||
* Structure object describing of a namespace
|
||||
*/
|
||||
class HTMLPurifier_ConfigDef_Namespace extends HTMLPurifier_ConfigDef {
|
||||
|
||||
public function HTMLPurifier_ConfigDef_Namespace($description = null) {
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public $class = 'namespace';
|
||||
|
||||
/**
|
||||
* String description of what kinds of directives go in this namespace.
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Error.php';
|
||||
require_once 'HTMLPurifier/ConfigDef.php';
|
||||
require_once 'HTMLPurifier/ConfigDef/Namespace.php';
|
||||
require_once 'HTMLPurifier/ConfigDef/Directive.php';
|
||||
require_once 'HTMLPurifier/ConfigDef/DirectiveAlias.php';
|
||||
|
||||
if (!defined('HTMLPURIFIER_SCHEMA_STRICT')) define('HTMLPURIFIER_SCHEMA_STRICT', false);
|
||||
|
||||
/**
|
||||
* Configuration definition, defines directives and their defaults.
|
||||
* @note If you update this, please update Printer_ConfigForm
|
||||
* @todo The ability to define things multiple times is confusing and should
|
||||
* be factored out to its own function named registerDependency() or
|
||||
* addNote(), where only the namespace.name and an extra descriptions
|
||||
* documenting the nature of the dependency are needed. Since it's
|
||||
* possible that the dependency is registered before the configuration
|
||||
* is defined, deferring it to some sort of cache until it actually
|
||||
* gets defined would be wise, keeping it opaque until it does get
|
||||
* defined. We could add a finalize() method which would cause it to
|
||||
* error out if we get a dangling dependency. It's difficult, however,
|
||||
* to know whether or not it's a dependency, or a codependency, that is
|
||||
* neither of them fully depends on it. Where does the configuration go
|
||||
* then? This could be partially resolved by allowing blanket definitions
|
||||
* and then splitting them up into finer-grained versions, however, there
|
||||
* might be implementation difficulties in ini files regarding order of
|
||||
* execution.
|
||||
*/
|
||||
class HTMLPurifier_ConfigSchema {
|
||||
|
||||
/**
|
||||
* Defaults of the directives and namespaces.
|
||||
* @note This shares the exact same structure as HTMLPurifier_Config::$conf
|
||||
*/
|
||||
public $defaults = array();
|
||||
|
||||
/**
|
||||
* Definition of the directives.
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Definition of namespaces.
|
||||
*/
|
||||
public $info_namespace = array();
|
||||
|
||||
/**
|
||||
* Lookup table of allowed types.
|
||||
*/
|
||||
public $types = array(
|
||||
'string' => 'String',
|
||||
'istring' => 'Case-insensitive string',
|
||||
'text' => 'Text',
|
||||
'itext' => 'Case-insensitive text',
|
||||
'int' => 'Integer',
|
||||
'float' => 'Float',
|
||||
'bool' => 'Boolean',
|
||||
'lookup' => 'Lookup array',
|
||||
'list' => 'Array list',
|
||||
'hash' => 'Associative array',
|
||||
'mixed' => 'Mixed'
|
||||
);
|
||||
|
||||
/**
|
||||
* Initializes the default namespaces.
|
||||
*/
|
||||
public function initialize() {
|
||||
$this->defineNamespace('Core', 'Core features that are always available.');
|
||||
$this->defineNamespace('Attr', 'Features regarding attribute validation.');
|
||||
$this->defineNamespace('URI', 'Features regarding Uniform Resource Identifiers.');
|
||||
$this->defineNamespace('HTML', 'Configuration regarding allowed HTML.');
|
||||
$this->defineNamespace('CSS', 'Configuration regarding allowed CSS.');
|
||||
$this->defineNamespace('AutoFormat', 'Configuration for activating auto-formatting functionality (also known as <code>Injector</code>s)');
|
||||
$this->defineNamespace('AutoFormatParam', 'Configuration for customizing auto-formatting functionality');
|
||||
$this->defineNamespace('Filter', 'Configuration for filters');
|
||||
$this->defineNamespace('Output', 'Configuration relating to the generation of (X)HTML.');
|
||||
$this->defineNamespace('Cache', 'Configuration for DefinitionCache and related subclasses.');
|
||||
$this->defineNamespace('Test', 'Developer testing configuration for our unit tests.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance of the application-wide configuration definition.
|
||||
*/
|
||||
public static function &instance($prototype = null) {
|
||||
static $instance;
|
||||
if ($prototype !== null) {
|
||||
$instance = $prototype;
|
||||
} elseif ($instance === null || $prototype === true) {
|
||||
$instance = new HTMLPurifier_ConfigSchema();
|
||||
$instance->initialize();
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a directive for configuration
|
||||
* @warning Will fail of directive's namespace is defined
|
||||
* @param $namespace Namespace the directive is in
|
||||
* @param $name Key of directive
|
||||
* @param $default Default value of directive
|
||||
* @param $type Allowed type of the directive. See
|
||||
* HTMLPurifier_DirectiveDef::$type for allowed values
|
||||
* @param $description Description of directive for documentation
|
||||
*/
|
||||
public static function define($namespace, $name, $default, $type, $description) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
|
||||
// basic sanity checks
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot define directive for undefined namespace',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($name)) {
|
||||
trigger_error('Directive name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (empty($description)) {
|
||||
trigger_error('Description must be non-empty',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($def->info[$namespace][$name])) {
|
||||
// already defined
|
||||
if (
|
||||
$def->info[$namespace][$name]->type !== $type ||
|
||||
$def->defaults[$namespace][$name] !== $default
|
||||
) {
|
||||
trigger_error('Inconsistent default or type, cannot redefine');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// needs defining
|
||||
|
||||
// process modifiers (OPTIMIZE!)
|
||||
$type_values = explode('/', $type, 2);
|
||||
$type = $type_values[0];
|
||||
$modifier = isset($type_values[1]) ? $type_values[1] : false;
|
||||
$allow_null = ($modifier === 'null');
|
||||
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!isset($def->types[$type])) {
|
||||
trigger_error('Invalid type for configuration directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$default = $def->validate($default, $type, $allow_null);
|
||||
if ($def->isError($default)) {
|
||||
trigger_error('Default value does not match directive type',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$def->info[$namespace][$name] =
|
||||
new HTMLPurifier_ConfigDef_Directive();
|
||||
$def->info[$namespace][$name]->type = $type;
|
||||
$def->info[$namespace][$name]->allow_null = $allow_null;
|
||||
$def->defaults[$namespace][$name] = $default;
|
||||
}
|
||||
if (!HTMLPURIFIER_SCHEMA_STRICT) return;
|
||||
$backtrace = debug_backtrace();
|
||||
$file = $def->mungeFilename($backtrace[0]['file']);
|
||||
$line = $backtrace[0]['line'];
|
||||
$def->info[$namespace][$name]->addDescription($file,$line,$description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a namespace for directives to be put into.
|
||||
* @param $namespace Namespace's name
|
||||
* @param $description Description of the namespace
|
||||
*/
|
||||
public static function defineNamespace($namespace, $description) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot redefine namespace', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($namespace)) {
|
||||
trigger_error('Namespace name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (empty($description)) {
|
||||
trigger_error('Description must be non-empty',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$def->info[$namespace] = array();
|
||||
$def->info_namespace[$namespace] = new HTMLPurifier_ConfigDef_Namespace();
|
||||
$def->info_namespace[$namespace]->description = $description;
|
||||
$def->defaults[$namespace] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a directive value alias.
|
||||
*
|
||||
* Directive value aliases are convenient for developers because it lets
|
||||
* them set a directive to several values and get the same result.
|
||||
* @param $namespace Directive's namespace
|
||||
* @param $name Name of Directive
|
||||
* @param $alias Name of aliased value
|
||||
* @param $real Value aliased value will be converted into
|
||||
*/
|
||||
public static function defineValueAliases($namespace, $name, $aliases) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT && !isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot set value alias for non-existant directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
foreach ($aliases as $alias => $real) {
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!$def->info[$namespace][$name] !== true &&
|
||||
!isset($def->info[$namespace][$name]->allowed[$real])
|
||||
) {
|
||||
trigger_error('Cannot define alias to value that is not allowed',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (isset($def->info[$namespace][$name]->allowed[$alias])) {
|
||||
trigger_error('Cannot define alias over allowed value',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$def->info[$namespace][$name]->aliases[$alias] = $real;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a set of allowed values for a directive.
|
||||
* @param $namespace Namespace of directive
|
||||
* @param $name Name of directive
|
||||
* @param $allowed_values Arraylist of allowed values
|
||||
*/
|
||||
public static function defineAllowedValues($namespace, $name, $allowed_values) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT && !isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot define allowed values for undefined directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$directive =& $def->info[$namespace][$name];
|
||||
$type = $directive->type;
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT && $type != 'string' && $type != 'istring') {
|
||||
trigger_error('Cannot define allowed values for directive whose type is not string',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if ($directive->allowed === true) {
|
||||
$directive->allowed = array();
|
||||
}
|
||||
foreach ($allowed_values as $value) {
|
||||
$directive->allowed[$value] = true;
|
||||
}
|
||||
if (
|
||||
HTMLPURIFIER_SCHEMA_STRICT &&
|
||||
$def->defaults[$namespace][$name] !== null &&
|
||||
!isset($directive->allowed[$def->defaults[$namespace][$name]])
|
||||
) {
|
||||
trigger_error('Default value must be in allowed range of variables',
|
||||
E_USER_ERROR);
|
||||
$directive->allowed = true; // undo undo!
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a directive alias for backwards compatibility
|
||||
* @param $namespace
|
||||
* @param $name Directive that will be aliased
|
||||
* @param $new_namespace
|
||||
* @param $new_name Directive that the alias will be to
|
||||
*/
|
||||
public static function defineAlias($namespace, $name, $new_namespace, $new_name) {
|
||||
$def =& HTMLPurifier_ConfigSchema::instance();
|
||||
if (HTMLPURIFIER_SCHEMA_STRICT) {
|
||||
if (!isset($def->info[$namespace])) {
|
||||
trigger_error('Cannot define directive alias in undefined namespace',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!ctype_alnum($name)) {
|
||||
trigger_error('Directive name must be alphanumeric',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (isset($def->info[$namespace][$name])) {
|
||||
trigger_error('Cannot define alias over directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!isset($def->info[$new_namespace][$new_name])) {
|
||||
trigger_error('Cannot define alias to undefined directive',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if ($def->info[$new_namespace][$new_name]->class == 'alias') {
|
||||
trigger_error('Cannot define alias to alias',
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$def->info[$namespace][$name] =
|
||||
new HTMLPurifier_ConfigDef_DirectiveAlias(
|
||||
$new_namespace, $new_name);
|
||||
$def->info[$new_namespace][$new_name]->directiveAliases[] = "$namespace.$name";
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a variable according to type. Return null if invalid.
|
||||
* @todo Consider making protected
|
||||
*/
|
||||
public function validate($var, $type, $allow_null = false) {
|
||||
if (!isset($this->types[$type])) {
|
||||
trigger_error('Invalid type', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
if ($allow_null && $var === null) return null;
|
||||
switch ($type) {
|
||||
case 'mixed':
|
||||
//if (is_string($var)) $var = unserialize($var);
|
||||
return $var;
|
||||
case 'istring':
|
||||
case 'string':
|
||||
case 'text': // no difference, just is longer/multiple line string
|
||||
case 'itext':
|
||||
if (!is_string($var)) break;
|
||||
if ($type === 'istring' || $type === 'itext') $var = strtolower($var);
|
||||
return $var;
|
||||
case 'int':
|
||||
if (is_string($var) && ctype_digit($var)) $var = (int) $var;
|
||||
elseif (!is_int($var)) break;
|
||||
return $var;
|
||||
case 'float':
|
||||
if (is_string($var) && is_numeric($var)) $var = (float) $var;
|
||||
elseif (!is_float($var)) break;
|
||||
return $var;
|
||||
case 'bool':
|
||||
if (is_int($var) && ($var === 0 || $var === 1)) {
|
||||
$var = (bool) $var;
|
||||
} elseif (is_string($var)) {
|
||||
if ($var == 'on' || $var == 'true' || $var == '1') {
|
||||
$var = true;
|
||||
} elseif ($var == 'off' || $var == 'false' || $var == '0') {
|
||||
$var = false;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} elseif (!is_bool($var)) break;
|
||||
return $var;
|
||||
case 'list':
|
||||
case 'hash':
|
||||
case 'lookup':
|
||||
if (is_string($var)) {
|
||||
// special case: technically, this is an array with
|
||||
// a single empty string item, but having an empty
|
||||
// array is more intuitive
|
||||
if ($var == '') return array();
|
||||
if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
|
||||
// simplistic string to array method that only works
|
||||
// for simple lists of tag names or alphanumeric characters
|
||||
$var = explode(',',$var);
|
||||
} else {
|
||||
$var = preg_split('/(,|[\n\r]+)/', $var);
|
||||
}
|
||||
// remove spaces
|
||||
foreach ($var as $i => $j) $var[$i] = trim($j);
|
||||
if ($type === 'hash') {
|
||||
// key:value,key2:value2
|
||||
$nvar = array();
|
||||
foreach ($var as $keypair) {
|
||||
$c = explode(':', $keypair, 2);
|
||||
if (!isset($c[1])) continue;
|
||||
$nvar[$c[0]] = $c[1];
|
||||
}
|
||||
$var = $nvar;
|
||||
}
|
||||
}
|
||||
if (!is_array($var)) break;
|
||||
$keys = array_keys($var);
|
||||
if ($keys === array_keys($keys)) {
|
||||
if ($type == 'list') return $var;
|
||||
elseif ($type == 'lookup') {
|
||||
$new = array();
|
||||
foreach ($var as $key) {
|
||||
$new[$key] = true;
|
||||
}
|
||||
return $new;
|
||||
} else break;
|
||||
}
|
||||
if ($type === 'lookup') {
|
||||
foreach ($var as $key => $value) {
|
||||
$var[$key] = true;
|
||||
}
|
||||
}
|
||||
return $var;
|
||||
}
|
||||
$error = new HTMLPurifier_Error();
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an absolute path and munges it into a more manageable relative path
|
||||
* @todo Consider making protected
|
||||
* @param $filename Filename to check
|
||||
* @return string munged filename
|
||||
*/
|
||||
public function mungeFilename($filename) {
|
||||
if (!HTMLPURIFIER_SCHEMA_STRICT) return $filename;
|
||||
$offset = strrpos($filename, 'HTMLPurifier');
|
||||
$filename = substr($filename, $offset);
|
||||
$filename = str_replace('\\', '/', $filename);
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if var is an HTMLPurifier_Error object
|
||||
* @todo Consider making protected
|
||||
*/
|
||||
public function isError($var) {
|
||||
if (!is_object($var)) return false;
|
||||
if (!($var instanceof HTMLPurifier_Error)) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
// common defs that we'll support by default
|
||||
require_once 'HTMLPurifier/ChildDef.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Empty.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Required.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Optional.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Custom.php';
|
||||
|
||||
/**
|
||||
* @todo Unit test
|
||||
*/
|
||||
class HTMLPurifier_ContentSets
|
||||
{
|
||||
|
||||
/**
|
||||
* List of content set strings (pipe seperators) indexed by name.
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* List of content set lookups (element => true) indexed by name.
|
||||
* @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
|
||||
*/
|
||||
public $lookup = array();
|
||||
|
||||
/**
|
||||
* Synchronized list of defined content sets (keys of info)
|
||||
*/
|
||||
protected $keys = array();
|
||||
/**
|
||||
* Synchronized list of defined content values (values of info)
|
||||
*/
|
||||
protected $values = array();
|
||||
|
||||
/**
|
||||
* Merges in module's content sets, expands identifiers in the content
|
||||
* sets and populates the keys, values and lookup member variables.
|
||||
* @param $modules List of HTMLPurifier_HTMLModule
|
||||
*/
|
||||
public function __construct($modules) {
|
||||
if (!is_array($modules)) $modules = array($modules);
|
||||
// populate content_sets based on module hints
|
||||
// sorry, no way of overloading
|
||||
foreach ($modules as $module_i => $module) {
|
||||
foreach ($module->content_sets as $key => $value) {
|
||||
if (isset($this->info[$key])) {
|
||||
// add it into the existing content set
|
||||
$this->info[$key] = $this->info[$key] . ' | ' . $value;
|
||||
} else {
|
||||
$this->info[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// perform content_set expansions
|
||||
$this->keys = array_keys($this->info);
|
||||
foreach ($this->info as $i => $set) {
|
||||
// only performed once, so infinite recursion is not
|
||||
// a problem
|
||||
$this->info[$i] =
|
||||
str_replace(
|
||||
$this->keys,
|
||||
// must be recalculated each time due to
|
||||
// changing substitutions
|
||||
array_values($this->info),
|
||||
$set);
|
||||
}
|
||||
$this->values = array_values($this->info);
|
||||
|
||||
// generate lookup tables
|
||||
foreach ($this->info as $name => $set) {
|
||||
$this->lookup[$name] = $this->convertToLookup($set);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a definition; generates and assigns a ChildDef for it
|
||||
* @param $def HTMLPurifier_ElementDef reference
|
||||
* @param $module Module that defined the ElementDef
|
||||
*/
|
||||
public function generateChildDef(&$def, $module) {
|
||||
if (!empty($def->child)) return; // already done!
|
||||
$content_model = $def->content_model;
|
||||
if (is_string($content_model)) {
|
||||
$def->content_model = str_replace(
|
||||
$this->keys, $this->values, $content_model);
|
||||
}
|
||||
$def->child = $this->getChildDef($def, $module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a ChildDef based on content_model and content_model_type
|
||||
* member variables in HTMLPurifier_ElementDef
|
||||
* @note This will also defer to modules for custom HTMLPurifier_ChildDef
|
||||
* subclasses that need content set expansion
|
||||
* @param $def HTMLPurifier_ElementDef to have ChildDef extracted
|
||||
* @return HTMLPurifier_ChildDef corresponding to ElementDef
|
||||
*/
|
||||
public function getChildDef($def, $module) {
|
||||
$value = $def->content_model;
|
||||
if (is_object($value)) {
|
||||
trigger_error(
|
||||
'Literal object child definitions should be stored in '.
|
||||
'ElementDef->child not ElementDef->content_model',
|
||||
E_USER_NOTICE
|
||||
);
|
||||
return $value;
|
||||
}
|
||||
switch ($def->content_model_type) {
|
||||
case 'required':
|
||||
return new HTMLPurifier_ChildDef_Required($value);
|
||||
case 'optional':
|
||||
return new HTMLPurifier_ChildDef_Optional($value);
|
||||
case 'empty':
|
||||
return new HTMLPurifier_ChildDef_Empty();
|
||||
case 'custom':
|
||||
return new HTMLPurifier_ChildDef_Custom($value);
|
||||
}
|
||||
// defer to its module
|
||||
$return = false;
|
||||
if ($module->defines_child_def) { // save a func call
|
||||
$return = $module->getChildDef($def);
|
||||
}
|
||||
if ($return !== false) return $return;
|
||||
// error-out
|
||||
trigger_error(
|
||||
'Could not determine which ChildDef class to instantiate',
|
||||
E_USER_ERROR
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string list of elements separated by pipes into
|
||||
* a lookup array.
|
||||
* @param $string List of elements
|
||||
* @return Lookup array of elements
|
||||
*/
|
||||
protected function convertToLookup($string) {
|
||||
$array = explode('|', str_replace(' ', '', $string));
|
||||
$ret = array();
|
||||
foreach ($array as $i => $k) {
|
||||
$ret[$k] = true;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Registry object that contains information about the current context.
|
||||
* @warning Is a bit buggy when variables are set to null: it thinks
|
||||
* they don't exist! So use false instead, please.
|
||||
*/
|
||||
class HTMLPurifier_Context
|
||||
{
|
||||
|
||||
/**
|
||||
* Private array that stores the references.
|
||||
*/
|
||||
private $_storage = array();
|
||||
|
||||
/**
|
||||
* Registers a variable into the context.
|
||||
* @param $name String name
|
||||
* @param $ref Variable to be registered
|
||||
*/
|
||||
public function register($name, &$ref) {
|
||||
if (isset($this->_storage[$name])) {
|
||||
trigger_error("Name $name produces collision, cannot re-register",
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
$this->_storage[$name] =& $ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a variable reference from the context.
|
||||
* @param $name String name
|
||||
* @param $ignore_error Boolean whether or not to ignore error
|
||||
*/
|
||||
public function &get($name, $ignore_error = false) {
|
||||
if (!isset($this->_storage[$name])) {
|
||||
if (!$ignore_error) {
|
||||
trigger_error("Attempted to retrieve non-existent variable $name",
|
||||
E_USER_ERROR);
|
||||
}
|
||||
$var = null; // so we can return by reference
|
||||
return $var;
|
||||
}
|
||||
return $this->_storage[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Destorys a variable in the context.
|
||||
* @param $name String name
|
||||
*/
|
||||
public function destroy($name) {
|
||||
if (!isset($this->_storage[$name])) {
|
||||
trigger_error("Attempted to destroy non-existent variable $name",
|
||||
E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
unset($this->_storage[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the variable exists.
|
||||
* @param $name String name
|
||||
*/
|
||||
public function exists($name) {
|
||||
return isset($this->_storage[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a series of variables from an associative array
|
||||
* @param $context_array Assoc array of variables to load
|
||||
*/
|
||||
public function loadArray($context_array) {
|
||||
foreach ($context_array as $key => $discard) {
|
||||
$this->register($key, $context_array[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Super-class for definition datatype objects, implements serialization
|
||||
* functions for the class.
|
||||
*/
|
||||
abstract class HTMLPurifier_Definition
|
||||
{
|
||||
|
||||
/**
|
||||
* Has setup() been called yet?
|
||||
*/
|
||||
public $setup = false;
|
||||
|
||||
/**
|
||||
* What type of definition is it?
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* Sets up the definition object into the final form, something
|
||||
* not done by the constructor
|
||||
* @param $config HTMLPurifier_Config instance
|
||||
*/
|
||||
abstract protected function doSetup($config);
|
||||
|
||||
/**
|
||||
* Setup function that aborts if already setup
|
||||
* @param $config HTMLPurifier_Config instance
|
||||
*/
|
||||
public function setup($config) {
|
||||
if ($this->setup) return;
|
||||
$this->setup = true;
|
||||
$this->doSetup($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache/Serializer.php';
|
||||
require_once 'HTMLPurifier/DefinitionCache/Null.php';
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator/Memory.php';
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
|
||||
|
||||
/**
|
||||
* Abstract class representing Definition cache managers that implements
|
||||
* useful common methods and is a factory.
|
||||
* @todo Create a separate maintenance file advanced users can use to
|
||||
* cache their custom HTMLDefinition, which can be loaded
|
||||
* via a configuration directive
|
||||
* @todo Implement memcached
|
||||
*/
|
||||
abstract class HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @param $name Type of definition objects this instance of the
|
||||
* cache will handle.
|
||||
*/
|
||||
public function __construct($type) {
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a unique identifier for a particular configuration
|
||||
* @param Instance of HTMLPurifier_Config
|
||||
*/
|
||||
public function generateKey($config) {
|
||||
return $config->version . '-' . // possibly replace with function calls
|
||||
$config->getBatchSerial($this->type) . '-' .
|
||||
$config->get($this->type, 'DefinitionRev');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether or not a key is old with respect to the configuration's
|
||||
* version and revision number.
|
||||
* @param $key Key to test
|
||||
* @param $config Instance of HTMLPurifier_Config to test against
|
||||
*/
|
||||
public function isOld($key, $config) {
|
||||
if (substr_count($key, '-') < 2) return true;
|
||||
list($version, $hash, $revision) = explode('-', $key, 3);
|
||||
$compare = version_compare($version, $config->version);
|
||||
// version mismatch, is always old
|
||||
if ($compare != 0) return true;
|
||||
// versions match, ids match, check revision number
|
||||
if (
|
||||
$hash == $config->getBatchSerial($this->type) &&
|
||||
$revision < $config->get($this->type, 'DefinitionRev')
|
||||
) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a definition's type jives with the cache's type
|
||||
* @note Throws an error on failure
|
||||
* @param $def Definition object to check
|
||||
* @return Boolean true if good, false if not
|
||||
*/
|
||||
public function checkDefType($def) {
|
||||
if ($def->type !== $this->type) {
|
||||
trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a definition object to the cache
|
||||
*/
|
||||
abstract public function add($def, $config);
|
||||
|
||||
/**
|
||||
* Unconditionally saves a definition object to the cache
|
||||
*/
|
||||
abstract public function set($def, $config);
|
||||
|
||||
/**
|
||||
* Replace an object in the cache
|
||||
*/
|
||||
abstract public function replace($def, $config);
|
||||
|
||||
/**
|
||||
* Retrieves a definition object from the cache
|
||||
*/
|
||||
abstract public function get($config);
|
||||
|
||||
/**
|
||||
* Removes a definition object to the cache
|
||||
*/
|
||||
abstract public function remove($config);
|
||||
|
||||
/**
|
||||
* Clears all objects from cache
|
||||
*/
|
||||
abstract public function flush($config);
|
||||
|
||||
/**
|
||||
* Clears all expired (older version or revision) objects from cache
|
||||
* @note Be carefuly implementing this method as flush. Flush must
|
||||
* not interfere with other Definition types, and cleanup()
|
||||
* should not be repeatedly called by userland code.
|
||||
*/
|
||||
abstract public function cleanup($config);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache.php';
|
||||
|
||||
class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
/**
|
||||
* Cache object we are decorating
|
||||
*/
|
||||
public $cache;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* Lazy decorator function
|
||||
* @param $cache Reference to cache object to decorate
|
||||
*/
|
||||
public function decorate(&$cache) {
|
||||
$decorator = $this->copy();
|
||||
// reference is necessary for mocks in PHP 4
|
||||
$decorator->cache =& $cache;
|
||||
$decorator->type = $cache->type;
|
||||
return $decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-compatible clone substitute
|
||||
*/
|
||||
public function copy() {
|
||||
return new HTMLPurifier_DefinitionCache_Decorator();
|
||||
}
|
||||
|
||||
public function add($def, $config) {
|
||||
return $this->cache->add($def, $config);
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
return $this->cache->set($def, $config);
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
return $this->cache->replace($def, $config);
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
return $this->cache->get($config);
|
||||
}
|
||||
|
||||
public function remove($config) {
|
||||
return $this->cache->remove($config);
|
||||
}
|
||||
|
||||
public function flush($config) {
|
||||
return $this->cache->flush($config);
|
||||
}
|
||||
|
||||
public function cleanup($config) {
|
||||
return $this->cache->cleanup($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
|
||||
|
||||
/**
|
||||
* Definition cache decorator class that cleans up the cache
|
||||
* whenever there is a cache miss.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends
|
||||
HTMLPurifier_DefinitionCache_Decorator
|
||||
{
|
||||
|
||||
public $name = 'Cleanup';
|
||||
|
||||
public function copy() {
|
||||
return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
|
||||
}
|
||||
|
||||
public function add($def, $config) {
|
||||
$status = parent::add($def, $config);
|
||||
if (!$status) parent::cleanup($config);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
$status = parent::set($def, $config);
|
||||
if (!$status) parent::cleanup($config);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
$status = parent::replace($def, $config);
|
||||
if (!$status) parent::cleanup($config);
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
$ret = parent::get($config);
|
||||
if (!$ret) parent::cleanup($config);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
|
||||
|
||||
/**
|
||||
* Definition cache decorator class that saves all cache retrievals
|
||||
* to PHP's memory; good for unit tests or circumstances where
|
||||
* there are lots of configuration objects floating around.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Decorator_Memory extends
|
||||
HTMLPurifier_DefinitionCache_Decorator
|
||||
{
|
||||
|
||||
protected $definitions;
|
||||
public $name = 'Memory';
|
||||
|
||||
public function copy() {
|
||||
return new HTMLPurifier_DefinitionCache_Decorator_Memory();
|
||||
}
|
||||
|
||||
public function add($def, $config) {
|
||||
$status = parent::add($def, $config);
|
||||
if ($status) $this->definitions[$this->generateKey($config)] = $def;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
$status = parent::set($def, $config);
|
||||
if ($status) $this->definitions[$this->generateKey($config)] = $def;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
$status = parent::replace($def, $config);
|
||||
if ($status) $this->definitions[$this->generateKey($config)] = $def;
|
||||
return $status;
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
$key = $this->generateKey($config);
|
||||
if (isset($this->definitions[$key])) return $this->definitions[$key];
|
||||
$this->definitions[$key] = parent::get($config);
|
||||
return $this->definitions[$key];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
|
||||
|
||||
/**
|
||||
* Definition cache decorator template.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Decorator_Template extends
|
||||
HTMLPurifier_DefinitionCache_Decorator
|
||||
{
|
||||
|
||||
var $name = 'Template'; // replace this
|
||||
|
||||
function copy() {
|
||||
// replace class name with yours
|
||||
return new HTMLPurifier_DefinitionCache_Decorator_Template();
|
||||
}
|
||||
|
||||
// remove methods you don't need
|
||||
|
||||
function add($def, $config) {
|
||||
return parent::add($def, $config);
|
||||
}
|
||||
|
||||
function set($def, $config) {
|
||||
return parent::set($def, $config);
|
||||
}
|
||||
|
||||
function replace($def, $config) {
|
||||
return parent::replace($def, $config);
|
||||
}
|
||||
|
||||
function get($config) {
|
||||
return parent::get($config);
|
||||
}
|
||||
|
||||
function flush() {
|
||||
return parent::flush();
|
||||
}
|
||||
|
||||
function cleanup($config) {
|
||||
return parent::cleanup($config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache.php';
|
||||
|
||||
/**
|
||||
* Null cache object to use when no caching is on.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
public function add($def, $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function remove($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function flush($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function cleanup($config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Cache', 'SerializerPath', null, 'string/null', '
|
||||
<p>
|
||||
Absolute path with no trailing slash to store serialized definitions in.
|
||||
Default is within the
|
||||
HTML Purifier library inside DefinitionCache/Serializer. This
|
||||
path must be writable by the webserver. This directive has been
|
||||
available since 2.0.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
class HTMLPurifier_DefinitionCache_Serializer extends
|
||||
HTMLPurifier_DefinitionCache
|
||||
{
|
||||
|
||||
public function add($def, $config) {
|
||||
if (!$this->checkDefType($def)) return;
|
||||
$file = $this->generateFilePath($config);
|
||||
if (file_exists($file)) return false;
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
return $this->_write($file, serialize($def));
|
||||
}
|
||||
|
||||
public function set($def, $config) {
|
||||
if (!$this->checkDefType($def)) return;
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
return $this->_write($file, serialize($def));
|
||||
}
|
||||
|
||||
public function replace($def, $config) {
|
||||
if (!$this->checkDefType($def)) return;
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!file_exists($file)) return false;
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
return $this->_write($file, serialize($def));
|
||||
}
|
||||
|
||||
public function get($config) {
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!file_exists($file)) return false;
|
||||
return unserialize(file_get_contents($file));
|
||||
}
|
||||
|
||||
public function remove($config) {
|
||||
$file = $this->generateFilePath($config);
|
||||
if (!file_exists($file)) return false;
|
||||
return unlink($file);
|
||||
}
|
||||
|
||||
public function flush($config) {
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
$dir = $this->generateDirectoryPath($config);
|
||||
$dh = opendir($dir);
|
||||
while (false !== ($filename = readdir($dh))) {
|
||||
if (empty($filename)) continue;
|
||||
if ($filename[0] === '.') continue;
|
||||
unlink($dir . '/' . $filename);
|
||||
}
|
||||
}
|
||||
|
||||
public function cleanup($config) {
|
||||
if (!$this->_prepareDir($config)) return false;
|
||||
$dir = $this->generateDirectoryPath($config);
|
||||
$dh = opendir($dir);
|
||||
while (false !== ($filename = readdir($dh))) {
|
||||
if (empty($filename)) continue;
|
||||
if ($filename[0] === '.') continue;
|
||||
$key = substr($filename, 0, strlen($filename) - 4);
|
||||
if ($this->isOld($key, $config)) unlink($dir . '/' . $filename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the file path to the serial file corresponding to
|
||||
* the configuration and definition name
|
||||
* @todo Make protected
|
||||
*/
|
||||
public function generateFilePath($config) {
|
||||
$key = $this->generateKey($config);
|
||||
return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the path to the directory contain this cache's serial files
|
||||
* @note No trailing slash
|
||||
* @todo Make protected
|
||||
*/
|
||||
public function generateDirectoryPath($config) {
|
||||
$base = $this->generateBaseDirectoryPath($config);
|
||||
return $base . '/' . $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates path to base directory that contains all definition type
|
||||
* serials
|
||||
* @todo Make protected
|
||||
*/
|
||||
public function generateBaseDirectoryPath($config) {
|
||||
$base = $config->get('Cache', 'SerializerPath');
|
||||
$base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience wrapper function for file_put_contents
|
||||
* @param $file File name to write to
|
||||
* @param $data Data to write into file
|
||||
* @return Number of bytes written if success, or false if failure.
|
||||
*/
|
||||
private function _write($file, $data) {
|
||||
static $file_put_contents;
|
||||
if ($file_put_contents === null) {
|
||||
$file_put_contents = function_exists('file_put_contents');
|
||||
}
|
||||
if ($file_put_contents) {
|
||||
return file_put_contents($file, $data);
|
||||
}
|
||||
$fh = fopen($file, 'w');
|
||||
if (!$fh) return false;
|
||||
$status = fwrite($fh, $data);
|
||||
fclose($fh);
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the directory that this type stores the serials in
|
||||
* @return True if successful
|
||||
*/
|
||||
private function _prepareDir($config) {
|
||||
$directory = $this->generateDirectoryPath($config);
|
||||
if (!is_dir($directory)) {
|
||||
$base = $this->generateBaseDirectoryPath($config);
|
||||
if (!is_dir($base)) {
|
||||
trigger_error('Base directory '.$base.' does not exist,
|
||||
please create or change using %Cache.SerializerPath',
|
||||
E_USER_ERROR);
|
||||
return false;
|
||||
} elseif (!$this->_testPermissions($base)) {
|
||||
return false;
|
||||
}
|
||||
mkdir($directory);
|
||||
} elseif (!$this->_testPermissions($directory)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests permissions on a directory and throws out friendly
|
||||
* error messages and attempts to chmod it itself if possible
|
||||
*/
|
||||
private function _testPermissions($dir) {
|
||||
// early abort, if it is writable, everything is hunky-dory
|
||||
if (is_writable($dir)) return true;
|
||||
if (!is_dir($dir)) {
|
||||
// generally, you'll want to handle this beforehand
|
||||
// so a more specific error message can be given
|
||||
trigger_error('Directory '.$dir.' does not exist',
|
||||
E_USER_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (function_exists('posix_getuid')) {
|
||||
// POSIX system, we can give more specific advice
|
||||
if (fileowner($dir) === posix_getuid()) {
|
||||
// we can chmod it ourselves
|
||||
chmod($dir, 0755);
|
||||
return true;
|
||||
} elseif (filegroup($dir) === posix_getgid()) {
|
||||
$chmod = '775';
|
||||
} else {
|
||||
// PHP's probably running as nobody, so we'll
|
||||
// need to give global permissions
|
||||
$chmod = '777';
|
||||
}
|
||||
trigger_error('Directory '.$dir.' not writable, '.
|
||||
'please chmod to ' . $chmod,
|
||||
E_USER_ERROR);
|
||||
} else {
|
||||
// generic error message
|
||||
trigger_error('Directory '.$dir.' not writable, '.
|
||||
'please alter file permissions',
|
||||
E_USER_ERROR);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/DefinitionCache.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Cache', 'DefinitionImpl', 'Serializer', 'string/null', '
|
||||
This directive defines which method to use when caching definitions,
|
||||
the complex data-type that makes HTML Purifier tick. Set to null
|
||||
to disable caching (not recommended, as you will see a definite
|
||||
performance degradation). This directive has been available since 2.0.0.
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::defineAlias(
|
||||
'Core', 'DefinitionCache',
|
||||
'Cache', 'DefinitionImpl'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Responsible for creating definition caches.
|
||||
*/
|
||||
class HTMLPurifier_DefinitionCacheFactory
|
||||
{
|
||||
|
||||
protected $caches = array('Serializer' => array());
|
||||
protected $implementations = array();
|
||||
protected $decorators = array();
|
||||
|
||||
/**
|
||||
* Initialize default decorators
|
||||
*/
|
||||
public function setup() {
|
||||
$this->addDecorator('Cleanup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an instance of global definition cache factory.
|
||||
*/
|
||||
public static function &instance($prototype = null) {
|
||||
static $instance;
|
||||
if ($prototype !== null) {
|
||||
$instance = $prototype;
|
||||
} elseif ($instance === null || $prototype === true) {
|
||||
$instance = new HTMLPurifier_DefinitionCacheFactory();
|
||||
$instance->setup();
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new definition cache object
|
||||
* @param $short Short name of cache object, for reference
|
||||
* @param $long Full class name of cache object, for construction
|
||||
*/
|
||||
public function register($short, $long) {
|
||||
$this->implementations[$short] = $long;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method that creates a cache object based on configuration
|
||||
* @param $name Name of definitions handled by cache
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
*/
|
||||
public function &create($type, $config) {
|
||||
$method = $config->get('Cache', 'DefinitionImpl');
|
||||
if ($method === null) {
|
||||
$null = new HTMLPurifier_DefinitionCache_Null($type);
|
||||
return $null;
|
||||
}
|
||||
if (!empty($this->caches[$method][$type])) {
|
||||
return $this->caches[$method][$type];
|
||||
}
|
||||
if (
|
||||
isset($this->implementations[$method]) &&
|
||||
class_exists($class = $this->implementations[$method], false)
|
||||
) {
|
||||
$cache = new $class($type);
|
||||
} else {
|
||||
if ($method != 'Serializer') {
|
||||
trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
|
||||
}
|
||||
$cache = new HTMLPurifier_DefinitionCache_Serializer($type);
|
||||
}
|
||||
foreach ($this->decorators as $decorator) {
|
||||
$new_cache = $decorator->decorate($cache);
|
||||
// prevent infinite recursion in PHP 4
|
||||
unset($cache);
|
||||
$cache = $new_cache;
|
||||
}
|
||||
$this->caches[$method][$type] = $cache;
|
||||
return $this->caches[$method][$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a decorator to add to all new cache objects
|
||||
* @param
|
||||
*/
|
||||
public function addDecorator($decorator) {
|
||||
if (is_string($decorator)) {
|
||||
$class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
|
||||
$decorator = new $class;
|
||||
}
|
||||
$this->decorators[$decorator->name] = $decorator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Represents a document type, contains information on which modules
|
||||
* need to be loaded.
|
||||
* @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
|
||||
* If structure changes, please update that function.
|
||||
*/
|
||||
class HTMLPurifier_Doctype
|
||||
{
|
||||
/**
|
||||
* Full name of doctype
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* List of standard modules (string identifiers or literal objects)
|
||||
* that this doctype uses
|
||||
*/
|
||||
public $modules = array();
|
||||
|
||||
/**
|
||||
* List of modules to use for tidying up code
|
||||
*/
|
||||
public $tidyModules = array();
|
||||
|
||||
/**
|
||||
* Is the language derived from XML (i.e. XHTML)?
|
||||
*/
|
||||
public $xml = true;
|
||||
|
||||
/**
|
||||
* List of aliases for this doctype
|
||||
*/
|
||||
public $aliases = array();
|
||||
|
||||
/**
|
||||
* Public DTD identifier
|
||||
*/
|
||||
public $dtdPublic;
|
||||
|
||||
/**
|
||||
* System DTD identifier
|
||||
*/
|
||||
public $dtdSystem;
|
||||
|
||||
public function __construct($name = null, $xml = true, $modules = array(),
|
||||
$tidyModules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->xml = $xml;
|
||||
$this->modules = $modules;
|
||||
$this->tidyModules = $tidyModules;
|
||||
$this->aliases = $aliases;
|
||||
$this->dtdPublic = $dtd_public;
|
||||
$this->dtdSystem = $dtd_system;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones the doctype, use before resolving modes and the like
|
||||
*/
|
||||
public function copy() {
|
||||
return unserialize(serialize($this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Doctype.php';
|
||||
|
||||
// Legacy directives for doctype specification
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'Strict', false, 'bool',
|
||||
'Determines whether or not to use Transitional (loose) or Strict rulesets. '.
|
||||
'This directive is deprecated in favor of %HTML.Doctype. '.
|
||||
'This directive has been available since 1.3.0.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'XHTML', true, 'bool',
|
||||
'Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. '.
|
||||
'This directive is deprecated in favor of %HTML.Doctype. '.
|
||||
'This directive was available since 1.1.'
|
||||
);
|
||||
HTMLPurifier_ConfigSchema::defineAlias('Core', 'XHTML', 'HTML', 'XHTML');
|
||||
|
||||
class HTMLPurifier_DoctypeRegistry
|
||||
{
|
||||
|
||||
/**
|
||||
* Hash of doctype names to doctype objects
|
||||
*/
|
||||
protected $doctypes;
|
||||
|
||||
/**
|
||||
* Lookup table of aliases to real doctype names
|
||||
*/
|
||||
protected $aliases;
|
||||
|
||||
/**
|
||||
* Registers a doctype to the registry
|
||||
* @note Accepts a fully-formed doctype object, or the
|
||||
* parameters for constructing a doctype object
|
||||
* @param $doctype Name of doctype or literal doctype object
|
||||
* @param $modules Modules doctype will load
|
||||
* @param $modules_for_modes Modules doctype will load for certain modes
|
||||
* @param $aliases Alias names for doctype
|
||||
* @return Reference to registered doctype (usable for further editing)
|
||||
*/
|
||||
public function ®ister($doctype, $xml = true, $modules = array(),
|
||||
$tidy_modules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
|
||||
) {
|
||||
if (!is_array($modules)) $modules = array($modules);
|
||||
if (!is_array($tidy_modules)) $tidy_modules = array($tidy_modules);
|
||||
if (!is_array($aliases)) $aliases = array($aliases);
|
||||
if (!is_object($doctype)) {
|
||||
$doctype = new HTMLPurifier_Doctype(
|
||||
$doctype, $xml, $modules, $tidy_modules, $aliases, $dtd_public, $dtd_system
|
||||
);
|
||||
}
|
||||
$this->doctypes[$doctype->name] =& $doctype;
|
||||
$name = $doctype->name;
|
||||
// hookup aliases
|
||||
foreach ($doctype->aliases as $alias) {
|
||||
if (isset($this->doctypes[$alias])) continue;
|
||||
$this->aliases[$alias] = $name;
|
||||
}
|
||||
// remove old aliases
|
||||
if (isset($this->aliases[$name])) unset($this->aliases[$name]);
|
||||
return $doctype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves reference to a doctype of a certain name
|
||||
* @note This function resolves aliases
|
||||
* @note When possible, use the more fully-featured make()
|
||||
* @param $doctype Name of doctype
|
||||
* @return Reference to doctype object
|
||||
*/
|
||||
public function &get($doctype) {
|
||||
if (isset($this->aliases[$doctype])) $doctype = $this->aliases[$doctype];
|
||||
if (!isset($this->doctypes[$doctype])) {
|
||||
trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
|
||||
$anon = new HTMLPurifier_Doctype($doctype);
|
||||
return $anon;
|
||||
}
|
||||
return $this->doctypes[$doctype];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a doctype based on a configuration object,
|
||||
* will perform initialization on the doctype
|
||||
* @note Use this function to get a copy of doctype that config
|
||||
* can hold on to (this is necessary in order to tell
|
||||
* Generator whether or not the current document is XML
|
||||
* based or not).
|
||||
*/
|
||||
public function make($config) {
|
||||
$original_doctype = $this->get($this->getDoctypeFromConfig($config));
|
||||
$doctype = $original_doctype->copy();
|
||||
return $doctype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the doctype from the configuration object
|
||||
*/
|
||||
public function getDoctypeFromConfig($config) {
|
||||
// recommended test
|
||||
$doctype = $config->get('HTML', 'Doctype');
|
||||
if (!empty($doctype)) return $doctype;
|
||||
$doctype = $config->get('HTML', 'CustomDoctype');
|
||||
if (!empty($doctype)) return $doctype;
|
||||
// backwards-compatibility
|
||||
if ($config->get('HTML', 'XHTML')) {
|
||||
$doctype = 'XHTML 1.0';
|
||||
} else {
|
||||
$doctype = 'HTML 4.01';
|
||||
}
|
||||
if ($config->get('HTML', 'Strict')) {
|
||||
$doctype .= ' Strict';
|
||||
} else {
|
||||
$doctype .= ' Transitional';
|
||||
}
|
||||
return $doctype;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Structure that stores an HTML element definition. Used by
|
||||
* HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
|
||||
* @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
|
||||
* Please update that class too.
|
||||
*/
|
||||
class HTMLPurifier_ElementDef
|
||||
{
|
||||
|
||||
/**
|
||||
* Does the definition work by itself, or is it created solely
|
||||
* for the purpose of merging into another definition?
|
||||
*/
|
||||
public $standalone = true;
|
||||
|
||||
/**
|
||||
* Associative array of attribute name to HTMLPurifier_AttrDef
|
||||
* @note Before being processed by HTMLPurifier_AttrCollections
|
||||
* when modules are finalized during
|
||||
* HTMLPurifier_HTMLDefinition->setup(), this array may also
|
||||
* contain an array at index 0 that indicates which attribute
|
||||
* collections to load into the full array. It may also
|
||||
* contain string indentifiers in lieu of HTMLPurifier_AttrDef,
|
||||
* see HTMLPurifier_AttrTypes on how they are expanded during
|
||||
* HTMLPurifier_HTMLDefinition->setup() processing.
|
||||
*/
|
||||
public $attr = array();
|
||||
|
||||
/**
|
||||
* Indexed list of tag's HTMLPurifier_AttrTransform to be done before validation
|
||||
*/
|
||||
public $attr_transform_pre = array();
|
||||
|
||||
/**
|
||||
* Indexed list of tag's HTMLPurifier_AttrTransform to be done after validation
|
||||
*/
|
||||
public $attr_transform_post = array();
|
||||
|
||||
/**
|
||||
* HTMLPurifier_ChildDef of this tag.
|
||||
*/
|
||||
public $child;
|
||||
|
||||
/**
|
||||
* Abstract string representation of internal ChildDef rules. See
|
||||
* HTMLPurifier_ContentSets for how this is parsed and then transformed
|
||||
* into an HTMLPurifier_ChildDef.
|
||||
* @warning This is a temporary variable that is not available after
|
||||
* being processed by HTMLDefinition
|
||||
*/
|
||||
public $content_model;
|
||||
|
||||
/**
|
||||
* Value of $child->type, used to determine which ChildDef to use,
|
||||
* used in combination with $content_model.
|
||||
* @warning This must be lowercase
|
||||
* @warning This is a temporary variable that is not available after
|
||||
* being processed by HTMLDefinition
|
||||
*/
|
||||
public $content_model_type;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Does the element have a content model (#PCDATA | Inline)*? This
|
||||
* is important for chameleon ins and del processing in
|
||||
* HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
|
||||
* have to worry about this one.
|
||||
*/
|
||||
public $descendants_are_inline = false;
|
||||
|
||||
/**
|
||||
* List of the names of required attributes this element has. Dynamically
|
||||
* populated by HTMLPurifier_HTMLDefinition::getElement
|
||||
*/
|
||||
public $required_attr = array();
|
||||
|
||||
/**
|
||||
* Lookup table of tags excluded from all descendants of this tag.
|
||||
* @note SGML permits exclusions for all descendants, but this is
|
||||
* not possible with DTDs or XML Schemas. W3C has elected to
|
||||
* use complicated compositions of content_models to simulate
|
||||
* exclusion for children, but we go the simpler, SGML-style
|
||||
* route of flat-out exclusions, which correctly apply to
|
||||
* all descendants and not just children. Note that the XHTML
|
||||
* Modularization Abstract Modules are blithely unaware of such
|
||||
* distinctions.
|
||||
*/
|
||||
public $excludes = array();
|
||||
|
||||
/**
|
||||
* Is this element safe for untrusted users to use?
|
||||
*/
|
||||
public $safe;
|
||||
|
||||
/**
|
||||
* Low-level factory constructor for creating new standalone element defs
|
||||
*/
|
||||
public static function create($safe, $content_model, $content_model_type, $attr) {
|
||||
$def = new HTMLPurifier_ElementDef();
|
||||
$def->safe = (bool) $safe;
|
||||
$def->content_model = $content_model;
|
||||
$def->content_model_type = $content_model_type;
|
||||
$def->attr = $attr;
|
||||
return $def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the values of another element definition into this one.
|
||||
* Values from the new element def take precedence if a value is
|
||||
* not mergeable.
|
||||
*/
|
||||
public function mergeIn($def) {
|
||||
|
||||
// later keys takes precedence
|
||||
foreach($def->attr as $k => $v) {
|
||||
if ($k === 0) {
|
||||
// merge in the includes
|
||||
// sorry, no way to override an include
|
||||
foreach ($v as $v2) {
|
||||
$this->attr[0][] = $v2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($v === false) {
|
||||
if (isset($this->attr[$k])) unset($this->attr[$k]);
|
||||
continue;
|
||||
}
|
||||
$this->attr[$k] = $v;
|
||||
}
|
||||
$this->_mergeAssocArray($this->attr_transform_pre, $def->attr_transform_pre);
|
||||
$this->_mergeAssocArray($this->attr_transform_post, $def->attr_transform_post);
|
||||
$this->_mergeAssocArray($this->excludes, $def->excludes);
|
||||
|
||||
if(!empty($def->content_model)) {
|
||||
$this->content_model .= ' | ' . $def->content_model;
|
||||
$this->child = false;
|
||||
}
|
||||
if(!empty($def->content_model_type)) {
|
||||
$this->content_model_type = $def->content_model_type;
|
||||
$this->child = false;
|
||||
}
|
||||
if(!is_null($def->child)) $this->child = $def->child;
|
||||
if($def->descendants_are_inline) $this->descendants_are_inline = $def->descendants_are_inline;
|
||||
if(!is_null($def->safe)) $this->safe = $def->safe;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges one array into another, removes values which equal false
|
||||
* @param $a1 Array by reference that is merged into
|
||||
* @param $a2 Array that merges into $a1
|
||||
*/
|
||||
private function _mergeAssocArray(&$a1, $a2) {
|
||||
foreach ($a2 as $k => $v) {
|
||||
if ($v === false) {
|
||||
if (isset($a1[$k])) unset($a1[$k]);
|
||||
continue;
|
||||
}
|
||||
$a1[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a copy of the element definition
|
||||
*/
|
||||
public function copy() {
|
||||
return unserialize(serialize($this));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'Encoding', 'utf-8', 'istring',
|
||||
'If for some reason you are unable to convert all webpages to UTF-8, '.
|
||||
'you can use this directive as a stop-gap compatibility change to '.
|
||||
'let HTML Purifier deal with non UTF-8 input. This technique has '.
|
||||
'notable deficiencies: absolutely no characters outside of the selected '.
|
||||
'character encoding will be preserved, not even the ones that have '.
|
||||
'been ampersand escaped (this is due to a UTF-8 specific <em>feature</em> '.
|
||||
'that automatically resolves all entities), making it pretty useless '.
|
||||
'for anything except the most I18N-blind applications, although '.
|
||||
'%Core.EscapeNonASCIICharacters offers fixes this trouble with '.
|
||||
'another tradeoff. This directive '.
|
||||
'only accepts ISO-8859-1 if iconv is not enabled.'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Core', 'EscapeNonASCIICharacters', false, 'bool',
|
||||
'This directive overcomes a deficiency in %Core.Encoding by blindly '.
|
||||
'converting all non-ASCII characters into decimal numeric entities before '.
|
||||
'converting it to its native encoding. This means that even '.
|
||||
'characters that can be expressed in the non-UTF-8 encoding will '.
|
||||
'be entity-ized, which can be a real downer for encodings like Big5. '.
|
||||
'It also assumes that the ASCII repetoire is available, although '.
|
||||
'this is the case for almost all encodings. Anyway, use UTF-8! This '.
|
||||
'directive has been available since 1.4.0.'
|
||||
);
|
||||
|
||||
if ( !function_exists('iconv') ) {
|
||||
// only encodings with native PHP support
|
||||
HTMLPurifier_ConfigSchema::defineAllowedValues(
|
||||
'Core', 'Encoding', array(
|
||||
'utf-8',
|
||||
'iso-8859-1'
|
||||
)
|
||||
);
|
||||
HTMLPurifier_ConfigSchema::defineValueAliases(
|
||||
'Core', 'Encoding', array(
|
||||
'iso8859-1' => 'iso-8859-1'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Test', 'ForceNoIconv', false, 'bool',
|
||||
'When set to true, HTMLPurifier_Encoder will act as if iconv does not '.
|
||||
'exist and use only pure PHP implementations.'
|
||||
);
|
||||
|
||||
/**
|
||||
* A UTF-8 specific character encoder that handles cleaning and transforming.
|
||||
* @note All functions in this class should be static.
|
||||
*/
|
||||
class HTMLPurifier_Encoder
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor throws fatal error if you attempt to instantiate class
|
||||
*/
|
||||
private function __construct() {
|
||||
trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans a UTF-8 string for well-formedness and SGML validity
|
||||
*
|
||||
* It will parse according to UTF-8 and return a valid UTF8 string, with
|
||||
* non-SGML codepoints excluded.
|
||||
*
|
||||
* @note Just for reference, the non-SGML code points are 0 to 31 and
|
||||
* 127 to 159, inclusive. However, we allow code points 9, 10
|
||||
* and 13, which are the tab, line feed and carriage return
|
||||
* respectively. 128 and above the code points map to multibyte
|
||||
* UTF-8 representations.
|
||||
*
|
||||
* @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
|
||||
* hsivonen@iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
|
||||
* LGPL license. Notes on what changed are inside, but in general,
|
||||
* the original code transformed UTF-8 text into an array of integer
|
||||
* Unicode codepoints. Understandably, transforming that back to
|
||||
* a string would be somewhat expensive, so the function was modded to
|
||||
* directly operate on the string. However, this discourages code
|
||||
* reuse, and the logic enumerated here would be useful for any
|
||||
* function that needs to be able to understand UTF-8 characters.
|
||||
* As of right now, only smart lossless character encoding converters
|
||||
* would need that, and I'm probably not going to implement them.
|
||||
* Once again, PHP 6 should solve all our problems.
|
||||
*/
|
||||
public static function cleanUTF8($str, $force_php = false) {
|
||||
|
||||
static $non_sgml_chars = array();
|
||||
if (empty($non_sgml_chars)) {
|
||||
for ($i = 0; $i <= 31; $i++) {
|
||||
// non-SGML ASCII chars
|
||||
// save \r, \t and \n
|
||||
if ($i == 9 || $i == 13 || $i == 10) continue;
|
||||
$non_sgml_chars[chr($i)] = '';
|
||||
}
|
||||
for ($i = 127; $i <= 159; $i++) {
|
||||
$non_sgml_chars[HTMLPurifier_Encoder::unichr($i)] = '';
|
||||
}
|
||||
}
|
||||
|
||||
static $iconv = null;
|
||||
if ($iconv === null) $iconv = function_exists('iconv');
|
||||
|
||||
if ($iconv && !$force_php) {
|
||||
// do the shortcut way
|
||||
$str = @iconv('UTF-8', 'UTF-8//IGNORE', $str);
|
||||
return strtr($str, $non_sgml_chars);
|
||||
}
|
||||
|
||||
$mState = 0; // cached expected number of octets after the current octet
|
||||
// until the beginning of the next UTF8 character sequence
|
||||
$mUcs4 = 0; // cached Unicode character
|
||||
$mBytes = 1; // cached expected number of octets in the current sequence
|
||||
|
||||
// original code involved an $out that was an array of Unicode
|
||||
// codepoints. Instead of having to convert back into UTF-8, we've
|
||||
// decided to directly append valid UTF-8 characters onto a string
|
||||
// $out once they're done. $char accumulates raw bytes, while $mUcs4
|
||||
// turns into the Unicode code point, so there's some redundancy.
|
||||
|
||||
$out = '';
|
||||
$char = '';
|
||||
|
||||
$len = strlen($str);
|
||||
for($i = 0; $i < $len; $i++) {
|
||||
$in = ord($str{$i});
|
||||
$char .= $str[$i]; // append byte to char
|
||||
if (0 == $mState) {
|
||||
// When mState is zero we expect either a US-ASCII character
|
||||
// or a multi-octet sequence.
|
||||
if (0 == (0x80 & ($in))) {
|
||||
// US-ASCII, pass straight through.
|
||||
if (($in <= 31 || $in == 127) &&
|
||||
!($in == 9 || $in == 13 || $in == 10) // save \r\t\n
|
||||
) {
|
||||
// control characters, remove
|
||||
} else {
|
||||
$out .= $char;
|
||||
}
|
||||
// reset
|
||||
$char = '';
|
||||
$mBytes = 1;
|
||||
} elseif (0xC0 == (0xE0 & ($in))) {
|
||||
// First octet of 2 octet sequence
|
||||
$mUcs4 = ($in);
|
||||
$mUcs4 = ($mUcs4 & 0x1F) << 6;
|
||||
$mState = 1;
|
||||
$mBytes = 2;
|
||||
} elseif (0xE0 == (0xF0 & ($in))) {
|
||||
// First octet of 3 octet sequence
|
||||
$mUcs4 = ($in);
|
||||
$mUcs4 = ($mUcs4 & 0x0F) << 12;
|
||||
$mState = 2;
|
||||
$mBytes = 3;
|
||||
} elseif (0xF0 == (0xF8 & ($in))) {
|
||||
// First octet of 4 octet sequence
|
||||
$mUcs4 = ($in);
|
||||
$mUcs4 = ($mUcs4 & 0x07) << 18;
|
||||
$mState = 3;
|
||||
$mBytes = 4;
|
||||
} elseif (0xF8 == (0xFC & ($in))) {
|
||||
// First octet of 5 octet sequence.
|
||||
//
|
||||
// This is illegal because the encoded codepoint must be
|
||||
// either:
|
||||
// (a) not the shortest form or
|
||||
// (b) outside the Unicode range of 0-0x10FFFF.
|
||||
// Rather than trying to resynchronize, we will carry on
|
||||
// until the end of the sequence and let the later error
|
||||
// handling code catch it.
|
||||
$mUcs4 = ($in);
|
||||
$mUcs4 = ($mUcs4 & 0x03) << 24;
|
||||
$mState = 4;
|
||||
$mBytes = 5;
|
||||
} elseif (0xFC == (0xFE & ($in))) {
|
||||
// First octet of 6 octet sequence, see comments for 5
|
||||
// octet sequence.
|
||||
$mUcs4 = ($in);
|
||||
$mUcs4 = ($mUcs4 & 1) << 30;
|
||||
$mState = 5;
|
||||
$mBytes = 6;
|
||||
} else {
|
||||
// Current octet is neither in the US-ASCII range nor a
|
||||
// legal first octet of a multi-octet sequence.
|
||||
$mState = 0;
|
||||
$mUcs4 = 0;
|
||||
$mBytes = 1;
|
||||
$char = '';
|
||||
}
|
||||
} else {
|
||||
// When mState is non-zero, we expect a continuation of the
|
||||
// multi-octet sequence
|
||||
if (0x80 == (0xC0 & ($in))) {
|
||||
// Legal continuation.
|
||||
$shift = ($mState - 1) * 6;
|
||||
$tmp = $in;
|
||||
$tmp = ($tmp & 0x0000003F) << $shift;
|
||||
$mUcs4 |= $tmp;
|
||||
|
||||
if (0 == --$mState) {
|
||||
// End of the multi-octet sequence. mUcs4 now contains
|
||||
// the final Unicode codepoint to be output
|
||||
|
||||
// Check for illegal sequences and codepoints.
|
||||
|
||||
// From Unicode 3.1, non-shortest form is illegal
|
||||
if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
|
||||
((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
|
||||
((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
|
||||
(4 < $mBytes) ||
|
||||
// From Unicode 3.2, surrogate characters = illegal
|
||||
(($mUcs4 & 0xFFFFF800) == 0xD800) ||
|
||||
// Codepoints outside the Unicode range are illegal
|
||||
($mUcs4 > 0x10FFFF)
|
||||
) {
|
||||
|
||||
} elseif (0xFEFF != $mUcs4 && // omit BOM
|
||||
!($mUcs4 >= 128 && $mUcs4 <= 159) // omit non-SGML
|
||||
) {
|
||||
$out .= $char;
|
||||
}
|
||||
// initialize UTF8 cache (reset)
|
||||
$mState = 0;
|
||||
$mUcs4 = 0;
|
||||
$mBytes = 1;
|
||||
$char = '';
|
||||
}
|
||||
} else {
|
||||
// ((0xC0 & (*in) != 0x80) && (mState != 0))
|
||||
// Incomplete multi-octet sequence.
|
||||
// used to result in complete fail, but we'll reset
|
||||
$mState = 0;
|
||||
$mUcs4 = 0;
|
||||
$mBytes = 1;
|
||||
$char ='';
|
||||
}
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a Unicode codepoint into its corresponding UTF-8 character.
|
||||
* @note Based on Feyd's function at
|
||||
* <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
|
||||
* which is in public domain.
|
||||
* @note While we're going to do code point parsing anyway, a good
|
||||
* optimization would be to refuse to translate code points that
|
||||
* are non-SGML characters. However, this could lead to duplication.
|
||||
* @note This is very similar to the unichr function in
|
||||
* maintenance/generate-entity-file.php (although this is superior,
|
||||
* due to its sanity checks).
|
||||
*/
|
||||
|
||||
// +----------+----------+----------+----------+
|
||||
// | 33222222 | 22221111 | 111111 | |
|
||||
// | 10987654 | 32109876 | 54321098 | 76543210 | bit
|
||||
// +----------+----------+----------+----------+
|
||||
// | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
|
||||
// | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
|
||||
// | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
|
||||
// | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
|
||||
// +----------+----------+----------+----------+
|
||||
// | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
|
||||
// | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
|
||||
// +----------+----------+----------+----------+
|
||||
|
||||
public static function unichr($code) {
|
||||
if($code > 1114111 or $code < 0 or
|
||||
($code >= 55296 and $code <= 57343) ) {
|
||||
// bits are set outside the "valid" range as defined
|
||||
// by UNICODE 4.1.0
|
||||
return '';
|
||||
}
|
||||
|
||||
$x = $y = $z = $w = 0;
|
||||
if ($code < 128) {
|
||||
// regular ASCII character
|
||||
$x = $code;
|
||||
} else {
|
||||
// set up bits for UTF-8
|
||||
$x = ($code & 63) | 128;
|
||||
if ($code < 2048) {
|
||||
$y = (($code & 2047) >> 6) | 192;
|
||||
} else {
|
||||
$y = (($code & 4032) >> 6) | 128;
|
||||
if($code < 65536) {
|
||||
$z = (($code >> 12) & 15) | 224;
|
||||
} else {
|
||||
$z = (($code >> 12) & 63) | 128;
|
||||
$w = (($code >> 18) & 7) | 240;
|
||||
}
|
||||
}
|
||||
}
|
||||
// set up the actual character
|
||||
$ret = '';
|
||||
if($w) $ret .= chr($w);
|
||||
if($z) $ret .= chr($z);
|
||||
if($y) $ret .= chr($y);
|
||||
$ret .= chr($x);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to UTF-8 based on configuration.
|
||||
*/
|
||||
public static function convertToUTF8($str, $config, $context) {
|
||||
static $iconv = null;
|
||||
if ($iconv === null) $iconv = function_exists('iconv');
|
||||
$encoding = $config->get('Core', 'Encoding');
|
||||
if ($encoding === 'utf-8') return $str;
|
||||
if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
|
||||
return @iconv($encoding, 'utf-8//IGNORE', $str);
|
||||
} elseif ($encoding === 'iso-8859-1') {
|
||||
return @utf8_encode($str);
|
||||
}
|
||||
trigger_error('Encoding not supported', E_USER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string from UTF-8 based on configuration.
|
||||
* @note Currently, this is a lossy conversion, with unexpressable
|
||||
* characters being omitted.
|
||||
*/
|
||||
public static function convertFromUTF8($str, $config, $context) {
|
||||
static $iconv = null;
|
||||
if ($iconv === null) $iconv = function_exists('iconv');
|
||||
$encoding = $config->get('Core', 'Encoding');
|
||||
if ($encoding === 'utf-8') return $str;
|
||||
if ($config->get('Core', 'EscapeNonASCIICharacters')) {
|
||||
$str = HTMLPurifier_Encoder::convertToASCIIDumbLossless($str);
|
||||
}
|
||||
if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
|
||||
return @iconv('utf-8', $encoding . '//IGNORE', $str);
|
||||
} elseif ($encoding === 'iso-8859-1') {
|
||||
return @utf8_decode($str);
|
||||
}
|
||||
trigger_error('Encoding not supported', E_USER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lossless (character-wise) conversion of HTML to ASCII
|
||||
* @param $str UTF-8 string to be converted to ASCII
|
||||
* @returns ASCII encoded string with non-ASCII character entity-ized
|
||||
* @warning Adapted from MediaWiki, claiming fair use: this is a common
|
||||
* algorithm. If you disagree with this license fudgery,
|
||||
* implement it yourself.
|
||||
* @note Uses decimal numeric entities since they are best supported.
|
||||
* @note This is a DUMB function: it has no concept of keeping
|
||||
* character entities that the projected character encoding
|
||||
* can allow. We could possibly implement a smart version
|
||||
* but that would require it to also know which Unicode
|
||||
* codepoints the charset supported (not an easy task).
|
||||
* @note Sort of with cleanUTF8() but it assumes that $str is
|
||||
* well-formed UTF-8
|
||||
*/
|
||||
public static function convertToASCIIDumbLossless($str) {
|
||||
$bytesleft = 0;
|
||||
$result = '';
|
||||
$working = 0;
|
||||
$len = strlen($str);
|
||||
for( $i = 0; $i < $len; $i++ ) {
|
||||
$bytevalue = ord( $str[$i] );
|
||||
if( $bytevalue <= 0x7F ) { //0xxx xxxx
|
||||
$result .= chr( $bytevalue );
|
||||
$bytesleft = 0;
|
||||
} elseif( $bytevalue <= 0xBF ) { //10xx xxxx
|
||||
$working = $working << 6;
|
||||
$working += ($bytevalue & 0x3F);
|
||||
$bytesleft--;
|
||||
if( $bytesleft <= 0 ) {
|
||||
$result .= "&#" . $working . ";";
|
||||
}
|
||||
} elseif( $bytevalue <= 0xDF ) { //110x xxxx
|
||||
$working = $bytevalue & 0x1F;
|
||||
$bytesleft = 1;
|
||||
} elseif( $bytevalue <= 0xEF ) { //1110 xxxx
|
||||
$working = $bytevalue & 0x0F;
|
||||
$bytesleft = 2;
|
||||
} else { //1111 0xxx
|
||||
$working = $bytevalue & 0x07;
|
||||
$bytesleft = 3;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Object that provides entity lookup table from entity name to character
|
||||
*/
|
||||
class HTMLPurifier_EntityLookup {
|
||||
|
||||
/**
|
||||
* Assoc array of entity name to character represented.
|
||||
*/
|
||||
public $table;
|
||||
|
||||
/**
|
||||
* Sets up the entity lookup table from the serialized file contents.
|
||||
* @note The serialized contents are versioned, but were generated
|
||||
* using the maintenance script generate_entity_file.php
|
||||
* @warning This is not in constructor to help enforce the Singleton
|
||||
*/
|
||||
public function setup($file = false) {
|
||||
if (!$file) {
|
||||
$file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
|
||||
}
|
||||
$this->table = unserialize(file_get_contents($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves sole instance of the object.
|
||||
* @param Optional prototype of custom lookup table to overload with.
|
||||
*/
|
||||
public static function instance($prototype = false) {
|
||||
// no references, since PHP doesn't copy unless modified
|
||||
static $instance = null;
|
||||
if ($prototype) {
|
||||
$instance = $prototype;
|
||||
} elseif (!$instance) {
|
||||
$instance = new HTMLPurifier_EntityLookup();
|
||||
$instance->setup();
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/EntityLookup.php';
|
||||
require_once 'HTMLPurifier/Encoder.php';
|
||||
|
||||
// if want to implement error collecting here, we'll need to use some sort
|
||||
// of global data (probably trigger_error) because it's impossible to pass
|
||||
// $config or $context to the callback functions.
|
||||
|
||||
/**
|
||||
* Handles referencing and derefencing character entities
|
||||
*/
|
||||
class HTMLPurifier_EntityParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Reference to entity lookup table.
|
||||
*/
|
||||
protected $_entity_lookup;
|
||||
|
||||
/**
|
||||
* Callback regex string for parsing entities.
|
||||
*/
|
||||
protected $_substituteEntitiesRegex =
|
||||
'/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
|
||||
// 1. hex 2. dec 3. string (XML style)
|
||||
|
||||
|
||||
/**
|
||||
* Decimal to parsed string conversion table for special entities.
|
||||
*/
|
||||
protected $_special_dec2str =
|
||||
array(
|
||||
34 => '"',
|
||||
38 => '&',
|
||||
39 => "'",
|
||||
60 => '<',
|
||||
62 => '>'
|
||||
);
|
||||
|
||||
/**
|
||||
* Stripped entity names to decimal conversion table for special entities.
|
||||
*/
|
||||
protected $_special_ent2dec =
|
||||
array(
|
||||
'quot' => 34,
|
||||
'amp' => 38,
|
||||
'lt' => 60,
|
||||
'gt' => 62
|
||||
);
|
||||
|
||||
/**
|
||||
* Substitutes non-special entities with their parsed equivalents. Since
|
||||
* running this whenever you have parsed character is t3h 5uck, we run
|
||||
* it before everything else.
|
||||
*
|
||||
* @param $string String to have non-special entities parsed.
|
||||
* @returns Parsed string.
|
||||
*/
|
||||
public function substituteNonSpecialEntities($string) {
|
||||
// it will try to detect missing semicolons, but don't rely on it
|
||||
return preg_replace_callback(
|
||||
$this->_substituteEntitiesRegex,
|
||||
array($this, 'nonSpecialEntityCallback'),
|
||||
$string
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for substituteNonSpecialEntities() that does the work.
|
||||
*
|
||||
* @param $matches PCRE matches array, with 0 the entire match, and
|
||||
* either index 1, 2 or 3 set with a hex value, dec value,
|
||||
* or string (respectively).
|
||||
* @returns Replacement string.
|
||||
*/
|
||||
|
||||
protected function nonSpecialEntityCallback($matches) {
|
||||
// replaces all but big five
|
||||
$entity = $matches[0];
|
||||
$is_num = (@$matches[0][1] === '#');
|
||||
if ($is_num) {
|
||||
$is_hex = (@$entity[2] === 'x');
|
||||
$code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
|
||||
|
||||
// abort for special characters
|
||||
if (isset($this->_special_dec2str[$code])) return $entity;
|
||||
|
||||
return HTMLPurifier_Encoder::unichr($code);
|
||||
} else {
|
||||
if (isset($this->_special_ent2dec[$matches[3]])) return $entity;
|
||||
if (!$this->_entity_lookup) {
|
||||
$this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
|
||||
}
|
||||
if (isset($this->_entity_lookup->table[$matches[3]])) {
|
||||
return $this->_entity_lookup->table[$matches[3]];
|
||||
} else {
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitutes only special entities with their parsed equivalents.
|
||||
*
|
||||
* @notice We try to avoid calling this function because otherwise, it
|
||||
* would have to be called a lot (for every parsed section).
|
||||
*
|
||||
* @param $string String to have non-special entities parsed.
|
||||
* @returns Parsed string.
|
||||
*/
|
||||
public function substituteSpecialEntities($string) {
|
||||
return preg_replace_callback(
|
||||
$this->_substituteEntitiesRegex,
|
||||
array($this, 'specialEntityCallback'),
|
||||
$string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for substituteSpecialEntities() that does the work.
|
||||
*
|
||||
* This callback has same syntax as nonSpecialEntityCallback().
|
||||
*
|
||||
* @param $matches PCRE-style matches array, with 0 the entire match, and
|
||||
* either index 1, 2 or 3 set with a hex value, dec value,
|
||||
* or string (respectively).
|
||||
* @returns Replacement string.
|
||||
*/
|
||||
protected function specialEntityCallback($matches) {
|
||||
$entity = $matches[0];
|
||||
$is_num = (@$matches[0][1] === '#');
|
||||
if ($is_num) {
|
||||
$is_hex = (@$entity[2] === 'x');
|
||||
$int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
|
||||
return isset($this->_special_dec2str[$int]) ?
|
||||
$this->_special_dec2str[$int] :
|
||||
$entity;
|
||||
} else {
|
||||
return isset($this->_special_ent2dec[$matches[3]]) ?
|
||||
$this->_special_ent2dec[$matches[3]] :
|
||||
$entity;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Return object from functions that signifies error when null doesn't cut it
|
||||
*/
|
||||
class HTMLPurifier_Error {}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Generator.php';
|
||||
|
||||
/**
|
||||
* Error collection class that enables HTML Purifier to report HTML
|
||||
* problems back to the user
|
||||
*/
|
||||
class HTMLPurifier_ErrorCollector
|
||||
{
|
||||
|
||||
protected $errors = array();
|
||||
protected $locale;
|
||||
protected $generator;
|
||||
protected $context;
|
||||
|
||||
public function __construct($context) {
|
||||
$this->locale =& $context->get('Locale');
|
||||
$this->generator =& $context->get('Generator');
|
||||
$this->context =& $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an error message to the collector for later use
|
||||
* @param $line Integer line number, or HTMLPurifier_Token that caused error
|
||||
* @param $severity int Error severity, PHP error style (don't use E_USER_)
|
||||
* @param $msg string Error message text
|
||||
*/
|
||||
public function send($severity, $msg) {
|
||||
|
||||
$args = array();
|
||||
if (func_num_args() > 2) {
|
||||
$args = func_get_args();
|
||||
array_shift($args);
|
||||
unset($args[0]);
|
||||
}
|
||||
|
||||
$token = $this->context->get('CurrentToken', true);
|
||||
$line = $token ? $token->line : $this->context->get('CurrentLine', true);
|
||||
$attr = $this->context->get('CurrentAttr', true);
|
||||
|
||||
// perform special substitutions, also add custom parameters
|
||||
$subst = array();
|
||||
if (!is_null($token)) {
|
||||
$args['CurrentToken'] = $token;
|
||||
}
|
||||
if (!is_null($attr)) {
|
||||
$subst['$CurrentAttr.Name'] = $attr;
|
||||
if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr];
|
||||
}
|
||||
|
||||
if (empty($args)) {
|
||||
$msg = $this->locale->getMessage($msg);
|
||||
} else {
|
||||
$msg = $this->locale->formatMessage($msg, $args);
|
||||
}
|
||||
|
||||
if (!empty($subst)) $msg = strtr($msg, $subst);
|
||||
|
||||
$this->errors[] = array($line, $severity, $msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves raw error data for custom formatter to use
|
||||
* @param List of arrays in format of array(Error message text,
|
||||
* token that caused error, tokens surrounding token)
|
||||
*/
|
||||
public function getRaw() {
|
||||
return $this->errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default HTML formatting implementation for error messages
|
||||
* @param $config Configuration array, vital for HTML output nature
|
||||
*/
|
||||
public function getHTMLFormatted($config) {
|
||||
$ret = array();
|
||||
|
||||
$errors = $this->errors;
|
||||
|
||||
// sort error array by line
|
||||
// line numbers are enabled if they aren't explicitly disabled
|
||||
if ($config->get('Core', 'MaintainLineNumbers') !== false) {
|
||||
$has_line = array();
|
||||
$lines = array();
|
||||
$original_order = array();
|
||||
foreach ($errors as $i => $error) {
|
||||
$has_line[] = (int) (bool) $error[0];
|
||||
$lines[] = $error[0];
|
||||
$original_order[] = $i;
|
||||
}
|
||||
array_multisort($has_line, SORT_DESC, $lines, SORT_ASC, $original_order, SORT_ASC, $errors);
|
||||
}
|
||||
|
||||
foreach ($errors as $error) {
|
||||
list($line, $severity, $msg) = $error;
|
||||
$string = '';
|
||||
$string .= '<strong>' . $this->locale->getErrorName($severity) . '</strong>: ';
|
||||
$string .= $this->generator->escape($msg);
|
||||
if ($line) {
|
||||
// have javascript link generation that causes
|
||||
// textarea to skip to the specified line
|
||||
$string .= $this->locale->formatMessage(
|
||||
'ErrorCollector: At line', array('line' => $line));
|
||||
}
|
||||
$ret[] = $string;
|
||||
}
|
||||
|
||||
if (empty($errors)) {
|
||||
return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
|
||||
} else {
|
||||
return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Represents a pre or post processing filter on HTML Purifier's output
|
||||
*
|
||||
* Sometimes, a little ad-hoc fixing of HTML has to be done before
|
||||
* it gets sent through HTML Purifier: you can use filters to acheive
|
||||
* this effect. For instance, YouTube videos can be preserved using
|
||||
* this manner. You could have used a decorator for this task, but
|
||||
* PHP's support for them is not terribly robust, so we're going
|
||||
* to just loop through the filters.
|
||||
*
|
||||
* Filters should be exited first in, last out. If there are three filters,
|
||||
* named 1, 2 and 3, the order of execution should go 1->preFilter,
|
||||
* 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
|
||||
* 1->postFilter.
|
||||
*
|
||||
* @note Methods are not declared abstract as it is perfectly legitimate
|
||||
* for an implementation not to want anything to happen on a step
|
||||
*/
|
||||
|
||||
class HTMLPurifier_Filter
|
||||
{
|
||||
|
||||
/**
|
||||
* Name of the filter for identification purposes
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Pre-processor function, handles HTML before HTML Purifier
|
||||
*/
|
||||
public function preFilter($html, $config, $context) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-processor function, handles HTML after HTML Purifier
|
||||
*/
|
||||
public function postFilter($html, $config, $context) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Filter.php';
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Filter', 'ExtractStyleBlocksEscaping', true, 'bool', '
|
||||
<p>
|
||||
Whether or not to escape the dangerous characters <, > and &
|
||||
as \3C, \3E and \26, respectively. This is can be safely set to false
|
||||
if the contents of StyleBlocks will be placed in an external stylesheet,
|
||||
where there is no risk of it being interpreted as HTML. This directive
|
||||
has been available since 3.0.0.
|
||||
</p>
|
||||
'
|
||||
);
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Filter', 'ExtractStyleBlocksScope', null, 'string/null', '
|
||||
<p>
|
||||
If you would like users to be able to define external stylesheets, but
|
||||
only allow them to specify CSS declarations for a specific node and
|
||||
prevent them from fiddling with other elements, use this directive.
|
||||
It accepts any valid CSS selector, and will prepend this to any
|
||||
CSS declaration extracted from the document. For example, if this
|
||||
directive is set to <code>#user-content</code> and a user uses the
|
||||
selector <code>a:hover</code>, the final selector will be
|
||||
<code>#user-content a:hover</code>.
|
||||
</p>
|
||||
<p>
|
||||
The comma shorthand may be used; consider the above example, with
|
||||
<code>#user-content, #user-content2</code>, the final selector will
|
||||
be <code>#user-content a:hover, #user-content2 a:hover</code>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning:</strong> It is possible for users to bypass this measure
|
||||
using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML
|
||||
Purifier, and I am working to get it fixed. Until then, HTML Purifier
|
||||
performs a basic check to prevent this.
|
||||
</p>
|
||||
<p>
|
||||
This directive has been available since 3.0.0.
|
||||
</p>
|
||||
'
|
||||
);
|
||||
|
||||
/**
|
||||
* This filter extracts <style> blocks from input HTML, cleans them up
|
||||
* using CSSTidy, and then places them in $purifier->context->get('StyleBlocks')
|
||||
* so they can be used elsewhere in the document.
|
||||
*
|
||||
* @note
|
||||
* See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for
|
||||
* sample usage.
|
||||
*
|
||||
* @note
|
||||
* This filter can also be used on stylesheets not included in the
|
||||
* document--something purists would probably prefer. Just directly
|
||||
* call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS()
|
||||
*/
|
||||
class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
|
||||
{
|
||||
|
||||
public $name = 'ExtractStyleBlocks';
|
||||
private $_styleMatches = array();
|
||||
private $_tidy;
|
||||
|
||||
/**
|
||||
* @param $tidy
|
||||
* Instance of csstidy to use, false to turn off cleaning,
|
||||
* and null to automatically instantiate
|
||||
*/
|
||||
public function __construct($tidy = null) {
|
||||
if ($tidy === null) $tidy = new csstidy();
|
||||
$this->_tidy = $tidy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the contents of CSS blocks to style matches
|
||||
* @param $matches preg_replace style $matches array
|
||||
*/
|
||||
protected function styleCallback($matches) {
|
||||
$this->_styleMatches[] = $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes inline <style> tags from HTML, saves them for later use
|
||||
* @todo Extend to indicate non-text/css style blocks
|
||||
*/
|
||||
public function preFilter($html, $config, $context) {
|
||||
$html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html);
|
||||
$style_blocks = $this->_styleMatches;
|
||||
$this->_styleMatches = array(); // reset
|
||||
$context->register('StyleBlocks', $style_blocks); // $context must not be reused
|
||||
if ($this->_tidy) {
|
||||
foreach ($style_blocks as &$style) {
|
||||
$style = $this->cleanCSS($style, $config, $context);
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes CSS (the stuff found in <style>) and cleans it.
|
||||
* @warning Requires CSSTidy <http://csstidy.sourceforge.net/>
|
||||
* @param $css CSS styling to clean
|
||||
* @param $config Instance of HTMLPurifier_Config
|
||||
* @param $context Instance of HTMLPurifier_Context
|
||||
* @return Cleaned CSS
|
||||
*/
|
||||
public function cleanCSS($css, $config, $context) {
|
||||
// prepare scope
|
||||
$scope = $config->get('Filter', 'ExtractStyleBlocksScope');
|
||||
if ($scope !== null) {
|
||||
$scopes = array_map('trim', explode(',', $scope));
|
||||
} else {
|
||||
$scopes = array();
|
||||
}
|
||||
$this->_tidy->parse($css);
|
||||
$css_definition = $config->getDefinition('CSS');
|
||||
foreach ($this->_tidy->css as $k => $decls) {
|
||||
// $decls are all CSS declarations inside an @ selector
|
||||
$new_decls = array();
|
||||
foreach ($decls as $selector => $style) {
|
||||
$selector = trim($selector);
|
||||
if ($selector === '') continue; // should not happen
|
||||
if ($selector[0] === '+') {
|
||||
if ($selector !== '' && $selector[0] === '+') continue;
|
||||
}
|
||||
if (!empty($scopes)) {
|
||||
$new_selector = array(); // because multiple ones are possible
|
||||
$selectors = array_map('trim', explode(',', $selector));
|
||||
foreach ($scopes as $s1) {
|
||||
foreach ($selectors as $s2) {
|
||||
$new_selector[] = "$s1 $s2";
|
||||
}
|
||||
}
|
||||
$selector = implode(', ', $new_selector); // now it's a string
|
||||
}
|
||||
foreach ($style as $name => $value) {
|
||||
if (!isset($css_definition->info[$name])) {
|
||||
unset($style[$name]);
|
||||
continue;
|
||||
}
|
||||
$def = $css_definition->info[$name];
|
||||
$ret = $def->validate($value, $config, $context);
|
||||
if ($ret === false) unset($style[$name]);
|
||||
else $style[$name] = $ret;
|
||||
}
|
||||
$new_decls[$selector] = $style;
|
||||
}
|
||||
$this->_tidy->css[$k] = $new_decls;
|
||||
}
|
||||
// remove stuff that shouldn't be used, could be reenabled
|
||||
// after security risks are analyzed
|
||||
$this->_tidy->import = array();
|
||||
$this->_tidy->charset = null;
|
||||
$this->_tidy->namespace = null;
|
||||
$css = $this->_tidy->print->plain();
|
||||
// we are going to escape any special characters <>& to ensure
|
||||
// that no funny business occurs (i.e. </style> in a font-family prop).
|
||||
if ($config->get('Filter', 'ExtractStyleBlocksEscaping')) {
|
||||
$css = str_replace(
|
||||
array('<', '>', '&'),
|
||||
array('\3C ', '\3E ', '\26 '),
|
||||
$css
|
||||
);
|
||||
}
|
||||
return $css;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Filter.php';
|
||||
|
||||
class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter
|
||||
{
|
||||
|
||||
public $name = 'YouTube';
|
||||
|
||||
public function preFilter($html, $config, $context) {
|
||||
$pre_regex = '#<object[^>]+>.+?'.
|
||||
'http://www.youtube.com/v/([A-Za-z0-9\-_]+).+?</object>#s';
|
||||
$pre_replace = '<span class="youtube-embed">\1</span>';
|
||||
return preg_replace($pre_regex, $pre_replace, $html);
|
||||
}
|
||||
|
||||
public function postFilter($html, $config, $context) {
|
||||
$post_regex = '#<span class="youtube-embed">([A-Za-z0-9\-_]+)</span>#';
|
||||
$post_replace = '<object width="425" height="350" '.
|
||||
'data="http://www.youtube.com/v/\1">'.
|
||||
'<param name="movie" value="http://www.youtube.com/v/\1"></param>'.
|
||||
'<param name="wmode" value="transparent"></param>'.
|
||||
'<!--[if IE]>'.
|
||||
'<embed src="http://www.youtube.com/v/\1"'.
|
||||
'type="application/x-shockwave-flash"'.
|
||||
'wmode="transparent" width="425" height="350" />'.
|
||||
'<![endif]-->'.
|
||||
'</object>';
|
||||
return preg_replace($post_regex, $post_replace, $html);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Output', 'CommentScriptContents', true, 'bool',
|
||||
'Determines whether or not HTML Purifier should attempt to fix up '.
|
||||
'the contents of script tags for legacy browsers with comments. This '.
|
||||
'directive was available since 2.0.0.'
|
||||
);
|
||||
HTMLPurifier_ConfigSchema::defineAlias('Core', 'CommentScriptContents', 'Output', 'CommentScriptContents');
|
||||
|
||||
// extension constraints could be factored into ConfigSchema
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'Output', 'TidyFormat', false, 'bool', <<<HTML
|
||||
<p>
|
||||
Determines whether or not to run Tidy on the final output for pretty
|
||||
formatting reasons, such as indentation and wrap.
|
||||
</p>
|
||||
<p>
|
||||
This can greatly improve readability for editors who are hand-editing
|
||||
the HTML, but is by no means necessary as HTML Purifier has already
|
||||
fixed all major errors the HTML may have had. Tidy is a non-default
|
||||
extension, and this directive will silently fail if Tidy is not
|
||||
available.
|
||||
</p>
|
||||
<p>
|
||||
If you are looking to make the overall look of your page's source
|
||||
better, I recommend running Tidy on the entire page rather than just
|
||||
user-content (after all, the indentation relative to the containing
|
||||
blocks will be incorrect).
|
||||
</p>
|
||||
<p>
|
||||
This directive was available since 1.1.1.
|
||||
</p>
|
||||
HTML
|
||||
);
|
||||
HTMLPurifier_ConfigSchema::defineAlias('Core', 'TidyFormat', 'Output', 'TidyFormat');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define('Output', 'Newline', null, 'string/null', '
|
||||
<p>
|
||||
Newline string to format final output with. If left null, HTML Purifier
|
||||
will auto-detect the default newline type of the system and use that;
|
||||
you can manually override it here. Remember, \r\n is Windows, \r
|
||||
is Mac, and \n is Unix. This directive was available since 2.0.1.
|
||||
</p>
|
||||
');
|
||||
|
||||
/**
|
||||
* Generates HTML from tokens.
|
||||
* @todo Refactor interface so that configuration/context is determined
|
||||
* upon instantiation, no need for messy generateFromTokens() calls
|
||||
* @todo Make some of the more internal functions protected, and have
|
||||
* unit tests work around that
|
||||
*/
|
||||
class HTMLPurifier_Generator
|
||||
{
|
||||
|
||||
/**
|
||||
* Bool cache of %HTML.XHTML
|
||||
* @private
|
||||
*/
|
||||
private $_xhtml = true;
|
||||
|
||||
/**
|
||||
* Bool cache of %Output.CommentScriptContents
|
||||
* @private
|
||||
*/
|
||||
private $_scriptFix = false;
|
||||
|
||||
/**
|
||||
* Cache of HTMLDefinition
|
||||
* @private
|
||||
*/
|
||||
private $_def;
|
||||
|
||||
/**
|
||||
* Generates HTML from an array of tokens.
|
||||
* @param $tokens Array of HTMLPurifier_Token
|
||||
* @param $config HTMLPurifier_Config object
|
||||
* @return Generated HTML
|
||||
*/
|
||||
public function generateFromTokens($tokens, $config, $context) {
|
||||
$html = '';
|
||||
if (!$config) $config = HTMLPurifier_Config::createDefault();
|
||||
$this->_scriptFix = $config->get('Output', 'CommentScriptContents');
|
||||
|
||||
$this->_def = $config->getHTMLDefinition();
|
||||
$this->_xhtml = $this->_def->doctype->xml;
|
||||
|
||||
if (!$tokens) return '';
|
||||
for ($i = 0, $size = count($tokens); $i < $size; $i++) {
|
||||
if ($this->_scriptFix && $tokens[$i]->name === 'script'
|
||||
&& $i + 2 < $size && $tokens[$i+2]->type == 'end') {
|
||||
// script special case
|
||||
// the contents of the script block must be ONE token
|
||||
// for this to work
|
||||
$html .= $this->generateFromToken($tokens[$i++]);
|
||||
$html .= $this->generateScriptFromToken($tokens[$i++]);
|
||||
// We're not going to do this: it wouldn't be valid anyway
|
||||
//while ($tokens[$i]->name != 'script') {
|
||||
// $html .= $this->generateScriptFromToken($tokens[$i++]);
|
||||
//}
|
||||
}
|
||||
$html .= $this->generateFromToken($tokens[$i]);
|
||||
}
|
||||
if ($config->get('Output', 'TidyFormat') && extension_loaded('tidy')) {
|
||||
|
||||
$tidy_options = array(
|
||||
'indent'=> true,
|
||||
'output-xhtml' => $this->_xhtml,
|
||||
'show-body-only' => true,
|
||||
'indent-spaces' => 2,
|
||||
'wrap' => 68,
|
||||
);
|
||||
if (version_compare(PHP_VERSION, '5', '<')) {
|
||||
tidy_set_encoding('utf8');
|
||||
foreach ($tidy_options as $key => $value) {
|
||||
tidy_setopt($key, $value);
|
||||
}
|
||||
tidy_parse_string($html);
|
||||
tidy_clean_repair();
|
||||
$html = tidy_get_output();
|
||||
} else {
|
||||
$tidy = new Tidy;
|
||||
$tidy->parseString($html, $tidy_options, 'utf8');
|
||||
$tidy->cleanRepair();
|
||||
$html = (string) $tidy;
|
||||
}
|
||||
}
|
||||
// normalize newlines to system
|
||||
$nl = $config->get('Output', 'Newline');
|
||||
if ($nl === null) $nl = PHP_EOL;
|
||||
$html = str_replace("\n", $nl, $html);
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML from a single token.
|
||||
* @param $token HTMLPurifier_Token object.
|
||||
* @return Generated HTML
|
||||
*/
|
||||
public function generateFromToken($token) {
|
||||
if (!isset($token->type)) return '';
|
||||
if ($token->type == 'start') {
|
||||
$attr = $this->generateAttributes($token->attr, $token->name);
|
||||
return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
|
||||
|
||||
} elseif ($token->type == 'end') {
|
||||
return '</' . $token->name . '>';
|
||||
|
||||
} elseif ($token->type == 'empty') {
|
||||
$attr = $this->generateAttributes($token->attr, $token->name);
|
||||
return '<' . $token->name . ($attr ? ' ' : '') . $attr .
|
||||
( $this->_xhtml ? ' /': '' )
|
||||
. '>';
|
||||
|
||||
} elseif ($token->type == 'text') {
|
||||
return $this->escape($token->data);
|
||||
|
||||
} else {
|
||||
return '';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special case processor for the contents of script tags
|
||||
* @warning This runs into problems if there's already a literal
|
||||
* --> somewhere inside the script contents.
|
||||
*/
|
||||
public function generateScriptFromToken($token) {
|
||||
if ($token->type != 'text') return $this->generateFromToken($token);
|
||||
// return '<!--' . "\n" . trim($token->data) . "\n" . '// -->';
|
||||
// more advanced version:
|
||||
// thanks <http://lachy.id.au/log/2005/05/script-comments>
|
||||
$data = preg_replace('#//\s*$#', '', $token->data);
|
||||
return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates attribute declarations from attribute array.
|
||||
* @param $assoc_array_of_attributes Attribute array
|
||||
* @return Generate HTML fragment for insertion.
|
||||
*/
|
||||
public function generateAttributes($assoc_array_of_attributes, $element) {
|
||||
$html = '';
|
||||
foreach ($assoc_array_of_attributes as $key => $value) {
|
||||
if (!$this->_xhtml) {
|
||||
// remove namespaced attributes
|
||||
if (strpos($key, ':') !== false) continue;
|
||||
if (!empty($this->_def->info[$element]->attr[$key]->minimized)) {
|
||||
$html .= $key . ' ';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$html .= $key.'="'.$this->escape($value).'" ';
|
||||
}
|
||||
return rtrim($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes raw text data.
|
||||
* @param $string String data to escape for HTML.
|
||||
* @return String escaped data.
|
||||
*/
|
||||
public function escape($string) {
|
||||
return htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/Definition.php';
|
||||
require_once 'HTMLPurifier/HTMLModuleManager.php';
|
||||
|
||||
// this definition and its modules MUST NOT define configuration directives
|
||||
// outside of the HTML or Attr namespaces
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'DefinitionID', null, 'string/null', '
|
||||
<p>
|
||||
Unique identifier for a custom-built HTML definition. If you edit
|
||||
the raw version of the HTMLDefinition, introducing changes that the
|
||||
configuration object does not reflect, you must specify this variable.
|
||||
If you change your custom edits, you should change this directive, or
|
||||
clear your cache. Example:
|
||||
</p>
|
||||
<pre>
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set(\'HTML\', \'DefinitionID\', \'1\');
|
||||
$def = $config->getHTMLDefinition();
|
||||
$def->addAttribute(\'a\', \'tabindex\', \'Number\');
|
||||
</pre>
|
||||
<p>
|
||||
In the above example, the configuration is still at the defaults, but
|
||||
using the advanced API, an extra attribute has been added. The
|
||||
configuration object normally has no way of knowing that this change
|
||||
has taken place, so it needs an extra directive: %HTML.DefinitionID.
|
||||
If someone else attempts to use the default configuration, these two
|
||||
pieces of code will not clobber each other in the cache, since one has
|
||||
an extra directive attached to it.
|
||||
</p>
|
||||
<p>
|
||||
This directive has been available since 2.0.0, and in that version or
|
||||
later you <em>must</em> specify a value to this directive to use the
|
||||
advanced API features.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'DefinitionRev', 1, 'int', '
|
||||
<p>
|
||||
Revision identifier for your custom definition specified in
|
||||
%HTML.DefinitionID. This serves the same purpose: uniquely identifying
|
||||
your custom definition, but this one does so in a chronological
|
||||
context: revision 3 is more up-to-date then revision 2. Thus, when
|
||||
this gets incremented, the cache handling is smart enough to clean
|
||||
up any older revisions of your definition as well as flush the
|
||||
cache. This directive has been available since 2.0.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'BlockWrapper', 'p', 'string', '
|
||||
<p>
|
||||
String name of element to wrap inline elements that are inside a block
|
||||
context. This only occurs in the children of blockquote in strict mode.
|
||||
</p>
|
||||
<p>
|
||||
Example: by default value,
|
||||
<code><blockquote>Foo</blockquote></code> would become
|
||||
<code><blockquote><p>Foo</p></blockquote></code>.
|
||||
The <code><p></code> tags can be replaced with whatever you desire,
|
||||
as long as it is a block level element. This directive has been available
|
||||
since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'Parent', 'div', 'string', '
|
||||
<p>
|
||||
String name of element that HTML fragment passed to library will be
|
||||
inserted in. An interesting variation would be using span as the
|
||||
parent element, meaning that only inline tags would be allowed.
|
||||
This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'AllowedElements', null, 'lookup/null', '
|
||||
<p>
|
||||
If HTML Purifier\'s tag set is unsatisfactory for your needs, you
|
||||
can overload it with your own list of tags to allow. Note that this
|
||||
method is subtractive: it does its job by taking away from HTML Purifier
|
||||
usual feature set, so you cannot add a tag that HTML Purifier never
|
||||
supported in the first place (like embed, form or head). If you
|
||||
change this, you probably also want to change %HTML.AllowedAttributes.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning:</strong> If another directive conflicts with the
|
||||
elements here, <em>that</em> directive will win and override.
|
||||
This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'AllowedAttributes', null, 'lookup/null', '
|
||||
<p>
|
||||
If HTML Purifier\'s attribute set is unsatisfactory, overload it!
|
||||
The syntax is "tag.attr" or "*.attr" for the global attributes
|
||||
(style, id, class, dir, lang, xml:lang).
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning:</strong> If another directive conflicts with the
|
||||
elements here, <em>that</em> directive will win and override. For
|
||||
example, %HTML.EnableAttrID will take precedence over *.id in this
|
||||
directive. You must set that directive to true before you can use
|
||||
IDs at all. This directive has been available since 1.3.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
HTMLPurifier_ConfigSchema::define(
|
||||
'HTML', 'Allowed', null, 'itext/null', '
|
||||
<p>
|
||||
This is a convenience directive that rolls the functionality of
|
||||
%HTML.AllowedElements and %HTML.AllowedAttributes into one directive.
|
||||
Specify elements and attributes that are allowed using:
|
||||
<code>element1[attr1|attr2],element2...</code>. You can also use
|
||||
newlines instead of commas to separate elements.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Warning</strong>:
|
||||
All of the constraints on the component directives are still enforced.
|
||||
The syntax is a <em>subset</em> of TinyMCE\'s <code>valid_elements</code>
|
||||
whitelist: directly copy-pasting it here will probably result in
|
||||
broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes
|
||||
are set, this directive has no effect.
|
||||
This directive has been available since 2.0.0.
|
||||
</p>
|
||||
');
|
||||
|
||||
/**
|
||||
* Definition of the purified HTML that describes allowed children,
|
||||
* attributes, and many other things.
|
||||
*
|
||||
* Conventions:
|
||||
*
|
||||
* All member variables that are prefixed with info
|
||||
* (including the main $info array) are used by HTML Purifier internals
|
||||
* and should not be directly edited when customizing the HTMLDefinition.
|
||||
* They can usually be set via configuration directives or custom
|
||||
* modules.
|
||||
*
|
||||
* On the other hand, member variables without the info prefix are used
|
||||
* internally by the HTMLDefinition and MUST NOT be used by other HTML
|
||||
* Purifier internals. Many of them, however, are public, and may be
|
||||
* edited by userspace code to tweak the behavior of HTMLDefinition.
|
||||
*
|
||||
* @note This class is inspected by Printer_HTMLDefinition; please
|
||||
* update that class if things here change.
|
||||
*/
|
||||
class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
|
||||
{
|
||||
|
||||
// FULLY-PUBLIC VARIABLES ---------------------------------------------
|
||||
|
||||
/**
|
||||
* Associative array of element names to HTMLPurifier_ElementDef
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Associative array of global attribute name to attribute definition.
|
||||
*/
|
||||
public $info_global_attr = array();
|
||||
|
||||
/**
|
||||
* String name of parent element HTML will be going into.
|
||||
*/
|
||||
public $info_parent = 'div';
|
||||
|
||||
/**
|
||||
* Definition for parent element, allows parent element to be a
|
||||
* tag that's not allowed inside the HTML fragment.
|
||||
*/
|
||||
public $info_parent_def;
|
||||
|
||||
/**
|
||||
* String name of element used to wrap inline elements in block context
|
||||
* @note This is rarely used except for BLOCKQUOTEs in strict mode
|
||||
*/
|
||||
public $info_block_wrapper = 'p';
|
||||
|
||||
/**
|
||||
* Associative array of deprecated tag name to HTMLPurifier_TagTransform
|
||||
*/
|
||||
public $info_tag_transform = array();
|
||||
|
||||
/**
|
||||
* Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
|
||||
*/
|
||||
public $info_attr_transform_pre = array();
|
||||
|
||||
/**
|
||||
* Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
|
||||
*/
|
||||
public $info_attr_transform_post = array();
|
||||
|
||||
/**
|
||||
* Nested lookup array of content set name (Block, Inline) to
|
||||
* element name to whether or not it belongs in that content set.
|
||||
*/
|
||||
public $info_content_sets = array();
|
||||
|
||||
/**
|
||||
* Doctype object
|
||||
*/
|
||||
public $doctype;
|
||||
|
||||
|
||||
|
||||
// RAW CUSTOMIZATION STUFF --------------------------------------------
|
||||
|
||||
/**
|
||||
* Adds a custom attribute to a pre-existing element
|
||||
* @param $element_name String element name to add attribute to
|
||||
* @param $attr_name String name of attribute
|
||||
* @param $def Attribute definition, can be string or object, see
|
||||
* HTMLPurifier_AttrTypes for details
|
||||
*/
|
||||
public function addAttribute($element_name, $attr_name, $def) {
|
||||
$module =& $this->getAnonymousModule();
|
||||
$element =& $module->addBlankElement($element_name);
|
||||
$element->attr[$attr_name] = $def;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom element to your HTML definition
|
||||
* @note See HTMLPurifier_HTMLModule::addElement for detailed
|
||||
* parameter and return value descriptions.
|
||||
*/
|
||||
public function &addElement($element_name, $type, $contents, $attr_collections, $attributes) {
|
||||
$module =& $this->getAnonymousModule();
|
||||
// assume that if the user is calling this, the element
|
||||
// is safe. This may not be a good idea
|
||||
$element =& $module->addElement($element_name, true, $type, $contents, $attr_collections, $attributes);
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a blank element to your HTML definition, for overriding
|
||||
* existing behavior
|
||||
* @note See HTMLPurifier_HTMLModule::addBlankElement for detailed
|
||||
* parameter and return value descriptions.
|
||||
*/
|
||||
public function &addBlankElement($element_name) {
|
||||
$module =& $this->getAnonymousModule();
|
||||
$element =& $module->addBlankElement($element_name);
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a reference to the anonymous module, so you can
|
||||
* bust out advanced features without having to make your own
|
||||
* module.
|
||||
*/
|
||||
public function &getAnonymousModule() {
|
||||
if (!$this->_anonModule) {
|
||||
$this->_anonModule = new HTMLPurifier_HTMLModule();
|
||||
$this->_anonModule->name = 'Anonymous';
|
||||
}
|
||||
return $this->_anonModule;
|
||||
}
|
||||
|
||||
private $_anonModule;
|
||||
|
||||
|
||||
// PUBLIC BUT INTERNAL VARIABLES --------------------------------------
|
||||
|
||||
public $type = 'HTML';
|
||||
public $manager; /**< Instance of HTMLPurifier_HTMLModuleManager */
|
||||
|
||||
/**
|
||||
* Performs low-cost, preliminary initialization.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->manager = new HTMLPurifier_HTMLModuleManager();
|
||||
}
|
||||
|
||||
protected function doSetup($config) {
|
||||
$this->processModules($config);
|
||||
$this->setupConfigStuff($config);
|
||||
unset($this->manager);
|
||||
|
||||
// cleanup some of the element definitions
|
||||
foreach ($this->info as $k => $v) {
|
||||
unset($this->info[$k]->content_model);
|
||||
unset($this->info[$k]->content_model_type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract out the information from the manager
|
||||
*/
|
||||
protected function processModules($config) {
|
||||
|
||||
if ($this->_anonModule) {
|
||||
// for user specific changes
|
||||
// this is late-loaded so we don't have to deal with PHP4
|
||||
// reference wonky-ness
|
||||
$this->manager->addModule($this->_anonModule);
|
||||
unset($this->_anonModule);
|
||||
}
|
||||
|
||||
$this->manager->setup($config);
|
||||
$this->doctype = $this->manager->doctype;
|
||||
|
||||
foreach ($this->manager->modules as $module) {
|
||||
foreach($module->info_tag_transform as $k => $v) {
|
||||
if ($v === false) unset($this->info_tag_transform[$k]);
|
||||
else $this->info_tag_transform[$k] = $v;
|
||||
}
|
||||
foreach($module->info_attr_transform_pre as $k => $v) {
|
||||
if ($v === false) unset($this->info_attr_transform_pre[$k]);
|
||||
else $this->info_attr_transform_pre[$k] = $v;
|
||||
}
|
||||
foreach($module->info_attr_transform_post as $k => $v) {
|
||||
if ($v === false) unset($this->info_attr_transform_post[$k]);
|
||||
else $this->info_attr_transform_post[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info = $this->manager->getElements();
|
||||
$this->info_content_sets = $this->manager->contentSets->lookup;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up stuff based on config. We need a better way of doing this.
|
||||
*/
|
||||
protected function setupConfigStuff($config) {
|
||||
|
||||
$block_wrapper = $config->get('HTML', 'BlockWrapper');
|
||||
if (isset($this->info_content_sets['Block'][$block_wrapper])) {
|
||||
$this->info_block_wrapper = $block_wrapper;
|
||||
} else {
|
||||
trigger_error('Cannot use non-block element as block wrapper',
|
||||
E_USER_ERROR);
|
||||
}
|
||||
|
||||
$parent = $config->get('HTML', 'Parent');
|
||||
$def = $this->manager->getElement($parent, true);
|
||||
if ($def) {
|
||||
$this->info_parent = $parent;
|
||||
$this->info_parent_def = $def;
|
||||
} else {
|
||||
trigger_error('Cannot use unrecognized element as parent',
|
||||
E_USER_ERROR);
|
||||
$this->info_parent_def = $this->manager->getElement($this->info_parent, true);
|
||||
}
|
||||
|
||||
// support template text
|
||||
$support = "(for information on implementing this, see the ".
|
||||
"support forums) ";
|
||||
|
||||
// setup allowed elements
|
||||
|
||||
$allowed_elements = $config->get('HTML', 'AllowedElements');
|
||||
$allowed_attributes = $config->get('HTML', 'AllowedAttributes');
|
||||
|
||||
if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
|
||||
$allowed = $config->get('HTML', 'Allowed');
|
||||
if (is_string($allowed)) {
|
||||
list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($allowed_elements)) {
|
||||
foreach ($this->info as $name => $d) {
|
||||
if(!isset($allowed_elements[$name])) unset($this->info[$name]);
|
||||
unset($allowed_elements[$name]);
|
||||
}
|
||||
// emit errors
|
||||
foreach ($allowed_elements as $element => $d) {
|
||||
$element = htmlspecialchars($element);
|
||||
trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
$allowed_attributes_mutable = $allowed_attributes; // by copy!
|
||||
if (is_array($allowed_attributes)) {
|
||||
foreach ($this->info_global_attr as $attr_key => $info) {
|
||||
if (!isset($allowed_attributes["*.$attr_key"])) {
|
||||
unset($this->info_global_attr[$attr_key]);
|
||||
} elseif (isset($allowed_attributes_mutable["*.$attr_key"])) {
|
||||
unset($allowed_attributes_mutable["*.$attr_key"]);
|
||||
}
|
||||
}
|
||||
foreach ($this->info as $tag => $info) {
|
||||
foreach ($info->attr as $attr => $attr_info) {
|
||||
if (!isset($allowed_attributes["$tag.$attr"]) &&
|
||||
!isset($allowed_attributes["*.$attr"])) {
|
||||
unset($this->info[$tag]->attr[$attr]);
|
||||
} else {
|
||||
if (isset($allowed_attributes_mutable["$tag.$attr"])) {
|
||||
unset($allowed_attributes_mutable["$tag.$attr"]);
|
||||
} elseif (isset($allowed_attributes_mutable["*.$attr"])) {
|
||||
unset($allowed_attributes_mutable["*.$attr"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// emit errors
|
||||
foreach ($allowed_attributes_mutable as $elattr => $d) {
|
||||
list($element, $attribute) = explode('.', $elattr);
|
||||
$element = htmlspecialchars($element);
|
||||
$attribute = htmlspecialchars($attribute);
|
||||
if ($element == '*') {
|
||||
trigger_error("Global attribute '$attribute' is not ".
|
||||
"supported in any elements $support",
|
||||
E_USER_WARNING);
|
||||
} else {
|
||||
trigger_error("Attribute '$attribute' in element '$element' not supported $support",
|
||||
E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a TinyMCE-flavored Allowed Elements and Attributes list into
|
||||
* separate lists for processing. Format is element[attr1|attr2],element2...
|
||||
* @warning Although it's largely drawn from TinyMCE's implementation,
|
||||
* it is different, and you'll probably have to modify your lists
|
||||
* @param $list String list to parse
|
||||
* @param array($allowed_elements, $allowed_attributes)
|
||||
* @todo Give this its own class, probably static interface
|
||||
*/
|
||||
public function parseTinyMCEAllowedList($list) {
|
||||
|
||||
$elements = array();
|
||||
$attributes = array();
|
||||
|
||||
$chunks = preg_split('/(,|[\n\r]+)/', $list);
|
||||
foreach ($chunks as $chunk) {
|
||||
if (empty($chunk)) continue;
|
||||
// remove TinyMCE element control characters
|
||||
if (!strpos($chunk, '[')) {
|
||||
$element = $chunk;
|
||||
$attr = false;
|
||||
} else {
|
||||
list($element, $attr) = explode('[', $chunk);
|
||||
}
|
||||
if ($element !== '*') $elements[$element] = true;
|
||||
if (!$attr) continue;
|
||||
$attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
|
||||
$attr = explode('|', $attr);
|
||||
foreach ($attr as $key) {
|
||||
$attributes["$element.$key"] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return array($elements, $attributes);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Represents an XHTML 1.1 module, with information on elements, tags
|
||||
* and attributes.
|
||||
* @note Even though this is technically XHTML 1.1, it is also used for
|
||||
* regular HTML parsing. We are using modulization as a convenient
|
||||
* way to represent the internals of HTMLDefinition, and our
|
||||
* implementation is by no means conforming and does not directly
|
||||
* use the normative DTDs or XML schemas.
|
||||
* @note The public variables in a module should almost directly
|
||||
* correspond to the variables in HTMLPurifier_HTMLDefinition.
|
||||
* However, the prefix info carries no special meaning in these
|
||||
* objects (include it anyway if that's the correspondence though).
|
||||
* @todo Consider making some member functions protected
|
||||
*/
|
||||
|
||||
class HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
// -- Overloadable ----------------------------------------------------
|
||||
|
||||
/**
|
||||
* Short unique string identifier of the module
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Informally, a list of elements this module changes. Not used in
|
||||
* any significant way.
|
||||
*/
|
||||
public $elements = array();
|
||||
|
||||
/**
|
||||
* Associative array of element names to element definitions.
|
||||
* Some definitions may be incomplete, to be merged in later
|
||||
* with the full definition.
|
||||
*/
|
||||
public $info = array();
|
||||
|
||||
/**
|
||||
* Associative array of content set names to content set additions.
|
||||
* This is commonly used to, say, add an A element to the Inline
|
||||
* content set. This corresponds to an internal variable $content_sets
|
||||
* and NOT info_content_sets member variable of HTMLDefinition.
|
||||
*/
|
||||
public $content_sets = array();
|
||||
|
||||
/**
|
||||
* Associative array of attribute collection names to attribute
|
||||
* collection additions. More rarely used for adding attributes to
|
||||
* the global collections. Example is the StyleAttribute module adding
|
||||
* the style attribute to the Core. Corresponds to HTMLDefinition's
|
||||
* attr_collections->info, since the object's data is only info,
|
||||
* with extra behavior associated with it.
|
||||
*/
|
||||
public $attr_collections = array();
|
||||
|
||||
/**
|
||||
* Associative array of deprecated tag name to HTMLPurifier_TagTransform
|
||||
*/
|
||||
public $info_tag_transform = array();
|
||||
|
||||
/**
|
||||
* List of HTMLPurifier_AttrTransform to be performed before validation.
|
||||
*/
|
||||
public $info_attr_transform_pre = array();
|
||||
|
||||
/**
|
||||
* List of HTMLPurifier_AttrTransform to be performed after validation.
|
||||
*/
|
||||
public $info_attr_transform_post = array();
|
||||
|
||||
/**
|
||||
* Boolean flag that indicates whether or not getChildDef is implemented.
|
||||
* For optimization reasons: may save a call to a function. Be sure
|
||||
* to set it if you do implement getChildDef(), otherwise it will have
|
||||
* no effect!
|
||||
*/
|
||||
public $defines_child_def = false;
|
||||
|
||||
/**
|
||||
* Retrieves a proper HTMLPurifier_ChildDef subclass based on
|
||||
* content_model and content_model_type member variables of
|
||||
* the HTMLPurifier_ElementDef class. There is a similar function
|
||||
* in HTMLPurifier_HTMLDefinition.
|
||||
* @param $def HTMLPurifier_ElementDef instance
|
||||
* @return HTMLPurifier_ChildDef subclass
|
||||
*/
|
||||
public function getChildDef($def) {return false;}
|
||||
|
||||
// -- Convenience -----------------------------------------------------
|
||||
|
||||
/**
|
||||
* Convenience function that sets up a new element
|
||||
* @param $element Name of element to add
|
||||
* @param $safe Is element safe for untrusted users to use?
|
||||
* @param $type What content set should element be registered to?
|
||||
* Set as false to skip this step.
|
||||
* @param $contents Allowed children in form of:
|
||||
* "$content_model_type: $content_model"
|
||||
* @param $attr_includes What attribute collections to register to
|
||||
* element?
|
||||
* @param $attr What unique attributes does the element define?
|
||||
* @note See ElementDef for in-depth descriptions of these parameters.
|
||||
* @return Reference to created element definition object, so you
|
||||
* can set advanced parameters
|
||||
*/
|
||||
public function &addElement($element, $safe, $type, $contents, $attr_includes = array(), $attr = array()) {
|
||||
$this->elements[] = $element;
|
||||
// parse content_model
|
||||
list($content_model_type, $content_model) = $this->parseContents($contents);
|
||||
// merge in attribute inclusions
|
||||
$this->mergeInAttrIncludes($attr, $attr_includes);
|
||||
// add element to content sets
|
||||
if ($type) $this->addElementToContentSet($element, $type);
|
||||
// create element
|
||||
$this->info[$element] = HTMLPurifier_ElementDef::create(
|
||||
$safe, $content_model, $content_model_type, $attr
|
||||
);
|
||||
// literal object $contents means direct child manipulation
|
||||
if (!is_string($contents)) $this->info[$element]->child = $contents;
|
||||
return $this->info[$element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that creates a totally blank, non-standalone
|
||||
* element.
|
||||
* @param $element Name of element to create
|
||||
* @return Reference to created element
|
||||
*/
|
||||
public function &addBlankElement($element) {
|
||||
if (!isset($this->info[$element])) {
|
||||
$this->elements[] = $element;
|
||||
$this->info[$element] = new HTMLPurifier_ElementDef();
|
||||
$this->info[$element]->standalone = false;
|
||||
} else {
|
||||
trigger_error("Definition for $element already exists in module, cannot redefine");
|
||||
}
|
||||
return $this->info[$element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that registers an element to a content set
|
||||
* @param Element to register
|
||||
* @param Name content set (warning: case sensitive, usually upper-case
|
||||
* first letter)
|
||||
*/
|
||||
public function addElementToContentSet($element, $type) {
|
||||
if (!isset($this->content_sets[$type])) $this->content_sets[$type] = '';
|
||||
else $this->content_sets[$type] .= ' | ';
|
||||
$this->content_sets[$type] .= $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that transforms single-string contents
|
||||
* into separate content model and content model type
|
||||
* @param $contents Allowed children in form of:
|
||||
* "$content_model_type: $content_model"
|
||||
* @note If contents is an object, an array of two nulls will be
|
||||
* returned, and the callee needs to take the original $contents
|
||||
* and use it directly.
|
||||
*/
|
||||
public function parseContents($contents) {
|
||||
if (!is_string($contents)) return array(null, null); // defer
|
||||
switch ($contents) {
|
||||
// check for shorthand content model forms
|
||||
case 'Empty':
|
||||
return array('empty', '');
|
||||
case 'Inline':
|
||||
return array('optional', 'Inline | #PCDATA');
|
||||
case 'Flow':
|
||||
return array('optional', 'Flow | #PCDATA');
|
||||
}
|
||||
list($content_model_type, $content_model) = explode(':', $contents);
|
||||
$content_model_type = strtolower(trim($content_model_type));
|
||||
$content_model = trim($content_model);
|
||||
return array($content_model_type, $content_model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that merges a list of attribute includes into
|
||||
* an attribute array.
|
||||
* @param $attr Reference to attr array to modify
|
||||
* @param $attr_includes Array of includes / string include to merge in
|
||||
*/
|
||||
public function mergeInAttrIncludes(&$attr, $attr_includes) {
|
||||
if (!is_array($attr_includes)) {
|
||||
if (empty($attr_includes)) $attr_includes = array();
|
||||
else $attr_includes = array($attr_includes);
|
||||
}
|
||||
$attr[0] = $attr_includes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function that generates a lookup table with boolean
|
||||
* true as value.
|
||||
* @param $list List of values to turn into a lookup
|
||||
* @note You can also pass an arbitrary number of arguments in
|
||||
* place of the regular argument
|
||||
* @return Lookup array equivalent of list
|
||||
*/
|
||||
public function makeLookup($list) {
|
||||
if (is_string($list)) $list = func_get_args();
|
||||
$ret = array();
|
||||
foreach ($list as $value) {
|
||||
if (is_null($value)) continue;
|
||||
$ret[$value] = true;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
require_once 'HTMLPurifier/AttrTransform/BdoDir.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Bi-directional Text Module, defines elements that
|
||||
* declare directionality of content. Text Extension Module.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
public $name = 'Bdo';
|
||||
public $attr_collections = array(
|
||||
'I18N' => array('dir' => false)
|
||||
);
|
||||
|
||||
public function __construct() {
|
||||
$bdo =& $this->addElement(
|
||||
'bdo', true, 'Inline', 'Inline', array('Core', 'Lang'),
|
||||
array(
|
||||
'dir' => 'Enum#ltr,rtl', // required
|
||||
// The Abstract Module specification has the attribute
|
||||
// inclusions wrong for bdo: bdo allows Lang
|
||||
)
|
||||
);
|
||||
$bdo->attr_transform_post['required-dir'] = new HTMLPurifier_AttrTransform_BdoDir();
|
||||
|
||||
$this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
|
||||
class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
public $name = 'CommonAttributes';
|
||||
|
||||
public $attr_collections = array(
|
||||
'Core' => array(
|
||||
0 => array('Style'),
|
||||
// 'xml:space' => false,
|
||||
'class' => 'NMTOKENS',
|
||||
'id' => 'ID',
|
||||
'title' => 'CDATA',
|
||||
),
|
||||
'Lang' => array(),
|
||||
'I18N' => array(
|
||||
0 => array('Lang'), // proprietary, for xml:lang/lang
|
||||
),
|
||||
'Common' => array(
|
||||
0 => array('Core', 'I18N')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
require_once 'HTMLPurifier/ChildDef/Chameleon.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
|
||||
* Module.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
public $name = 'Edit';
|
||||
|
||||
public function __construct() {
|
||||
$contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
|
||||
$attr = array(
|
||||
'cite' => 'URI',
|
||||
// 'datetime' => 'Datetime', // not implemented
|
||||
);
|
||||
$this->addElement('del', true, 'Inline', $contents, 'Common', $attr);
|
||||
$this->addElement('ins', true, 'Inline', $contents, 'Common', $attr);
|
||||
}
|
||||
|
||||
// HTML 4.01 specifies that ins/del must not contain block
|
||||
// elements when used in an inline context, chameleon is
|
||||
// a complicated workaround to acheive this effect
|
||||
|
||||
// Inline context ! Block context (exclamation mark is
|
||||
// separator, see getChildDef for parsing)
|
||||
|
||||
public $defines_child_def = true;
|
||||
public function getChildDef($def) {
|
||||
if ($def->content_model_type != 'chameleon') return false;
|
||||
$value = explode('!', $def->content_model);
|
||||
return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/LinkTypes.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
public $name = 'Hypertext';
|
||||
|
||||
public function __construct() {
|
||||
$a =& $this->addElement(
|
||||
'a', true, 'Inline', 'Inline', 'Common',
|
||||
array(
|
||||
// 'accesskey' => 'Character',
|
||||
// 'charset' => 'Charset',
|
||||
'href' => 'URI',
|
||||
// 'hreflang' => 'LanguageCode',
|
||||
'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
|
||||
'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
|
||||
// 'tabindex' => 'Number',
|
||||
// 'type' => 'ContentType',
|
||||
)
|
||||
);
|
||||
$a->excludes = array('a' => true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/URI.php';
|
||||
require_once 'HTMLPurifier/AttrTransform/ImgRequired.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Image Module provides basic image embedding.
|
||||
* @note There is specialized code for removing empty images in
|
||||
* HTMLPurifier_Strategy_RemoveForeignElements
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
public $name = 'Image';
|
||||
|
||||
public function __construct() {
|
||||
$img =& $this->addElement(
|
||||
'img', true, 'Inline', 'Empty', 'Common',
|
||||
array(
|
||||
'alt*' => 'Text',
|
||||
'height' => 'Length',
|
||||
'longdesc' => 'URI',
|
||||
'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
|
||||
'width' => 'Length'
|
||||
)
|
||||
);
|
||||
// kind of strange, but splitting things up would be inefficient
|
||||
$img->attr_transform_pre[] =
|
||||
$img->attr_transform_post[] =
|
||||
new HTMLPurifier_AttrTransform_ImgRequired();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/AttrDef/HTML/Bool.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 Legacy module defines elements that were previously
|
||||
* deprecated.
|
||||
*
|
||||
* @note Not all legacy elements have been implemented yet, which
|
||||
* is a bit of a reverse problem as compared to browsers! In
|
||||
* addition, this legacy module may implement a bit more than
|
||||
* mandated by XHTML 1.1.
|
||||
*
|
||||
* This module can be used in combination with TransformToStrict in order
|
||||
* to transform as many deprecated elements as possible, but retain
|
||||
* questionably deprecated elements that do not have good alternatives
|
||||
* as well as transform elements that don't have an implementation.
|
||||
* See docs/ref-strictness.txt for more details.
|
||||
*/
|
||||
|
||||
class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
public $name = 'Legacy';
|
||||
|
||||
public function __construct() {
|
||||
|
||||
$this->addElement('basefont', true, 'Inline', 'Empty', false, array(
|
||||
'color' => 'Color',
|
||||
'face' => 'Text', // extremely broad, we should
|
||||
'size' => 'Text', // tighten it
|
||||
'id' => 'ID'
|
||||
));
|
||||
$this->addElement('center', true, 'Block', 'Flow', 'Common');
|
||||
$this->addElement('dir', true, 'Block', 'Required: li', 'Common', array(
|
||||
'compact' => 'Bool#compact'
|
||||
));
|
||||
$this->addElement('font', true, 'Inline', 'Inline', array('Core', 'I18N'), array(
|
||||
'color' => 'Color',
|
||||
'face' => 'Text', // extremely broad, we should
|
||||
'size' => 'Text', // tighten it
|
||||
));
|
||||
$this->addElement('menu', true, 'Block', 'Required: li', 'Common', array(
|
||||
'compact' => 'Bool#compact'
|
||||
));
|
||||
$this->addElement('s', true, 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('strike', true, 'Inline', 'Inline', 'Common');
|
||||
$this->addElement('u', true, 'Inline', 'Inline', 'Common');
|
||||
|
||||
// setup modifications to old elements
|
||||
|
||||
$align = 'Enum#left,right,center,justify';
|
||||
|
||||
$address =& $this->addBlankElement('address');
|
||||
$address->content_model = 'Inline | #PCDATA | p';
|
||||
$address->content_model_type = 'optional';
|
||||
$address->child = false;
|
||||
|
||||
$blockquote =& $this->addBlankElement('blockquote');
|
||||
$blockquote->content_model = 'Flow | #PCDATA';
|
||||
$blockquote->content_model_type = 'optional';
|
||||
$blockquote->child = false;
|
||||
|
||||
$br =& $this->addBlankElement('br');
|
||||
$br->attr['clear'] = 'Enum#left,all,right,none';
|
||||
|
||||
$caption =& $this->addBlankElement('caption');
|
||||
$caption->attr['align'] = 'Enum#top,bottom,left,right';
|
||||
|
||||
$div =& $this->addBlankElement('div');
|
||||
$div->attr['align'] = $align;
|
||||
|
||||
$dl =& $this->addBlankElement('dl');
|
||||
$dl->attr['compact'] = 'Bool#compact';
|
||||
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
$h =& $this->addBlankElement("h$i");
|
||||
$h->attr['align'] = $align;
|
||||
}
|
||||
|
||||
$hr =& $this->addBlankElement('hr');
|
||||
$hr->attr['align'] = $align;
|
||||
$hr->attr['noshade'] = 'Bool#noshade';
|
||||
$hr->attr['size'] = 'Pixels';
|
||||
$hr->attr['width'] = 'Length';
|
||||
|
||||
$img =& $this->addBlankElement('img');
|
||||
$img->attr['align'] = 'Enum#top,middle,bottom,left,right';
|
||||
$img->attr['border'] = 'Pixels';
|
||||
$img->attr['hspace'] = 'Pixels';
|
||||
$img->attr['vspace'] = 'Pixels';
|
||||
|
||||
// figure out this integer business
|
||||
|
||||
$li =& $this->addBlankElement('li');
|
||||
$li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
|
||||
$li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle';
|
||||
|
||||
$ol =& $this->addBlankElement('ol');
|
||||
$ol->attr['compact'] = 'Bool#compact';
|
||||
$ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
|
||||
$ol->attr['type'] = 'Enum#s:1,i,I,a,A';
|
||||
|
||||
$p =& $this->addBlankElement('p');
|
||||
$p->attr['align'] = $align;
|
||||
|
||||
$pre =& $this->addBlankElement('pre');
|
||||
$pre->attr['width'] = 'Number';
|
||||
|
||||
// script omitted
|
||||
|
||||
$table =& $this->addBlankElement('table');
|
||||
$table->attr['align'] = 'Enum#left,center,right';
|
||||
$table->attr['bgcolor'] = 'Color';
|
||||
|
||||
$tr =& $this->addBlankElement('tr');
|
||||
$tr->attr['bgcolor'] = 'Color';
|
||||
|
||||
$th =& $this->addBlankElement('th');
|
||||
$th->attr['bgcolor'] = 'Color';
|
||||
$th->attr['height'] = 'Length';
|
||||
$th->attr['nowrap'] = 'Bool#nowrap';
|
||||
$th->attr['width'] = 'Length';
|
||||
|
||||
$td =& $this->addBlankElement('td');
|
||||
$td->attr['bgcolor'] = 'Color';
|
||||
$td->attr['height'] = 'Length';
|
||||
$td->attr['nowrap'] = 'Bool#nowrap';
|
||||
$td->attr['width'] = 'Length';
|
||||
|
||||
$ul =& $this->addBlankElement('ul');
|
||||
$ul->attr['compact'] = 'Bool#compact';
|
||||
$ul->attr['type'] = 'Enum#square,disc,circle';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
|
||||
/**
|
||||
* XHTML 1.1 List Module, defines list-oriented elements. Core Module.
|
||||
*/
|
||||
class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
|
||||
public $name = 'List';
|
||||
|
||||
// According to the abstract schema, the List content set is a fully formed
|
||||
// one or more expr, but it invariably occurs in an optional declaration
|
||||
// so we're not going to do that subtlety. It might cause trouble
|
||||
// if a user defines "List" and expects that multiple lists are
|
||||
// allowed to be specified, but then again, that's not very intuitive.
|
||||
// Furthermore, the actual XML Schema may disagree. Regardless,
|
||||
// we don't have support for such nested expressions without using
|
||||
// the incredibly inefficient and draconic Custom ChildDef.
|
||||
|
||||
public $content_sets = array('Flow' => 'List');
|
||||
|
||||
public function __construct() {
|
||||
$this->addElement('ol', true, 'List', 'Required: li', 'Common');
|
||||
$this->addElement('ul', true, 'List', 'Required: li', 'Common');
|
||||
$this->addElement('dl', true, 'List', 'Required: dt | dd', 'Common');
|
||||
|
||||
$this->addElement('li', true, false, 'Flow', 'Common');
|
||||
|
||||
$this->addElement('dd', true, false, 'Flow', 'Common');
|
||||
$this->addElement('dt', true, false, 'Inline', 'Common');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
require_once 'HTMLPurifier/HTMLModule.php';
|
||||
|
||||
class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
|
||||
{
|
||||
public $name = 'NonXMLCommonAttributes';
|
||||
|
||||
public $attr_collections = array(
|
||||
'Lang' => array(
|
||||
'lang' => 'LanguageCode',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user