MDL-16668 import htmlpurifier 3.1.1 to HEAD

This commit is contained in:
skodak 2008-09-24 21:32:10 +00:00
parent 165a2c9efe
commit 67d7aa437a
70 changed files with 1441 additions and 292 deletions

View File

@ -19,7 +19,7 @@
*/
/*
HTML Purifier 3.1.0 - Standards Compliant HTML Filtering
HTML Purifier 3.1.1 - Standards Compliant HTML Filtering
Copyright (C) 2006-2008 Edward Z. Yang
This library is free software; you can redistribute it and/or
@ -55,10 +55,10 @@ class HTMLPurifier
{
/** Version of HTML Purifier */
public $version = '3.1.0';
public $version = '3.1.1';
/** Constant with version of HTML Purifier */
const VERSION = '3.1.0';
const VERSION = '3.1.1';
/** Global configuration object */
public $config;

View File

@ -23,7 +23,6 @@ require_once $__dir . '/HTMLPurifier/Definition.php';
require_once $__dir . '/HTMLPurifier/CSSDefinition.php';
require_once $__dir . '/HTMLPurifier/ChildDef.php';
require_once $__dir . '/HTMLPurifier/Config.php';
require_once $__dir . '/HTMLPurifier/ConfigDef.php';
require_once $__dir . '/HTMLPurifier/ConfigSchema.php';
require_once $__dir . '/HTMLPurifier/ContentSets.php';
require_once $__dir . '/HTMLPurifier/Context.php';
@ -46,6 +45,7 @@ require_once $__dir . '/HTMLPurifier/IDAccumulator.php';
require_once $__dir . '/HTMLPurifier/Injector.php';
require_once $__dir . '/HTMLPurifier/Language.php';
require_once $__dir . '/HTMLPurifier/LanguageFactory.php';
require_once $__dir . '/HTMLPurifier/Length.php';
require_once $__dir . '/HTMLPurifier/Lexer.php';
require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
require_once $__dir . '/HTMLPurifier/Strategy.php';
@ -60,12 +60,14 @@ require_once $__dir . '/HTMLPurifier/URIFilter.php';
require_once $__dir . '/HTMLPurifier/URIParser.php';
require_once $__dir . '/HTMLPurifier/URIScheme.php';
require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
require_once $__dir . '/HTMLPurifier/UnitConverter.php';
require_once $__dir . '/HTMLPurifier/VarParser.php';
require_once $__dir . '/HTMLPurifier/VarParserException.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Switch.php';
require_once $__dir . '/HTMLPurifier/AttrDef/Text.php';
require_once $__dir . '/HTMLPurifier/AttrDef/URI.php';
require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Number.php';
@ -110,6 +112,9 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
@ -118,9 +123,6 @@ require_once $__dir . '/HTMLPurifier/ChildDef/Required.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php';
require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php';
require_once $__dir . '/HTMLPurifier/ChildDef/Table.php';
require_once $__dir . '/HTMLPurifier/ConfigDef/Directive.php';
require_once $__dir . '/HTMLPurifier/ConfigDef/DirectiveAlias.php';
require_once $__dir . '/HTMLPurifier/ConfigDef/Namespace.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Null.php';
require_once $__dir . '/HTMLPurifier/DefinitionCache/Serializer.php';
@ -138,6 +140,8 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
@ -153,6 +157,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php';
require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php';
require_once $__dir . '/HTMLPurifier/Injector/Linkify.php';
require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php';
require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';
@ -173,6 +178,7 @@ require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php';
require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php';
require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php';
require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php';
require_once $__dir . '/HTMLPurifier/URIScheme/http.php';
require_once $__dir . '/HTMLPurifier/URIScheme/https.php';

View File

@ -51,16 +51,13 @@ abstract class HTMLPurifier_AttrDef
*
* @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.
* 4.7. However, note that we are NOT necessarily
* parsing XML, thus, this behavior may still be correct. We
* assume that newlines have been normalized.
*/
public function parseCDATA($string) {
$string = trim($string);
$string = str_replace("\n", '', $string);
$string = str_replace(array("\r", "\t"), ' ', $string);
$string = str_replace(array("\n", "\t", "\r"), ' ', $string);
return $string;
}

View File

@ -29,6 +29,12 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$declarations = explode(';', $css);
$propvalues = array();
/**
* Name of the current CSS property being validated.
*/
$property = false;
$context->register('CurrentCSSProperty', $property);
foreach ($declarations as $declaration) {
if (!$declaration) continue;
if (!strpos($declaration, ':')) continue;
@ -61,6 +67,8 @@ class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
$propvalues[$property] = $result;
}
$context->destroy('CurrentCSSProperty');
// 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.

View File

@ -16,7 +16,6 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
'cursive' => true
);
$string = $this->parseCDATA($string);
// assume that no font names contain commas in them
$fonts = explode(',', $string);
$final = '';
@ -35,13 +34,40 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
$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
$new_font = '';
for ($i = 0, $c = strlen($font); $i < $c; $i++) {
if ($font[$i] === '\\') {
$i++;
if ($i >= $c) {
$new_font .= '\\';
break;
}
if (ctype_xdigit($font[$i])) {
$code = $font[$i];
for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
if (!ctype_xdigit($font[$i])) break;
$code .= $font[$i];
}
// We have to be extremely careful when adding
// new characters, to make sure we're not breaking
// the encoding.
$char = HTMLPurifier_Encoder::unichr(hexdec($code));
if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue;
$new_font .= $char;
if ($i < $c && trim($font[$i]) !== '') $i--;
continue;
}
if ($font[$i] === "\n") continue;
}
$new_font .= $font[$i];
}
$font = $new_font;
}
// $font is a pure representation of the font name
if (ctype_alnum($font)) {
if (ctype_alnum($font) && $font !== '') {
// very simple font, allow it in unharmed
$final .= $font . ', ';
continue;
@ -50,8 +76,8 @@ class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
// complicated font, requires quoting
// armor single quotes and new lines
$font = str_replace("\\", "\\\\", $font);
$font = str_replace("'", "\\'", $font);
$font = str_replace("\n", "\\\n", $font);
$final .= "'$font', ";
}
$final = rtrim($final, ', ');

View File

@ -6,46 +6,40 @@
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;
protected $min, $max;
/**
* @param $non_negative Bool indication whether or not negative values are
* allowed.
* @param HTMLPurifier_Length $max Minimum length, or null for no bound. String is also acceptable.
* @param HTMLPurifier_Length $max Maximum length, or null for no bound. String is also acceptable.
*/
public function __construct($non_negative = false) {
$this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
public function __construct($min = null, $max = null) {
$this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
$this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
}
public function validate($length, $config, $context) {
public function validate($string, $config, $context) {
$string = $this->parseCDATA($string);
$length = $this->parseCDATA($length);
if ($length === '') return false;
if ($length === '0') return '0';
$strlen = strlen($length);
if ($strlen === 1) return false; // impossible!
// Optimizations
if ($string === '') return false;
if ($string === '0') return '0';
if (strlen($string) === 1) return false;
// 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);
$length = HTMLPurifier_Length::make($string);
if (!$length->isValid()) return false;
if (!isset($this->units[$unit])) return false;
$number = $this->number_def->validate($number, $config, $context);
if ($number === false) return false;
return $number . $unit;
if ($this->min) {
$c = $length->compareTo($this->min);
if ($c === false) return false;
if ($c < 0) return false;
}
if ($this->max) {
$c = $length->compareTo($this->max);
if ($c === false) return false;
if ($c > 0) return false;
}
return $length->toString();
}
}

View File

@ -18,6 +18,10 @@ class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
$this->non_negative = $non_negative;
}
/**
* @warning Some contexts do not pass $config, $context. These
* variables should not be used without checking HTMLPurifier_Length
*/
public function validate($number, $config, $context) {
$number = $this->parseCDATA($number);

View File

@ -13,10 +13,13 @@ class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
static $allowed_values = array(
'line-through' => true,
'overline' => true,
'underline' => true
'underline' => true,
);
$string = strtolower($this->parseCDATA($string));
if ($string === 'none') return $string;
$parts = explode(' ', $string);
$final = '';
foreach ($parts as $part) {

View File

@ -6,6 +6,12 @@
class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
{
protected $max;
public function __construct($max = null) {
$this->max = $max;
}
public function validate($string, $config, $context) {
$string = trim($string);
@ -24,11 +30,18 @@ class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
// 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';
if ($this->max !== null && $int > $this->max) return (string) $this->max;
return (string) $int;
}
public function make($string) {
if ($string === '') $max = null;
else $max = (int) $string;
$class = get_class($this);
return new $class($max);
}
}

View File

@ -0,0 +1,32 @@
<?php
/**
* Decorator that, depending on a token, switches between two definitions.
*/
class HTMLPurifier_AttrDef_Switch
{
protected $tag;
protected $withTag, $withoutTag;
/**
* @param string $tag Tag name to switch upon
* @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
* @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
*/
public function __construct($tag, $with_tag, $without_tag) {
$this->tag = $tag;
$this->withTag = $with_tag;
$this->withoutTag = $without_tag;
}
public function validate($string, $config, $context) {
$token = $context->get('CurrentToken', true);
if (!$token || $token->name !== $this->tag) {
return $this->withoutTag->validate($string, $config, $context);
} else {
return $this->withTag->validate($string, $config, $context);
}
}
}

View File

@ -18,6 +18,11 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
$this->embedsResource = (bool) $embeds_resource;
}
public function make($string) {
$embeds = (bool) $string;
return new HTMLPurifier_AttrDef_URI($embeds);
}
public function validate($uri, $config, $context) {
if ($config->get('URI', 'Disable')) return false;
@ -50,6 +55,10 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
$result = $scheme_obj->validate($uri, $config, $context);
if (!$result) break;
// Post chained filtering
$result = $uri_def->postFilter($uri, $config, $context);
if (!$result) break;
// survived gauntlet
$ok = true;
@ -59,18 +68,7 @@ class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
if (!$ok) return false;
// 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;
return $uri->toString();
}

View File

@ -0,0 +1,13 @@
<?php
class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
{
public $name = "SafeEmbed";
public function transform($attr, $config, $context) {
$attr['allowscriptaccess'] = 'never';
$attr['allownetworking'] = 'internal';
$attr['type'] = 'application/x-shockwave-flash';
return $attr;
}
}

View File

@ -0,0 +1,14 @@
<?php
/**
* Writes default type for all objects. Currently only supports flash.
*/
class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
{
public $name = "SafeObject";
function transform($attr, $config, $context) {
if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash';
return $attr;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Validates name/value pairs in param tags to be used in safe objects. This
* will only allow name values it recognizes, and pre-fill certain attributes
* with required values.
*
* @note
* This class only supports Flash. In the future, Quicktime support
* may be added.
*
* @warning
* This class expects an injector to add the necessary parameters tags.
*/
class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
{
public $name = "SafeParam";
private $uri;
public function __construct() {
$this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
}
public function transform($attr, $config, $context) {
// If we add support for other objects, we'll need to alter the
// transforms.
switch ($attr['name']) {
// application/x-shockwave-flash
// Keep this synchronized with Injector/SafeObject.php
case 'allowScriptAccess':
$attr['value'] = 'never';
break;
case 'allowNetworking':
$attr['value'] = 'internal';
break;
case 'wmode':
$attr['value'] = 'window';
break;
case 'movie':
$attr['value'] = $this->uri->validate($attr['value'], $config, $context);
break;
// add other cases to support other param name/value pairs
default:
$attr['name'] = $attr['value'] = null;
}
return $attr;
}
}

View File

@ -43,8 +43,8 @@ class HTMLPurifier_AttrValidator
// DEFINITION CALL
$d_defs = $definition->info_global_attr;
// reference attributes for easy manipulation
$attr =& $token->attr;
// don't update token until the very end, to ensure an atomic update
$attr = $token->attr;
// do global transformations (pre)
// nothing currently utilizes this
@ -139,6 +139,8 @@ class HTMLPurifier_AttrValidator
if ($e && ($attr != $o)) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
}
$token->attr = $attr;
// destroy CurrentToken if we made it ourselves
if (!$current_token) $context->destroy('CurrentToken');

View File

@ -90,7 +90,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$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
new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
));
$this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
@ -116,7 +116,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$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_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true)
));
@ -138,7 +138,7 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
$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_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true)
));
@ -149,14 +149,26 @@ class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
new HTMLPurifier_AttrDef_CSS_Percentage()
));
$this->info['width'] =
$this->info['height'] =
new HTMLPurifier_AttrDef_CSS_DenyElementDecorator(
new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length(true),
$trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length('0'),
new HTMLPurifier_AttrDef_CSS_Percentage(true),
new HTMLPurifier_AttrDef_Enum(array('auto'))
)), 'img');
));
$max = $config->get('CSS', 'MaxImgLength');
$this->info['width'] =
$this->info['height'] =
$max === null ?
$trusted_wh :
new HTMLPurifier_AttrDef_Switch('img',
// For img tags:
new HTMLPurifier_AttrDef_CSS_Composite(array(
new HTMLPurifier_AttrDef_CSS_Length('0', $max),
new HTMLPurifier_AttrDef_Enum(array('auto'))
)),
// For everyone else:
$trusted_wh
);
$this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();

View File

@ -55,10 +55,7 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
$escape_invalid_children = $config->get('Core', 'EscapeInvalidChildren');
// generator
static $gen = null;
if ($gen === null) {
$gen = new HTMLPurifier_Generator($config, $context);
}
$gen = new HTMLPurifier_Generator($config, $context);
foreach ($tokens_of_children as $token) {
if (!empty($token->is_whitespace)) {
@ -83,7 +80,7 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
$result[] = $token;
} elseif ($pcdata_allowed && $escape_invalid_children) {
$result[] = new HTMLPurifier_Token_Text(
$gen->generateFromToken($token, $config)
$gen->generateFromToken($token)
);
}
continue;
@ -94,7 +91,7 @@ class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
} elseif ($pcdata_allowed && $escape_invalid_children) {
$result[] =
new HTMLPurifier_Token_Text(
$gen->generateFromToken( $token, $config )
$gen->generateFromToken($token)
);
} else {
// drop silently

View File

@ -20,7 +20,7 @@ class HTMLPurifier_Config
/**
* HTML Purifier's version
*/
public $version = '3.1.0';
public $version = '3.1.1';
/**
* Bool indicator whether or not to automatically finalize
@ -125,7 +125,7 @@ class HTMLPurifier_Config
E_USER_WARNING);
return;
}
if ($this->def->info[$namespace][$key]->class == 'alias') {
if (isset($this->def->info[$namespace][$key]->isAlias)) {
$d = $this->def->info[$namespace][$key];
trigger_error('Cannot get value from aliased directive, use real name ' . $d->namespace . '.' . $d->name,
E_USER_ERROR);
@ -196,40 +196,48 @@ class HTMLPurifier_Config
E_USER_WARNING);
return;
}
if ($this->def->info[$namespace][$key]->class == 'alias') {
$def = $this->def->info[$namespace][$key];
if (isset($def->isAlias)) {
if ($from_alias) {
trigger_error('Double-aliases not allowed, please fix '.
'ConfigSchema bug with' . "$namespace.$key", E_USER_ERROR);
return;
}
$this->set($new_ns = $this->def->info[$namespace][$key]->namespace,
$new_dir = $this->def->info[$namespace][$key]->name,
$this->set($new_ns = $def->namespace,
$new_dir = $def->name,
$value, true);
trigger_error("$namespace.$key is an alias, preferred directive name is $new_ns.$new_dir", E_USER_NOTICE);
return;
}
// Raw type might be negative when using the fully optimized form
// of stdclass, which indicates allow_null == true
$rtype = is_int($def) ? $def : $def->type;
if ($rtype < 0) {
$type = -$rtype;
$allow_null = true;
} else {
$type = $rtype;
$allow_null = isset($def->allow_null);
}
try {
$value = $this->parser->parse(
$value,
$type = $this->def->info[$namespace][$key]->type,
$this->def->info[$namespace][$key]->allow_null
);
$value = $this->parser->parse($value, $type, $allow_null);
} catch (HTMLPurifier_VarParserException $e) {
trigger_error('Value for ' . "$namespace.$key" . ' is of invalid type, should be ' . $type, E_USER_WARNING);
trigger_error('Value for ' . "$namespace.$key" . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
return;
}
if (is_string($value)) {
if (is_string($value) && is_object($def)) {
// resolve value alias if defined
if (isset($this->def->info[$namespace][$key]->aliases[$value])) {
$value = $this->def->info[$namespace][$key]->aliases[$value];
if (isset($def->aliases[$value])) {
$value = $def->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;
}
// check to see if the value is allowed
if (isset($def->allowed) && !isset($def->allowed[$value])) {
trigger_error('Value not supported, valid values are: ' .
$this->_listify($def->allowed), E_USER_WARNING);
return;
}
}
$this->conf[$namespace][$key] = $value;
@ -386,7 +394,7 @@ class HTMLPurifier_Config
if (isset($blacklisted_directives["$ns.$directive"])) continue;
if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
}
if ($def->class == 'alias') continue;
if (isset($def->isAlias)) continue;
if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
$ret[] = array($ns, $directive);
}

View File

@ -12,7 +12,33 @@ class HTMLPurifier_ConfigSchema {
public $defaults = array();
/**
* Definition of the directives.
* Definition of the directives. The structure of this is:
*
* array(
* 'Namespace' => array(
* 'Directive' => new stdclass(),
* )
* )
*
* The stdclass may have the following properties:
*
* - If isAlias isn't set:
* - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
* - allow_null: If set, this directive allows null values
* - aliases: If set, an associative array of value aliases to real values
* - allowed: If set, a lookup array of allowed (string) values
* - If isAlias is set:
* - namespace: Namespace this directive aliases to
* - name: Directive name this directive aliases to
*
* In certain degenerate cases, stdclass will actually be an integer. In
* that case, the value is equivalent to an stdclass with the type
* property set to the integer. If the integer is negative, type is
* equal to the absolute value of integer, and allow_null is true.
*
* This class is friendly with HTMLPurifier_Config. If you need introspection
* about the schema, you're better of using the ConfigSchema_Interchange,
* which uses more memory but has much richer information.
*/
public $info = array();
@ -21,15 +47,6 @@ class HTMLPurifier_ConfigSchema {
*/
static protected $singleton;
/**
* Variable parser.
*/
protected $parser;
public function __construct() {
$this->parser = new HTMLPurifier_VarParser_Flexible();
}
/**
* Unserializes the default ConfigSchema.
*/
@ -62,11 +79,11 @@ class HTMLPurifier_ConfigSchema {
* @param $allow_null Whether or not to allow null values
*/
public function add($namespace, $name, $default, $type, $allow_null) {
$default = $this->parser->parse($default, $type, $allow_null);
$this->info[$namespace][$name] = new HTMLPurifier_ConfigDef_Directive();
$this->info[$namespace][$name]->type = $type;
$this->info[$namespace][$name]->allow_null = $allow_null;
$this->defaults[$namespace][$name] = $default;
$obj = new stdclass();
$obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
if ($allow_null) $obj->allow_null = true;
$this->info[$namespace][$name] = $obj;
$this->defaults[$namespace][$name] = $default;
}
/**
@ -90,6 +107,9 @@ class HTMLPurifier_ConfigSchema {
* @param $aliases Hash of aliased values to the real alias
*/
public function addValueAliases($namespace, $name, $aliases) {
if (!isset($this->info[$namespace][$name]->aliases)) {
$this->info[$namespace][$name]->aliases = array();
}
foreach ($aliases as $alias => $real) {
$this->info[$namespace][$name]->aliases[$alias] = $real;
}
@ -104,7 +124,6 @@ class HTMLPurifier_ConfigSchema {
* @param $allowed Lookup array of allowed values
*/
public function addAllowedValues($namespace, $name, $allowed) {
$type = $this->info[$namespace][$name]->type;
$this->info[$namespace][$name]->allowed = $allowed;
}
@ -116,7 +135,26 @@ class HTMLPurifier_ConfigSchema {
* @param $new_name Directive that the alias will be to
*/
public function addAlias($namespace, $name, $new_namespace, $new_name) {
$this->info[$namespace][$name] = new HTMLPurifier_ConfigDef_DirectiveAlias($new_namespace, $new_name);
$obj = new stdclass;
$obj->namespace = $new_namespace;
$obj->name = $new_name;
$obj->isAlias = true;
$this->info[$namespace][$name] = $obj;
}
/**
* Replaces any stdclass that only has the type property with type integer.
*/
public function postProcess() {
foreach ($this->info as $namespace => $info) {
foreach ($info as $directive => $v) {
if (count((array) $v) == 1) {
$this->info[$namespace][$directive] = $v->type;
} elseif (count((array) $v) == 2 && isset($v->allow_null)) {
$this->info[$namespace][$directive] = -$v->type;
}
}
}
}
// DEPRECATED METHODS
@ -124,7 +162,6 @@ class HTMLPurifier_ConfigSchema {
/** @see HTMLPurifier_ConfigSchema->set() */
public static function define($namespace, $name, $default, $type, $description) {
HTMLPurifier_ConfigSchema::deprecated(__METHOD__);
// process modifiers (OPTIMIZE!)
$type_values = explode('/', $type, 2);
$type = $type_values[0];
$modifier = isset($type_values[1]) ? $type_values[1] : false;
@ -168,7 +205,8 @@ class HTMLPurifier_ConfigSchema {
/** @deprecated, use HTMLPurifier_VarParser->parse() */
public function validate($a, $b, $c = false) {
trigger_error("HTMLPurifier_ConfigSchema->validate deprecated, use HTMLPurifier_VarParser->parse instead", E_USER_NOTICE);
return $this->parser->parse($a, $b, $c);
$parser = new HTMLPurifier_VarParser();
return $parser->parse($a, $b, $c);
}
/**

View File

@ -43,6 +43,7 @@ class HTMLPurifier_ConfigSchema_Builder_ConfigSchema
);
}
}
$schema->postProcess();
return $schema;
}

View File

@ -111,7 +111,8 @@ class HTMLPurifier_ConfigSchema_Validator
if (!is_null($d->allowed) || !empty($d->valueAliases)) {
// allowed and valueAliases require that we be dealing with
// strings, so check for that early.
if (!isset(HTMLPurifier_VarParser::$stringTypes[$d->type])) {
$d_int = HTMLPurifier_VarParser::$types[$d->type];
if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) {
$this->error('type', 'must be a string type when used with allowed or value aliases');
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,15 @@
CSS.MaxImgLength
TYPE: string/null
DEFAULT: '1200px'
VERSION: 3.1.1
--DESCRIPTION--
<p>
This parameter sets the maximum allowed length on <code>img</code> tags,
effectively the <code>width</code> and <code>height</code> properties.
Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is
in place to prevent imagecrash attacks, disable with null at your own risk.
This directive is similar to %HTML.MaxImgLength, and both should be
concurrently edited, although there are
subtle differences in the input format (the CSS max is a number with
a unit).
</p>

View File

@ -0,0 +1,13 @@
HTML.MaxImgLength
TYPE: int/null
DEFAULT: 1200
VERSION: 3.1.1
--DESCRIPTION--
<p>
This directive controls the maximum number of pixels in the width and
height attributes in <code>img</code> tags. This is
in place to prevent imagecrash attacks, disable with null at your own risk.
This directive is similar to %CSS.MaxImgLength, and both should be
concurrently edited, although there are
subtle differences in the input format (the HTML max is an integer).
</p>

View File

@ -0,0 +1,13 @@
HTML.SafeEmbed
TYPE: bool
VERSION: 3.1.1
DEFAULT: false
--DESCRIPTION--
<p>
Whether or not to permit embed tags in documents, with a number of extra
security features added to prevent script execution. This is similar to
what websites like MySpace do to embed tags. Embed is a proprietary
element and will cause your website to stop validating. You probably want
to enable this with %HTML.SafeObject.
<strong>Highly experimental.</strong>
</p>

View File

@ -0,0 +1,13 @@
HTML.SafeObject
TYPE: bool
VERSION: 3.1.1
DEFAULT: false
--DESCRIPTION--
<p>
Whether or not to permit object tags in documents, with a number of extra
security features added to prevent script execution. This is similar to
what websites like MySpace do to object tags. You may also want to
enable %HTML.SafeEmbed for maximum interoperability with Internet Explorer,
although embed tags will cause your website to stop validating.
<strong>Highly experimental.</strong>
</p>

View File

@ -6,7 +6,7 @@ DEFAULT: NULL
<p>
Munges all browsable (usually http, https and ftp)
absolute URI's into another URI, usually a URI redirection service.
absolute URIs 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>).
@ -19,13 +19,64 @@ DEFAULT: NULL
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.
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
website. While this is poor usability practice, it is often mandated
in corporate environments.
</li>
</ul>
<p>
Prior to HTML Purifier 3.1.1, this directive also enabled the munging
of browsable external resources, which could break things if your redirection
script was a splash page or used <code>meta</code> tags. To revert to
previous behavior, please use %URI.MungeResources.
</p>
<p>
You may want to also use %URI.MungeSecretKey along with this directive
in order to enforce what URIs your redirector script allows. Open
redirector scripts can be a security risk and negatively affect the
reputation of your domain name.
</p>
<p>
Starting with HTML Purifier 3.1.1, there is also these substitutions:
</p>
<table>
<thead>
<tr>
<th>Key</th>
<th>Description</th>
<th>Example <code>&lt;a href=""&gt;</code></th>
</tr>
</thead>
<tbody>
<tr>
<td>%r</td>
<td>1 - The URI embeds a resource<br />(blank) - The URI is merely a link</td>
<td></td>
</tr>
<tr>
<td>%n</td>
<td>The name of the tag this URI came from</td>
<td>a</td>
</tr>
<tr>
<td>%m</td>
<td>The name of the attribute this URI came from</td>
<td>href</td>
</tr>
<tr>
<td>%p</td>
<td>The name of the CSS property this URI came from, or blank if irrelevant</td>
<td></td>
</tr>
</tbody>
</table>
<p>
Admittedly, these letters are somewhat arbitrary; the only stipulation
was that they couldn't be a through f. r is for resource (I would have preferred
e, but you take what you can get), n is for name, m
was picked because it came after n (and I couldn't use a), p is for
property.
</p>

View File

@ -0,0 +1,16 @@
URI.MungeResources
TYPE: bool
VERSION: 3.1.1
DEFAULT: false
--DESCRIPTION--
<p>
If true, any URI munging directives like %URI.Munge
will also apply to embedded resources, such as <code>&lt;img src=""&gt;</code>.
Be careful enabling this directive if you have a redirector script
that does not use the <code>Location</code> HTTP header; all of your images
and other embedded resources will break.
</p>
<p>
<strong>Warning:</strong> It is strongly advised you use this in conjunction
%URI.MungeSecretKey to mitigate the security risk of an open redirector.
</p>

View File

@ -0,0 +1,29 @@
URI.MungeSecretKey
TYPE: string/null
VERSION: 3.1.1
DEFAULT: NULL
--DESCRIPTION--
<p>
This directive enables secure checksum generation along with %URI.Munge.
It should be set to a secure key that is not shared with anyone else.
The checksum can be placed in the URI using %t. Use of this checksum
affords an additional level of protection by allowing a redirector
to check if a URI has passed through HTML Purifier with this line:
</p>
<pre>$checksum === sha1($secret_key . ':' . $url)</pre>
<p>
If the output is TRUE, the redirector script should accept the URI.
</p>
<p>
Please note that it would still be possible for an attacker to procure
secure hashes en-mass by abusing your website's Preview feature or the
like, but this service affords an additional level of protection
that should be combined with website blacklisting.
</p>
<p>
Remember this has no effect if %URI.Munge is not on.
</p>

View File

@ -46,35 +46,13 @@ class HTMLPurifier_Encoder
*/
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');
// UTF-8 validity is checked since PHP 4.3.5
// This is an optimization: if the string is already valid UTF-8, no
// need to do iconv/php stuff. 99% of the time, this will be the case.
if (preg_match('/^.{1}/us', $str)) {
return strtr($str, $non_sgml_chars);
}
if ($iconv && !$force_php) {
// do the shortcut way
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
$str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
restore_error_handler();
return strtr($str, $non_sgml_chars);
// need to do PHP stuff. 99% of the time, this will be the case.
// The regexp matches the XML char production, as well as well as excluding
// non-SGML codepoints U+007F to U+009F
if (preg_match('/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', $str)) {
return $str;
}
$mState = 0; // cached expected number of octets after the current octet
@ -185,7 +163,17 @@ class HTMLPurifier_Encoder
) {
} elseif (0xFEFF != $mUcs4 && // omit BOM
!($mUcs4 >= 128 && $mUcs4 <= 159) // omit non-SGML
// check for valid Char unicode codepoints
(
0x9 == $mUcs4 ||
0xA == $mUcs4 ||
0xD == $mUcs4 ||
(0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
// 7F-9F is not strictly prohibited by XML,
// but it is non-SGML, and thus we don't allow it
(0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
(0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
)
) {
$out .= $char;
}
@ -276,17 +264,20 @@ class HTMLPurifier_Encoder
* 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;
static $iconv = null;
if ($iconv === null) $iconv = function_exists('iconv');
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
$str = iconv($encoding, 'utf-8//IGNORE', $str);
// If the string is bjorked by Shift_JIS or a similar encoding
// that doesn't support all of ASCII, convert the naughty
// characters to their true byte-wise ASCII/UTF-8 equivalents.
$str = strtr($str, HTMLPurifier_Encoder::testEncodingSupportsASCII($encoding));
restore_error_handler();
return $str;
} elseif ($encoding === 'iso-8859-1') {
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
$str = utf8_encode($str);
restore_error_handler();
return $str;
@ -300,20 +291,28 @@ class HTMLPurifier_Encoder
* 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')) {
static $iconv = null;
if ($iconv === null) $iconv = function_exists('iconv');
if ($escape = $config->get('Core', 'EscapeNonASCIICharacters')) {
$str = HTMLPurifier_Encoder::convertToASCIIDumbLossless($str);
}
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
if ($iconv && !$config->get('Test', 'ForceNoIconv')) {
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
// Undo our previous fix in convertToUTF8, otherwise iconv will barf
$ascii_fix = HTMLPurifier_Encoder::testEncodingSupportsASCII($encoding);
if (!$escape && !empty($ascii_fix)) {
$clear_fix = array();
foreach ($ascii_fix as $utf8 => $native) $clear_fix[$utf8] = '';
$str = strtr($str, $clear_fix);
}
$str = strtr($str, array_flip($ascii_fix));
// Normal stuff
$str = iconv('utf-8', $encoding . '//IGNORE', $str);
restore_error_handler();
return $str;
} elseif ($encoding === 'iso-8859-1') {
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
$str = utf8_decode($str);
restore_error_handler();
return $str;
@ -368,6 +367,47 @@ class HTMLPurifier_Encoder
return $result;
}
/**
* This expensive function tests whether or not a given character
* encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
* fail this test, and require special processing. Variable width
* encodings shouldn't ever fail.
*
* @param string $encoding Encoding name to test, as per iconv format
* @param bool $bypass Whether or not to bypass the precompiled arrays.
* @return Array of UTF-8 characters to their corresponding ASCII,
* which can be used to "undo" any overzealous iconv action.
*/
public static function testEncodingSupportsASCII($encoding, $bypass = false) {
static $encodings = array();
if (!$bypass) {
if (isset($encodings[$encoding])) return $encodings[$encoding];
$lenc = strtolower($encoding);
switch ($lenc) {
case 'shift_jis':
return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
case 'johab':
return array("\xE2\x82\xA9" => '\\');
}
if (strpos($lenc, 'iso-8859-') === 0) return array();
}
$ret = array();
set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
if (iconv('UTF-8', $encoding, 'a') === false) return false;
for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
$c = chr($i);
if (iconv('UTF-8', "$encoding//IGNORE", $c) === '') {
// Reverse engineer: what's the UTF-8 equiv of this byte
// sequence? This assumes that there's no variable width
// encoding that doesn't support ASCII.
$ret[iconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
}
}
restore_error_handler();
$encodings[$encoding] = $ret;
return $ret;
}
}

View File

@ -35,8 +35,7 @@ class HTMLPurifier_Generator
* @param $config Instance of HTMLPurifier_Config
* @param $context Instance of HTMLPurifier_Context
*/
public function __construct($config = null, $context = null) {
if (!$config) $config = HTMLPurifier_Config::createDefault();
public function __construct($config, $context) {
$this->config = $config;
$this->_scriptFix = $config->get('Output', 'CommentScriptContents');
$this->_def = $config->getHTMLDefinition();

View File

@ -76,6 +76,11 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
*/
public $info_content_sets = array();
/**
* Indexed list of HTMLPurifier_Injector to be used.
*/
public $info_injector = array();
/**
* Doctype object
*/
@ -186,18 +191,22 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
$this->doctype = $this->manager->doctype;
foreach ($this->manager->modules as $module) {
foreach($module->info_tag_transform as $k => $v) {
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) {
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) {
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;
}
foreach ($module->info_injector as $k => $v) {
if ($v === false) unset($this->info_injector[$k]);
else $this->info_injector[$k] = $v;
}
}
$this->info = $this->manager->getElements();
@ -356,6 +365,14 @@ class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
}
}
// setup injectors -----------------------------------------------------
foreach ($this->info_injector as $i => $injector) {
if ($injector->checkNeeded($config) !== false) {
// remove injector that does not have it's required
// elements/attributes present, and is thus not needed.
unset($this->info_injector[$i]);
}
}
}
/**

View File

@ -71,6 +71,14 @@ class HTMLPurifier_HTMLModule
*/
public $info_attr_transform_post = array();
/**
* List of HTMLPurifier_Injector to be performed during well-formedness fixing.
* An injector will only be invoked if all of it's pre-requisites are met;
* if an injector fails setup, there will be no error; it will simply be
* silently disabled.
*/
public $info_injector = array();
/**
* Boolean flag that indicates whether or not getChildDef is implemented.
* For optimization reasons: may save a call to a function. Be sure
@ -222,5 +230,14 @@ class HTMLPurifier_HTMLModule
}
return $ret;
}
/**
* Lazy load construction of the module after determining whether
* or not it's needed, and also when a finalized configuration object
* is available.
* @param $config Instance of HTMLPurifier_Config
*/
public function setup($config) {}
}

View File

@ -12,7 +12,7 @@ class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
'I18N' => array('dir' => false)
);
public function __construct() {
public function setup($config) {
$bdo = $this->addElement(
'bdo', 'Inline', 'Inline', array('Core', 'Lang'),
array(

View File

@ -9,7 +9,7 @@ class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
public $name = 'Edit';
public function __construct() {
public function setup($config) {
$contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
$attr = array(
'cite' => 'URI',

View File

@ -8,7 +8,7 @@ class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
public $name = 'Hypertext';
public function __construct() {
public function setup($config) {
$a = $this->addElement(
'a', 'Inline', 'Inline', 'Common',
array(

View File

@ -10,17 +10,25 @@ class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
public $name = 'Image';
public function __construct() {
public function setup($config) {
$max = $config->get('HTML', 'MaxImgLength');
$img = $this->addElement(
'img', 'Inline', 'Empty', 'Common',
array(
'alt*' => 'Text',
'height' => 'Length',
// According to the spec, it's Length, but percents can
// be abused, so we allow only Pixels.
'height' => 'Pixels#' . $max,
'width' => 'Pixels#' . $max,
'longdesc' => 'URI',
'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
'width' => 'Length'
)
);
if ($max === null || $config->get('HTML', 'Trusted')) {
$img->attr['height'] =
$img->attr['width'] = 'Length';
}
// kind of strange, but splitting things up would be inefficient
$img->attr_transform_pre[] =
$img->attr_transform_post[] =

View File

@ -21,7 +21,7 @@ class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
public $name = 'Legacy';
public function __construct() {
public function setup($config) {
$this->addElement('basefont', 'Inline', 'Empty', false, array(
'color' => 'Color',

View File

@ -19,7 +19,7 @@ class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
public $content_sets = array('Flow' => 'List');
public function __construct() {
public function setup($config) {
$this->addElement('ol', 'List', 'Required: li', 'Common');
$this->addElement('ul', 'List', 'Required: li', 'Common');
$this->addElement('dl', 'List', 'Required: dt | dd', 'Common');

View File

@ -11,7 +11,7 @@ class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
public $name = 'Object';
public $safe = false;
public function __construct() {
public function setup($config) {
$this->addElement('object', 'Inline', 'Optional: #PCDATA | Flow | param', 'Common',
array(

View File

@ -15,7 +15,7 @@ class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
public $name = 'Presentation';
public function __construct() {
public function setup($config) {
$this->addElement('b', 'Inline', 'Inline', 'Common');
$this->addElement('big', 'Inline', 'Inline', 'Common');
$this->addElement('hr', 'Block', 'Empty', 'Common');

View File

@ -9,7 +9,7 @@ class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
public $name = 'Proprietary';
public function __construct() {
public function setup($config) {
$this->addElement('marquee', 'Inline', 'Flow', 'Common',
array(

View File

@ -9,7 +9,7 @@ class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
public $name = 'Ruby';
public function __construct() {
public function setup($config) {
$this->addElement('ruby', 'Inline',
'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
'Common');

View File

@ -0,0 +1,31 @@
<?php
/**
* A "safe" embed module. See SafeObject. This is a proprietary element.
*/
class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
{
public $name = 'SafeEmbed';
public function setup($config) {
$max = $config->get('HTML', 'MaxImgLength');
$embed = $this->addElement(
'embed', 'Inline', 'Empty', 'Common',
array(
'src*' => 'URI#embedded',
'type' => 'Enum#application/x-shockwave-flash',
'width' => 'Pixels#' . $max,
'height' => 'Pixels#' . $max,
'allowscriptaccess' => 'Enum#never',
'allownetworking' => 'Enum#internal',
'wmode' => 'Enum#window',
'name' => 'ID',
)
);
$embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* A "safe" object module. In theory, objects permitted by this module will
* be safe, and untrusted users can be allowed to embed arbitrary flash objects
* (maybe other types too, but only Flash is supported as of right now).
* Highly experimental.
*/
class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
{
public $name = 'SafeObject';
public function setup($config) {
// These definitions are not intrinsically safe: the attribute transforms
// are a vital part of ensuring safety.
$max = $config->get('HTML', 'MaxImgLength');
$object = $this->addElement(
'object',
'Inline',
'Optional: param | Flow | #PCDATA',
'Common',
array(
// While technically not required by the spec, we're forcing
// it to this value.
'type' => 'Enum#application/x-shockwave-flash',
'width' => 'Pixels#' . $max,
'height' => 'Pixels#' . $max,
'data' => 'URI#embedded'
)
);
$object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
$param = $this->addElement('param', false, 'Empty', false,
array(
'id' => 'ID',
'name*' => 'Text',
'value' => 'Text'
)
);
$param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
$this->info_injector[] = 'SafeObject';
}
}

View File

@ -20,7 +20,7 @@ class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
public $safe = false;
public function __construct() {
public function setup($config) {
// TODO: create custom child-definition for noscript that
// auto-wraps stray #PCDATA in a similar manner to
// blockquote's custom definition (we would use it but

View File

@ -15,7 +15,7 @@ class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
'Core' => array(0 => array('Style'))
);
public function __construct() {
public function setup($config) {
$this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
}

View File

@ -8,7 +8,7 @@ class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
public $name = 'Tables';
public function __construct() {
public function setup($config) {
$this->addElement('caption', false, 'Inline', 'Common');

View File

@ -8,7 +8,7 @@ class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
public $name = 'Target';
public function __construct() {
public function setup($config) {
$elements = array('a');
foreach ($elements as $name) {
$e = $this->addBlankElement($name);

View File

@ -20,7 +20,7 @@ class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
'Flow' => 'Heading | Block | Inline'
);
public function __construct() {
public function setup($config) {
// Inline Phrasal -------------------------------------------------
$this->addElement('abbr', 'Inline', 'Inline', 'Common');

View File

@ -35,7 +35,7 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
* @todo Wildcard matching and error reporting when an added or
* subtracted fix has no effect.
*/
public function construct($config) {
public function setup($config) {
// create fixes, initialize fixesForLevel
$fixes = $this->makeFixes();

View File

@ -221,15 +221,35 @@ class HTMLPurifier_HTMLModuleManager
$modules[] = 'Proprietary';
}
// add SafeObject/Safeembed modules
if ($config->get('HTML', 'SafeObject')) {
$modules[] = 'SafeObject';
}
if ($config->get('HTML', 'SafeEmbed')) {
$modules[] = 'SafeEmbed';
}
foreach ($modules as $module) {
$this->processModule($module);
$this->modules[$module]->setup($config);
}
foreach ($this->doctype->tidyModules as $module) {
$this->processModule($module);
if (method_exists($this->modules[$module], 'construct')) {
$this->modules[$module]->construct($config);
$this->modules[$module]->setup($config);
}
// prepare any injectors
foreach ($this->modules as $module) {
$n = array();
foreach ($module->info_injector as $i => $injector) {
if (!is_object($injector)) {
$class = "HTMLPurifier_Injector_$injector";
$injector = new $class;
}
$n[$injector->name] = $injector;
}
$module->info_injector = $n;
}
// setup lookup table based on all valid modules

View File

@ -58,29 +58,45 @@ abstract class HTMLPurifier_Injector
* Prepares the injector by giving it the config and context objects:
* this allows references to important variables to be made within
* the injector. This function also checks if the HTML environment
* will work with the Injector: if p tags are not allowed, the
* Auto-Paragraphing injector should not be enabled.
* will work with the Injector (see checkNeeded()).
* @param $config Instance of HTMLPurifier_Config
* @param $context Instance of HTMLPurifier_Context
* @return Boolean false if success, string of missing needed element/attribute if failure
*/
public function prepare($config, $context) {
$this->htmlDefinition = $config->getHTMLDefinition();
// perform $needed checks
foreach ($this->needed as $element => $attributes) {
if (is_int($element)) $element = $attributes;
if (!isset($this->htmlDefinition->info[$element])) return $element;
if (!is_array($attributes)) continue;
foreach ($attributes as $name) {
if (!isset($this->htmlDefinition->info[$element]->attr[$name])) return "$element.$name";
}
}
// Even though this might fail, some unit tests ignore this and
// still test checkNeeded, so be careful. Maybe get rid of that
// dependency.
$result = $this->checkNeeded($config);
if ($result !== false) return $result;
$this->currentNesting =& $context->get('CurrentNesting');
$this->inputTokens =& $context->get('InputTokens');
$this->inputIndex =& $context->get('InputIndex');
return false;
}
/**
* This function checks if the HTML environment
* will work with the Injector: if p tags are not allowed, the
* Auto-Paragraphing injector should not be enabled.
* @param $config Instance of HTMLPurifier_Config
* @param $context Instance of HTMLPurifier_Context
* @return Boolean false if success, string of missing needed element/attribute if failure
*/
public function checkNeeded($config) {
$def = $config->getHTMLDefinition();
foreach ($this->needed as $element => $attributes) {
if (is_int($element)) $element = $attributes;
if (!isset($def->info[$element])) return $element;
if (!is_array($attributes)) continue;
foreach ($attributes as $name) {
if (!isset($def->info[$element]->attr[$name])) return "$element.$name";
}
}
return false;
}
/**
* Tests if the context node allows a certain element
* @param $name Name of element to test for

View File

@ -0,0 +1,83 @@
<?php
/**
* Adds important param elements to inside of object in order to make
* things safe.
*/
class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
{
public $name = 'SafeObject';
public $needed = array('object', 'param');
protected $objectStack = array();
protected $paramStack = array();
// Keep this synchronized with AttrTransform/SafeParam.php
protected $addParam = array(
'allowScriptAccess' => 'never',
'allowNetworking' => 'internal',
);
protected $allowedParam = array(
'wmode' => true,
'movie' => true,
);
public function prepare($config, $context) {
parent::prepare($config, $context);
}
public function handleElement(&$token) {
if ($token->name == 'object') {
$this->objectStack[] = $token;
$this->paramStack[] = array();
$new = array($token);
foreach ($this->addParam as $name => $value) {
$new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
}
$token = $new;
} elseif ($token->name == 'param') {
$nest = count($this->currentNesting) - 1;
if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
$i = count($this->objectStack) - 1;
if (!isset($token->attr['name'])) {
$token = false;
return;
}
$n = $token->attr['name'];
// We need this fix because YouTube doesn't supply a data
// attribute, which we need if a type is specified. This is
// *very* Flash specific.
if (!isset($this->objectStack[$i]->attr['data']) && $token->attr['name'] == 'movie') {
$this->objectStack[$i]->attr['data'] = $token->attr['value'];
}
// Check if the parameter is the correct value but has not
// already been added
if (
!isset($this->paramStack[$i][$n]) &&
isset($this->addParam[$n]) &&
$token->attr['name'] === $this->addParam[$n]
) {
// keep token, and add to param stack
$this->paramStack[$i][$n] = true;
} elseif (isset($this->allowedParam[$n])) {
// keep token, don't do anything to it
// (could possibly check for duplicates here)
} else {
$token = false;
}
} else {
// not directly inside an object, DENY!
$token = false;
}
}
}
public function notifyEnd($token) {
if ($token->name == 'object') {
array_pop($this->objectStack);
array_pop($this->paramStack);
}
}
}

View File

@ -0,0 +1,113 @@
<?php
/**
* Represents a measurable length, with a string numeric magnitude
* and a unit. This object is immutable.
*/
class HTMLPurifier_Length
{
/**
* String numeric magnitude.
*/
protected $n;
/**
* String unit. False is permitted if $n = 0.
*/
protected $unit;
/**
* Whether or not this length is valid. Null if not calculated yet.
*/
protected $isValid;
/**
* Lookup array of units recognized by CSS 2.1
*/
protected static $allowedUnits = array(
'em' => true, 'ex' => true, 'px' => true, 'in' => true,
'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true
);
/**
* @param number $n Magnitude
* @param string $u Unit
*/
public function __construct($n = '0', $u = false) {
$this->n = (string) $n;
$this->unit = $u !== false ? (string) $u : false;
}
/**
* @param string $s Unit string, like '2em' or '3.4in'
* @warning Does not perform validation.
*/
static public function make($s) {
if ($s instanceof HTMLPurifier_Length) return $s;
$n_length = strspn($s, '1234567890.+-');
$n = substr($s, 0, $n_length);
$unit = substr($s, $n_length);
if ($unit === '') $unit = false;
return new HTMLPurifier_Length($n, $unit);
}
/**
* Validates the number and unit.
*/
protected function validate() {
// Special case:
if ($this->n === '+0' || $this->n === '-0') $this->n = '0';
if ($this->n === '0' && $this->unit === false) return true;
if (!ctype_lower($this->unit)) $this->unit = strtolower($this->unit);
if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) return false;
// Hack:
$def = new HTMLPurifier_AttrDef_CSS_Number();
$result = $def->validate($this->n, false, false);
if ($result === false) return false;
$this->n = $result;
return true;
}
/**
* Returns string representation of number.
*/
public function toString() {
if (!$this->isValid()) return false;
return $this->n . $this->unit;
}
/**
* Retrieves string numeric magnitude.
*/
public function getN() {return $this->n;}
/**
* Retrieves string unit.
*/
public function getUnit() {return $this->unit;}
/**
* Returns true if this length unit is valid.
*/
public function isValid() {
if ($this->isValid === null) $this->isValid = $this->validate();
return $this->isValid;
}
/**
* Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
* @warning If both values are too large or small, this calculation will
* not work properly
*/
public function compareTo($l) {
if ($l === false) return false;
if ($l->unit !== $this->unit) {
$converter = new HTMLPurifier_UnitConverter();
$l = $converter->convert($l, $this->unit);
if ($l === false) return false;
}
return $this->n - $l->n;
}
}

View File

@ -20,18 +20,15 @@ class HTMLPurifier_Printer
* Initialize $generator.
*/
public function __construct() {
$this->generator = new HTMLPurifier_Generator();
}
/**
* Give generator necessary configuration if possible
*/
public function prepareGenerator($config) {
// hack for smoketests/configForm.php
$all = $config->getAll();
if (empty($all['HTML'])) return;
$context = new HTMLPurifier_Context();
$this->generator->generateFromTokens(array(), $config, $context);
$this->generator = new HTMLPurifier_Generator($config, $context);
}
/**

View File

@ -1,5 +1,8 @@
<?php
/**
* @todo Rewrite to use Interchange objects
*/
class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
{
@ -38,8 +41,8 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
$this->name = $name;
$this->compress = $compress;
// initialize sub-printers
$this->fields['default'] = new HTMLPurifier_Printer_ConfigForm_default();
$this->fields['bool'] = new HTMLPurifier_Printer_ConfigForm_bool();
$this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default();
$this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool();
}
/**
@ -68,14 +71,23 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
/**
* Returns HTML output for a configuration form
* @param $config Configuration object of current form state
* @param $config Configuration object of current form state, or an array
* where [0] has an HTML namespace and [1] is being rendered.
* @param $allowed Optional namespace(s) and directives to restrict form to.
*/
public function render($config, $allowed = true, $render_controls = true) {
$this->config = $config;
$this->prepareGenerator($config);
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
} else {
$gen_config = $config;
}
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed);
$this->config = $config;
$this->genConfig = $gen_config;
$this->prepareGenerator($gen_config);
$allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def);
$all = array();
foreach ($allowed as $key) {
list($ns, $directive) = $key;
@ -148,13 +160,19 @@ class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
$ret .= $this->start('td');
$def = $this->config->def->info[$ns][$directive];
$type = $def->type;
if (!isset($this->fields[$type])) $type = 'default';
if (is_int($def)) {
$allow_null = $def < 0;
$type = abs($def);
} else {
$type = $def->type;
$allow_null = isset($def->allow_null);
}
if (!isset($this->fields[$type])) $type = 0; // default
$type_obj = $this->fields[$type];
if ($def->allow_null) {
if ($allow_null) {
$type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
}
$ret .= $type_obj->render($ns, $directive, $value, $this->name, $this->config);
$ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config));
$ret .= $this->end('td');
$ret .= $this->end('tr');
}
@ -180,7 +198,14 @@ class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer
$this->obj = $obj;
}
public function render($ns, $directive, $value, $name, $config) {
$this->prepareGenerator($config);
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
} else {
$gen_config = $config;
}
$this->prepareGenerator($gen_config);
$ret = '';
$ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive"));
$ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
@ -202,7 +227,7 @@ class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer
$ret .= $this->elementEmpty('input', $attr);
$ret .= $this->text(' or ');
$ret .= $this->elementEmpty('br');
$ret .= $this->obj->render($ns, $directive, $value, $name, $config);
$ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config));
return $ret;
}
}
@ -214,22 +239,33 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
public $cols = 18;
public $rows = 5;
public function render($ns, $directive, $value, $name, $config) {
$this->prepareGenerator($config);
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
} else {
$gen_config = $config;
}
$this->prepareGenerator($gen_config);
// this should probably be split up a little
$ret = '';
$def = $config->def->info[$ns][$directive];
if (is_int($def)) {
$type = abs($def);
} else {
$type = $def->type;
}
if (is_array($value)) {
switch ($def->type) {
case 'lookup':
switch ($type) {
case HTMLPurifier_VarParser::LOOKUP:
$array = $value;
$value = array();
foreach ($array as $val => $b) {
$value[] = $val;
}
case 'list':
case HTMLPurifier_VarParser::ALIST:
$value = implode(PHP_EOL, $value);
break;
case 'hash':
case HTMLPurifier_VarParser::HASH:
$nvalue = '';
foreach ($value as $i => $v) {
$nvalue .= "$i:$v" . PHP_EOL;
@ -240,7 +276,7 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
$value = '';
}
}
if ($def->type === 'mixed') {
if ($type === HTMLPurifier_VarParser::MIXED) {
return 'Not supported';
$value = serialize($value);
}
@ -249,7 +285,7 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
'id' => "$name:$ns.$directive"
);
if ($value === null) $attr['disabled'] = 'disabled';
if (is_array($def->allowed)) {
if (isset($def->allowed)) {
$ret .= $this->start('select', $attr);
foreach ($def->allowed as $val => $b) {
$attr = array();
@ -258,8 +294,11 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
}
$ret .= $this->end('select');
} elseif (
$def->type == 'text' || $def->type == 'itext' ||
$def->type == 'list' || $def->type == 'hash' || $def->type == 'lookup'
$type === HTMLPurifier_VarParser::TEXT ||
$type === HTMLPurifier_VarParser::ITEXT ||
$type === HTMLPurifier_VarParser::ALIST ||
$type === HTMLPurifier_VarParser::HASH ||
$type === HTMLPurifier_VarParser::LOOKUP
) {
$attr['cols'] = $this->cols;
$attr['rows'] = $this->rows;
@ -280,7 +319,13 @@ class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
*/
class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer {
public function render($ns, $directive, $value, $name, $config) {
$this->prepareGenerator($config);
if (is_array($config) && isset($config[0])) {
$gen_config = $config[0];
$config = $config[1];
} else {
$gen_config = $config;
}
$this->prepareGenerator($gen_config);
$ret = '';
$ret .= $this->start('div', array('id' => "$name:$ns.$directive"));

View File

@ -38,6 +38,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
$this->injectors = array();
$injectors = $config->getBatch('AutoFormat');
$def_injectors = $definition->info_injector;
$custom_injectors = $injectors['Custom'];
unset($injectors['Custom']); // special case
foreach ($injectors as $injector => $b) {
@ -45,6 +46,10 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if (!$b) continue;
$this->injectors[] = new $injector;
}
foreach ($def_injectors as $injector) {
// assumed to be objects
$this->injectors[] = $injector;
}
foreach ($custom_injectors as $injector) {
if (is_string($injector)) {
$injector = "HTMLPurifier_Injector_$injector";
@ -169,7 +174,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($escape_invalid_tags) {
if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
$result[] = new HTMLPurifier_Token_Text(
$generator->generateFromToken($token, $config, $context)
$generator->generateFromToken($token)
);
} elseif ($e) {
$e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
@ -209,7 +214,7 @@ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
if ($skipped_tags === false) {
if ($escape_invalid_tags) {
$result[] = new HTMLPurifier_Token_Text(
$generator->generateFromToken($token, $config, $context)
$generator->generateFromToken($token)
);
if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
} elseif ($e) {

View File

@ -101,7 +101,7 @@ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
// invalid tag, generate HTML representation and insert in
if ($e) $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
$token = new HTMLPurifier_Token_Text(
$generator->generateFromToken($token, $config, $context)
$generator->generateFromToken($token)
);
} else {
// check if we need to destroy all of the tag's children

View File

@ -4,7 +4,6 @@
* Abstract base token class that all others inherit from.
*/
class HTMLPurifier_Token {
public $type; /**< Type of node to bypass <tt>is_a()</tt>. */
public $line; /**< Line number node was on in source document. Null if unknown. */
/**

View File

@ -128,6 +128,17 @@ class HTMLPurifier_URI
$this->path = ''; // just to be safe
}
// qf = query and fragment
$qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
if (!is_null($this->query)) {
$this->query = $qf_encoder->encode($this->query);
}
if (!is_null($this->fragment)) {
$this->fragment = $qf_encoder->encode($this->fragment);
}
return true;
}

View File

@ -5,6 +5,7 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
public $type = 'URI';
protected $filters = array();
protected $postFilters = array();
protected $registeredFilters = array();
/**
@ -27,6 +28,7 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
$this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
$this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
$this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
$this->registerFilter(new HTMLPurifier_URIFilter_Munge());
}
public function registerFilter($filter) {
@ -34,8 +36,13 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
}
public function addFilter($filter, $config) {
$filter->prepare($config);
$this->filters[$filter->name] = $filter;
$r = $filter->prepare($config);
if ($r === false) return; // null is ok, for backwards compat
if ($filter->post) {
$this->postFilters[$filter->name] = $filter;
} else {
$this->filters[$filter->name] = $filter;
}
}
protected function doSetup($config) {
@ -66,8 +73,16 @@ class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
}
public function filter(&$uri, $config, $context) {
foreach ($this->filters as $name => $x) {
$result = $this->filters[$name]->filter($uri, $config, $context);
foreach ($this->filters as $name => $f) {
$result = $f->filter($uri, $config, $context);
if (!$result) return false;
}
return true;
}
public function postFilter(&$uri, $config, $context) {
foreach ($this->postFilters as $name => $f) {
$result = $f->filter($uri, $config, $context);
if (!$result) return false;
}
return true;

View File

@ -19,10 +19,15 @@ abstract class HTMLPurifier_URIFilter
*/
public $name;
/**
* True if this filter should be run after scheme validation.
*/
public $post = false;
/**
* Performs initialization for the filter
*/
public function prepare($config) {}
public function prepare($config) {return true;}
/**
* Filter a URI object

View File

@ -6,6 +6,7 @@ class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
protected $blacklist = array();
public function prepare($config) {
$this->blacklist = $config->get('URI', 'HostBlacklist');
return true;
}
public function filter(&$uri, $config, $context) {
foreach($this->blacklist as $blacklisted_host_fragment) {

View File

@ -11,14 +11,15 @@ class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
$def = $config->getDefinition('URI');
$this->base = $def->base;
if (is_null($this->base)) {
trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_ERROR);
return;
trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_WARNING);
return false;
}
$this->base->fragment = null; // fragment is invalid for base URI
$stack = explode('/', $this->base->path);
array_pop($stack); // discard last segment
$stack = $this->_collapseStack($stack); // do pre-parsing
$this->basePathStack = $stack;
return true;
}
public function filter(&$uri, $config, $context) {
if (is_null($this->base)) return true; // abort early

View File

@ -0,0 +1,48 @@
<?php
class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
{
public $name = 'Munge';
public $post = true;
private $target, $parser, $doEmbed, $secretKey;
protected $replace = array();
public function prepare($config) {
$this->target = $config->get('URI', $this->name);
$this->parser = new HTMLPurifier_URIParser();
$this->doEmbed = $config->get('URI', 'MungeResources');
$this->secretKey = $config->get('URI', 'MungeSecretKey');
return true;
}
public function filter(&$uri, $config, $context) {
if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true;
$scheme_obj = $uri->getSchemeObj($config, $context);
if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it
if (is_null($uri->host) || empty($scheme_obj->browsable)) {
return true;
}
$this->makeReplace($uri, $config, $context);
$this->replace = array_map('rawurlencode', $this->replace);
$new_uri = strtr($this->target, $this->replace);
$uri = $this->parser->parse($new_uri); // overwrite
return true;
}
protected function makeReplace($uri, $config, $context) {
$string = $uri->toString();
// always available
$this->replace['%s'] = $string;
$this->replace['%r'] = $context->get('EmbeddedURI', true);
$token = $context->get('CurrentToken', true);
$this->replace['%n'] = $token ? $token->name : null;
$this->replace['%m'] = $context->get('CurrentAttr', true);
$this->replace['%p'] = $context->get('CurrentCSSProperty', true);
// not always available
if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string);
}
}

View File

@ -0,0 +1,240 @@
<?php
/**
* Class for converting between different unit-lengths as specified by
* CSS.
*/
class HTMLPurifier_UnitConverter
{
const ENGLISH = 1;
const METRIC = 2;
const DIGITAL = 3;
/**
* Units information array. Units are grouped into measuring systems
* (English, Metric), and are assigned an integer representing
* the conversion factor between that unit and the smallest unit in
* the system. Numeric indexes are actually magical constants that
* encode conversion data from one system to the next, with a O(n^2)
* constraint on memory (this is generally not a problem, since
* the number of measuring systems is small.)
*/
protected static $units = array(
self::ENGLISH => array(
'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
'pt' => 4,
'pc' => 48,
'in' => 288,
self::METRIC => array('pt', '0.352777778', 'mm'),
),
self::METRIC => array(
'mm' => 1,
'cm' => 10,
self::ENGLISH => array('mm', '2.83464567', 'pt'),
),
);
/**
* Minimum bcmath precision for output.
*/
protected $outputPrecision;
/**
* Bcmath precision for internal calculations.
*/
protected $internalPrecision;
/**
* Whether or not BCMath is available
*/
private $bcmath;
public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) {
$this->outputPrecision = $output_precision;
$this->internalPrecision = $internal_precision;
$this->bcmath = !$force_no_bcmath && function_exists('bcmul');
}
/**
* Converts a length object of one unit into another unit.
* @param HTMLPurifier_Length $length
* Instance of HTMLPurifier_Length to convert. You must validate()
* it before passing it here!
* @param string $to_unit
* Unit to convert to.
* @note
* About precision: This conversion function pays very special
* attention to the incoming precision of values and attempts
* to maintain a number of significant figure. Results are
* fairly accurate up to nine digits. Some caveats:
* - If a number is zero-padded as a result of this significant
* figure tracking, the zeroes will be eliminated.
* - If a number contains less than four sigfigs ($outputPrecision)
* and this causes some decimals to be excluded, those
* decimals will be added on.
*/
public function convert($length, $to_unit) {
if (!$length->isValid()) return false;
$n = $length->getN();
$unit = $length->getUnit();
if ($n === '0' || $unit === false) {
return new HTMLPurifier_Length('0', false);
}
$state = $dest_state = false;
foreach (self::$units as $k => $x) {
if (isset($x[$unit])) $state = $k;
if (isset($x[$to_unit])) $dest_state = $k;
}
if (!$state || !$dest_state) return false;
// Some calculations about the initial precision of the number;
// this will be useful when we need to do final rounding.
$sigfigs = $this->getSigFigs($n);
if ($sigfigs < $this->outputPrecision) $sigfigs = $this->outputPrecision;
// BCMath's internal precision deals only with decimals. Use
// our default if the initial number has no decimals, or increase
// it by how ever many decimals, thus, the number of guard digits
// will always be greater than or equal to internalPrecision.
$log = (int) floor(log(abs($n), 10));
$cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
for ($i = 0; $i < 2; $i++) {
// Determine what unit IN THIS SYSTEM we need to convert to
if ($dest_state === $state) {
// Simple conversion
$dest_unit = $to_unit;
} else {
// Convert to the smallest unit, pending a system shift
$dest_unit = self::$units[$state][$dest_state][0];
}
// Do the conversion if necessary
if ($dest_unit !== $unit) {
$factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
$n = $this->mul($n, $factor, $cp);
$unit = $dest_unit;
}
// Output was zero, so bail out early. Shouldn't ever happen.
if ($n === '') {
$n = '0';
$unit = $to_unit;
break;
}
// It was a simple conversion, so bail out
if ($dest_state === $state) {
break;
}
if ($i !== 0) {
// Conversion failed! Apparently, the system we forwarded
// to didn't have this unit. This should never happen!
return false;
}
// Pre-condition: $i == 0
// Perform conversion to next system of units
$n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
$unit = self::$units[$state][$dest_state][2];
$state = $dest_state;
// One more loop around to convert the unit in the new system.
}
// Post-condition: $unit == $to_unit
if ($unit !== $to_unit) return false;
// Useful for debugging:
//echo "<pre>n";
//echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
$n = $this->round($n, $sigfigs);
if (strpos($n, '.') !== false) $n = rtrim($n, '0');
$n = rtrim($n, '.');
return new HTMLPurifier_Length($n, $unit);
}
/**
* Returns the number of significant figures in a string number.
* @param string $n Decimal number
* @return int number of sigfigs
*/
public function getSigFigs($n) {
$n = ltrim($n, '0+-');
$dp = strpos($n, '.'); // decimal position
if ($dp === false) {
$sigfigs = strlen(rtrim($n, '0'));
} else {
$sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
if ($dp !== 0) $sigfigs--;
}
return $sigfigs;
}
/**
* Adds two numbers, using arbitrary precision when available.
*/
private function add($s1, $s2, $scale) {
if ($this->bcmath) return bcadd($s1, $s2, $scale);
else return $this->scale($s1 + $s2, $scale);
}
/**
* Multiples two numbers, using arbitrary precision when available.
*/
private function mul($s1, $s2, $scale) {
if ($this->bcmath) return bcmul($s1, $s2, $scale);
else return $this->scale($s1 * $s2, $scale);
}
/**
* Divides two numbers, using arbitrary precision when available.
*/
private function div($s1, $s2, $scale) {
if ($this->bcmath) return bcdiv($s1, $s2, $scale);
else return $this->scale($s1 / $s2, $scale);
}
/**
* Rounds a number according to the number of sigfigs it should have,
* using arbitrary precision when available.
*/
private function round($n, $sigfigs) {
$new_log = (int) floor(log(abs($n), 10)); // Number of digits left of decimal - 1
$rp = $sigfigs - $new_log - 1; // Number of decimal places needed
$neg = $n < 0 ? '-' : ''; // Negative sign
if ($this->bcmath) {
if ($rp >= 0) {
$n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1);
$n = bcdiv($n, '1', $rp);
} else {
// This algorithm partially depends on the standardized
// form of numbers that comes out of bcmath.
$n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
$n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
}
return $n;
} else {
return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
}
}
/**
* Scales a float to $scale digits right of decimal point, like BCMath.
*/
private function scale($r, $scale) {
return sprintf('%.' . $scale . 'f', (float) $r);
}
}

View File

@ -7,21 +7,34 @@
class HTMLPurifier_VarParser
{
const STRING = 1;
const ISTRING = 2;
const TEXT = 3;
const ITEXT = 4;
const INT = 5;
const FLOAT = 6;
const BOOL = 7;
const LOOKUP = 8;
const ALIST = 9;
const HASH = 10;
const MIXED = 11;
/**
* Lookup table of allowed types.
* Lookup table of allowed types. Mainly for backwards compatibility, but
* also convenient for transforming string type names to the integer constants.
*/
static public $types = array(
'string' => true,
'istring' => true,
'text' => true,
'itext' => true,
'int' => true,
'float' => true,
'bool' => true,
'lookup' => true,
'list' => true,
'hash' => true,
'mixed' => true
'string' => self::STRING,
'istring' => self::ISTRING,
'text' => self::TEXT,
'itext' => self::ITEXT,
'int' => self::INT,
'float' => self::FLOAT,
'bool' => self::BOOL,
'lookup' => self::LOOKUP,
'list' => self::ALIST,
'hash' => self::HASH,
'mixed' => self::MIXED
);
/**
@ -29,10 +42,10 @@ class HTMLPurifier_VarParser
* allowed value lists.
*/
static public $stringTypes = array(
'string' => true,
'istring' => true,
'text' => true,
'itext' => true,
self::STRING => true,
self::ISTRING => true,
self::TEXT => true,
self::ITEXT => true,
);
/**
@ -46,42 +59,46 @@ class HTMLPurifier_VarParser
* @return Validated and type-coerced variable
*/
final public function parse($var, $type, $allow_null = false) {
if (!isset(HTMLPurifier_VarParser::$types[$type])) {
throw new HTMLPurifier_VarParserException("Invalid type '$type'");
if (is_string($type)) {
if (!isset(HTMLPurifier_VarParser::$types[$type])) {
throw new HTMLPurifier_VarParserException("Invalid type '$type'");
} else {
$type = HTMLPurifier_VarParser::$types[$type];
}
}
$var = $this->parseImplementation($var, $type, $allow_null);
if ($allow_null && $var === null) return null;
// These are basic checks, to make sure nothing horribly wrong
// happened in our implementations.
switch ($type) {
case 'string':
case 'istring':
case 'text':
case 'itext':
case (self::STRING):
case (self::ISTRING):
case (self::TEXT):
case (self::ITEXT):
if (!is_string($var)) break;
if ($type[0] == 'i') $var = strtolower($var);
if ($type == self::ISTRING || $type == self::ITEXT) $var = strtolower($var);
return $var;
case 'int':
case (self::INT):
if (!is_int($var)) break;
return $var;
case 'float':
case (self::FLOAT):
if (!is_float($var)) break;
return $var;
case 'bool':
case (self::BOOL):
if (!is_bool($var)) break;
return $var;
case 'lookup':
case 'list':
case 'hash':
case (self::LOOKUP):
case (self::ALIST):
case (self::HASH):
if (!is_array($var)) break;
if ($type === 'lookup') {
if ($type === self::LOOKUP) {
foreach ($var as $k) if ($k !== true) $this->error('Lookup table contains value other than true');
} elseif ($type === 'list') {
} elseif ($type === self::ALIST) {
$keys = array_keys($var);
if (array_keys($keys) !== $keys) $this->error('Indices for list are not uniform');
}
return $var;
case 'mixed':
case (self::MIXED):
return $var;
default:
$this->errorInconsistent(get_class($this), $type);
@ -111,7 +128,7 @@ class HTMLPurifier_VarParser
* updating subclasses.
*/
protected function errorInconsistent($class, $type) {
throw new HTMLPurifier_Exception("Inconsistency in $class: $type not implemented");
throw new HTMLPurifier_Exception("Inconsistency in $class: ".HTMLPurifier_VarParser::getTypeName($type)." not implemented");
}
/**
@ -119,7 +136,17 @@ class HTMLPurifier_VarParser
*/
protected function errorGeneric($var, $type) {
$vtype = gettype($var);
$this->error("Expected type $type, got $vtype");
$this->error("Expected type ".HTMLPurifier_VarParser::getTypeName($type).", got $vtype");
}
static public function getTypeName($type) {
static $lookup;
if (!$lookup) {
// Lazy load the alternative lookup table
$lookup = array_flip(HTMLPurifier_VarParser::$types);
}
if (!isset($lookup[$type])) return 'unknown';
return $lookup[$type];
}
}

View File

@ -14,19 +14,19 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
// Note: if code "breaks" from the switch, it triggers a generic
// exception to be thrown. Specific errors can be specifically
// done here.
case 'mixed':
case 'istring':
case 'string':
case 'text':
case 'itext':
case self::MIXED :
case self::ISTRING :
case self::STRING :
case self::TEXT :
case self::ITEXT :
return $var;
case 'int':
case self::INT :
if (is_string($var) && ctype_digit($var)) $var = (int) $var;
return $var;
case 'float':
case self::FLOAT :
if ((is_string($var) && is_numeric($var)) || is_int($var)) $var = (float) $var;
return $var;
case 'bool':
case self::BOOL :
if (is_int($var) && ($var === 0 || $var === 1)) {
$var = (bool) $var;
} elseif (is_string($var)) {
@ -39,9 +39,9 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
}
}
return $var;
case 'list':
case 'hash':
case 'lookup':
case self::ALIST :
case self::HASH :
case self::LOOKUP :
if (is_string($var)) {
// special case: technically, this is an array with
// a single empty string item, but having an empty
@ -56,7 +56,7 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
}
// remove spaces
foreach ($var as $i => $j) $var[$i] = trim($j);
if ($type === 'hash') {
if ($type === self::HASH) {
// key:value,key2:value2
$nvar = array();
foreach ($var as $keypair) {
@ -70,8 +70,8 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
if (!is_array($var)) break;
$keys = array_keys($var);
if ($keys === array_keys($keys)) {
if ($type == 'list') return $var;
elseif ($type == 'lookup') {
if ($type == self::ALIST) return $var;
elseif ($type == self::LOOKUP) {
$new = array();
foreach ($var as $key) {
$new[$key] = true;
@ -79,7 +79,7 @@ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
return $new;
} else break;
}
if ($type === 'lookup') {
if ($type === self::LOOKUP) {
foreach ($var as $key => $value) {
$var[$key] = true;
}

View File

@ -1,12 +1,10 @@
Description of HTML Purifier v3.1.0 library import into Moodle
Description of HTML Purifier v3.1.1 library import into Moodle
Changes:
* HMLTModule/Text.php - added <nolink>, <tex>, <lang> and <algebra> tags
* HMLTModule/XMLCommonAttributes.php - remove xml:lang - needed for multilang
* AttrDef/Lang.php - relax lang check - needed for multilang
* temporary work dir fix from http://htmlpurifier.org/phorum/read.php?2,1809,1809#msg-1809
skodak
$Id$