From 67d7aa437aaa1abda657d250fd16cd47d4ba0786 Mon Sep 17 00:00:00 2001 From: skodak Date: Wed, 24 Sep 2008 21:32:10 +0000 Subject: [PATCH] MDL-16668 import htmlpurifier 3.1.1 to HEAD --- lib/htmlpurifier/HTMLPurifier.php | 6 +- .../HTMLPurifier.safe-includes.php | 14 +- lib/htmlpurifier/HTMLPurifier/AttrDef.php | 11 +- lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php | 8 + .../HTMLPurifier/AttrDef/CSS/FontFamily.php | 38 ++- .../HTMLPurifier/AttrDef/CSS/Length.php | 56 ++-- .../HTMLPurifier/AttrDef/CSS/Number.php | 4 + .../AttrDef/CSS/TextDecoration.php | 5 +- .../HTMLPurifier/AttrDef/HTML/Pixels.php | 15 +- .../HTMLPurifier/AttrDef/Switch.php | 32 +++ lib/htmlpurifier/HTMLPurifier/AttrDef/URI.php | 22 +- .../HTMLPurifier/AttrTransform/SafeEmbed.php | 13 + .../HTMLPurifier/AttrTransform/SafeObject.php | 14 + .../HTMLPurifier/AttrTransform/SafeParam.php | 48 ++++ .../HTMLPurifier/AttrValidator.php | 6 +- .../HTMLPurifier/CSSDefinition.php | 30 ++- .../HTMLPurifier/ChildDef/Required.php | 9 +- lib/htmlpurifier/HTMLPurifier/Config.php | 52 ++-- .../HTMLPurifier/ConfigSchema.php | 76 ++++-- .../ConfigSchema/Builder/ConfigSchema.php | 1 + .../HTMLPurifier/ConfigSchema/Validator.php | 3 +- .../HTMLPurifier/ConfigSchema/schema.ser | 2 +- .../ConfigSchema/schema/CSS.MaxImgLength.txt | 15 ++ .../ConfigSchema/schema/HTML.MaxImgLength.txt | 13 + .../ConfigSchema/schema/HTML.SafeEmbed.txt | 13 + .../ConfigSchema/schema/HTML.SafeObject.txt | 13 + .../ConfigSchema/schema/URI.Munge.txt | 61 ++++- .../schema/URI.MungeResources.txt | 16 ++ .../schema/URI.MungeSecretKey.txt | 29 +++ lib/htmlpurifier/HTMLPurifier/Encoder.php | 114 ++++++--- lib/htmlpurifier/HTMLPurifier/Generator.php | 3 +- .../HTMLPurifier/HTMLDefinition.php | 23 +- lib/htmlpurifier/HTMLPurifier/HTMLModule.php | 17 ++ .../HTMLPurifier/HTMLModule/Bdo.php | 2 +- .../HTMLPurifier/HTMLModule/Edit.php | 2 +- .../HTMLPurifier/HTMLModule/Hypertext.php | 2 +- .../HTMLPurifier/HTMLModule/Image.php | 14 +- .../HTMLPurifier/HTMLModule/Legacy.php | 2 +- .../HTMLPurifier/HTMLModule/List.php | 2 +- .../HTMLPurifier/HTMLModule/Object.php | 2 +- .../HTMLPurifier/HTMLModule/Presentation.php | 2 +- .../HTMLPurifier/HTMLModule/Proprietary.php | 2 +- .../HTMLPurifier/HTMLModule/Ruby.php | 2 +- .../HTMLPurifier/HTMLModule/SafeEmbed.php | 31 +++ .../HTMLPurifier/HTMLModule/SafeObject.php | 48 ++++ .../HTMLPurifier/HTMLModule/Scripting.php | 2 +- .../HTMLModule/StyleAttribute.php | 2 +- .../HTMLPurifier/HTMLModule/Tables.php | 2 +- .../HTMLPurifier/HTMLModule/Target.php | 2 +- .../HTMLPurifier/HTMLModule/Text.php | 2 +- .../HTMLPurifier/HTMLModule/Tidy.php | 2 +- .../HTMLPurifier/HTMLModuleManager.php | 24 +- lib/htmlpurifier/HTMLPurifier/Injector.php | 32 ++- .../HTMLPurifier/Injector/SafeObject.php | 83 ++++++ lib/htmlpurifier/HTMLPurifier/Length.php | 113 +++++++++ lib/htmlpurifier/HTMLPurifier/Printer.php | 5 +- .../HTMLPurifier/Printer/ConfigForm.php | 87 +++++-- .../HTMLPurifier/Strategy/MakeWellFormed.php | 9 +- .../Strategy/RemoveForeignElements.php | 2 +- lib/htmlpurifier/HTMLPurifier/Token.php | 1 - lib/htmlpurifier/HTMLPurifier/URI.php | 11 + .../HTMLPurifier/URIDefinition.php | 23 +- lib/htmlpurifier/HTMLPurifier/URIFilter.php | 7 +- .../HTMLPurifier/URIFilter/HostBlacklist.php | 1 + .../HTMLPurifier/URIFilter/MakeAbsolute.php | 5 +- .../HTMLPurifier/URIFilter/Munge.php | 48 ++++ .../HTMLPurifier/UnitConverter.php | 240 ++++++++++++++++++ lib/htmlpurifier/HTMLPurifier/VarParser.php | 95 ++++--- .../HTMLPurifier/VarParser/Flexible.php | 30 +-- lib/htmlpurifier/readme_moodle.txt | 4 +- 70 files changed, 1437 insertions(+), 288 deletions(-) create mode 100644 lib/htmlpurifier/HTMLPurifier/AttrDef/Switch.php create mode 100644 lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php create mode 100644 lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeObject.php create mode 100644 lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeParam.php create mode 100644 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt create mode 100644 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt create mode 100644 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt create mode 100644 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt create mode 100644 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt create mode 100644 lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt create mode 100644 lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php create mode 100644 lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php create mode 100644 lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php create mode 100644 lib/htmlpurifier/HTMLPurifier/Length.php create mode 100644 lib/htmlpurifier/HTMLPurifier/URIFilter/Munge.php create mode 100644 lib/htmlpurifier/HTMLPurifier/UnitConverter.php diff --git a/lib/htmlpurifier/HTMLPurifier.php b/lib/htmlpurifier/HTMLPurifier.php index 03709f1268..da2269dab4 100644 --- a/lib/htmlpurifier/HTMLPurifier.php +++ b/lib/htmlpurifier/HTMLPurifier.php @@ -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; diff --git a/lib/htmlpurifier/HTMLPurifier.safe-includes.php b/lib/htmlpurifier/HTMLPurifier.safe-includes.php index 4273da369e..42acd76bbb 100644 --- a/lib/htmlpurifier/HTMLPurifier.safe-includes.php +++ b/lib/htmlpurifier/HTMLPurifier.safe-includes.php @@ -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'; diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef.php b/lib/htmlpurifier/HTMLPurifier/AttrDef.php index 2c59a8d73a..eefe1c5d16 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef.php @@ -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; } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php index 128158e19c..53afaf089f 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS.php @@ -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. diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php index 51f83ac5bf..d7f04a8bf3 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php @@ -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, ', '); diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php index 2684f8ccf4..2b8db17cc0 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php @@ -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) { - - $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); + public function validate($string, $config, $context) { + $string = $this->parseCDATA($string); - if (!isset($this->units[$unit])) return false; + // Optimizations + if ($string === '') return false; + if ($string === '0') return '0'; + if (strlen($string) === 1) return false; - $number = $this->number_def->validate($number, $config, $context); - if ($number === false) return false; + $length = HTMLPurifier_Length::make($string); + if (!$length->isValid()) 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(); } } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php index 3d4028ee8c..15153e4b9c 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php @@ -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); diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php index 2108f80492..af16d59155 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php @@ -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) { diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php index 156b7242a8..6b615fe94d 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php @@ -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 // 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); + } + } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/Switch.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/Switch.php new file mode 100644 index 0000000000..31398e25d9 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/Switch.php @@ -0,0 +1,32 @@ +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); + } + } + +} diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/URI.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI.php index 9c632f1ae7..b814e9eeef 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/URI.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI.php @@ -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(); } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php b/lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php new file mode 100644 index 0000000000..d3c1883b89 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php @@ -0,0 +1,13 @@ +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; + } +} diff --git a/lib/htmlpurifier/HTMLPurifier/AttrValidator.php b/lib/htmlpurifier/HTMLPurifier/AttrValidator.php index 3b2bd4b375..ba4510e3a4 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrValidator.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrValidator.php @@ -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'); diff --git a/lib/htmlpurifier/HTMLPurifier/CSSDefinition.php b/lib/htmlpurifier/HTMLPurifier/CSSDefinition.php index 0d42ed42e6..36dfa618b7 100644 --- a/lib/htmlpurifier/HTMLPurifier/CSSDefinition.php +++ b/lib/htmlpurifier/HTMLPurifier/CSSDefinition.php @@ -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(); diff --git a/lib/htmlpurifier/HTMLPurifier/ChildDef/Required.php b/lib/htmlpurifier/HTMLPurifier/ChildDef/Required.php index a4be02eac7..2009fc8835 100644 --- a/lib/htmlpurifier/HTMLPurifier/ChildDef/Required.php +++ b/lib/htmlpurifier/HTMLPurifier/ChildDef/Required.php @@ -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 diff --git a/lib/htmlpurifier/HTMLPurifier/Config.php b/lib/htmlpurifier/HTMLPurifier/Config.php index be264a6250..e2fe21b6b5 100644 --- a/lib/htmlpurifier/HTMLPurifier/Config.php +++ b/lib/htmlpurifier/HTMLPurifier/Config.php @@ -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); } diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema.php b/lib/htmlpurifier/HTMLPurifier/ConfigSchema.php index bfa84a1b7e..b324181ce4 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema.php +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema.php @@ -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); } /** diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php index 69b71387d6..727f58eefa 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php @@ -43,6 +43,7 @@ class HTMLPurifier_ConfigSchema_Builder_ConfigSchema ); } } + $schema->postProcess(); return $schema; } diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php index e36ef8f130..64a35430da 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php @@ -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'); } } diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser index 619cf532bf..1af702b7df 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser @@ -1 +1 @@ -O:25:"HTMLPurifier_ConfigSchema":3:{s:8:"defaults";a:12:{s:4:"Attr";a:11:{s:19:"AllowedFrameTargets";a:0:{}s:10:"AllowedRel";a:0:{}s:10:"AllowedRev";a:0:{}s:19:"DefaultInvalidImage";s:0:"";s:22:"DefaultInvalidImageAlt";s:13:"Invalid image";s:14:"DefaultTextDir";s:3:"ltr";s:8:"EnableID";b:0;s:11:"IDBlacklist";a:0:{}s:17:"IDBlacklistRegexp";N;s:8:"IDPrefix";s:0:"";s:13:"IDPrefixLocal";s:0:"";}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";b:0;s:6:"Custom";a:0:{}s:7:"Linkify";b:0;s:15:"PurifierLinkify";b:0;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";s:3:"#%s";}s:3:"CSS";a:5:{s:14:"AllowImportant";b:0;s:11:"AllowTricky";b:0;s:17:"AllowedProperties";N;s:13:"DefinitionRev";i:1;s:11:"Proprietary";b:0;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";s:10:"Serializer";s:14:"SerializerPath";N;}s:4:"Core";a:15:{s:17:"AggressivelyFixLt";b:0;s:13:"CollectErrors";b:0;s:13:"ColorKeywords";a:17:{s:6:"maroon";s:7:"#800000";s:3:"red";s:7:"#FF0000";s:6:"orange";s:7:"#FFA500";s:6:"yellow";s:7:"#FFFF00";s:5:"olive";s:7:"#808000";s:6:"purple";s:7:"#800080";s:7:"fuchsia";s:7:"#FF00FF";s:5:"white";s:7:"#FFFFFF";s:4:"lime";s:7:"#00FF00";s:5:"green";s:7:"#008000";s:4:"navy";s:7:"#000080";s:4:"blue";s:7:"#0000FF";s:4:"aqua";s:7:"#00FFFF";s:4:"teal";s:7:"#008080";s:5:"black";s:7:"#000000";s:6:"silver";s:7:"#C0C0C0";s:4:"gray";s:7:"#808080";}s:25:"ConvertDocumentToFragment";b:1;s:31:"DirectLexLineNumberSyncInterval";i:0;s:8:"Encoding";s:5:"utf-8";s:21:"EscapeInvalidChildren";b:0;s:17:"EscapeInvalidTags";b:0;s:24:"EscapeNonASCIICharacters";b:0;s:14:"HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:8:"Language";s:2:"en";s:9:"LexerImpl";N;s:19:"MaintainLineNumbers";N;s:16:"RemoveInvalidImg";b:1;s:20:"RemoveScriptContents";N;}s:6:"Filter";a:3:{s:6:"Custom";a:0:{}s:18:"ExtractStyleBlocks";b:0;s:7:"YouTube";b:0;}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";b:1;s:23:"ExtractStyleBlocksScope";N;s:26:"ExtractStyleBlocksTidyImpl";N;}s:4:"HTML";a:20:{s:7:"Allowed";N;s:17:"AllowedAttributes";N;s:15:"AllowedElements";N;s:14:"AllowedModules";N;s:12:"BlockWrapper";s:1:"p";s:11:"CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:13:"CustomDoctype";N;s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Doctype";N;s:19:"ForbiddenAttributes";a:0:{}s:17:"ForbiddenElements";a:0:{}s:6:"Parent";s:3:"div";s:11:"Proprietary";b:0;s:6:"Strict";b:0;s:7:"TidyAdd";a:0:{}s:9:"TidyLevel";s:6:"medium";s:10:"TidyRemove";a:0:{}s:7:"Trusted";b:0;s:5:"XHTML";b:1;}s:6:"Output";a:3:{s:21:"CommentScriptContents";b:1;s:7:"Newline";N;s:10:"TidyFormat";b:0;}s:4:"Test";a:1:{s:12:"ForceNoIconv";b:0;}s:3:"URI";a:14:{s:14:"AllowedSchemes";a:6:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;}s:4:"Base";N;s:13:"DefaultScheme";s:4:"http";s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Disable";b:0;s:15:"DisableExternal";b:0;s:24:"DisableExternalResources";b:0;s:16:"DisableResources";b:0;s:4:"Host";N;s:13:"HostBlacklist";a:0:{}s:12:"MakeAbsolute";b:0;s:5:"Munge";N;s:22:"OverrideAllowedSchemes";b:1;}}s:4:"info";a:12:{s:4:"Attr";a:12:{s:19:"AllowedFrameTargets";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:10:"AllowedRel";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:10:"AllowedRev";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:19:"DefaultInvalidImage";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:22:"DefaultInvalidImageAlt";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:14:"DefaultTextDir";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}s:7:"aliases";a:0:{}}s:8:"EnableID";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:11:"IDBlacklist";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"list";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:17:"IDBlacklistRegexp";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:8:"IDPrefix";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"IDPrefixLocal";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:10:"DisableURI";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:3:"URI";s:4:"name";s:7:"Disable";}}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:6:"Custom";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"list";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"Linkify";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:15:"PurifierLinkify";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:3:"CSS";a:5:{s:14:"AllowImportant";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:11:"AllowTricky";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:17:"AllowedProperties";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"DefinitionRev";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:3:"int";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:11:"Proprietary";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:5:"Cache";a:2:{s:14:"DefinitionImpl";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:14:"SerializerPath";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:4:"Core";a:20:{s:15:"DefinitionCache";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:5:"Cache";s:4:"name";s:14:"DefinitionImpl";}s:17:"AggressivelyFixLt";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"CollectErrors";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"ColorKeywords";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"hash";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:25:"ConvertDocumentToFragment";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:19:"AcceptFullDocuments";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:4:"Core";s:4:"name";s:25:"ConvertDocumentToFragment";}s:31:"DirectLexLineNumberSyncInterval";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:3:"int";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:8:"Encoding";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:7:"istring";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:21:"EscapeInvalidChildren";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:17:"EscapeInvalidTags";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:24:"EscapeNonASCIICharacters";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:14:"HiddenElements";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:8:"Language";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:9:"LexerImpl";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:5:"mixed";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:19:"MaintainLineNumbers";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:16:"RemoveInvalidImg";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:20:"RemoveScriptContents";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:5:"XHTML";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:4:"HTML";s:4:"name";s:5:"XHTML";}s:21:"CommentScriptContents";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:6:"Output";s:4:"name";s:21:"CommentScriptContents";}s:10:"TidyFormat";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:6:"Output";s:4:"name";s:10:"TidyFormat";}}s:6:"Filter";a:5:{s:6:"Custom";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"list";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:18:"ExtractStyleBlocks";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"YouTube";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:26:"ExtractStyleBlocksEscaping";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:11:"FilterParam";s:4:"name";s:26:"ExtractStyleBlocksEscaping";}s:23:"ExtractStyleBlocksScope";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:11:"FilterParam";s:4:"name";s:23:"ExtractStyleBlocksScope";}}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:23:"ExtractStyleBlocksScope";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:26:"ExtractStyleBlocksTidyImpl";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:5:"mixed";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:4:"HTML";a:21:{s:12:"EnableAttrID";O:37:"HTMLPurifier_ConfigDef_DirectiveAlias":3:{s:5:"class";s:5:"alias";s:9:"namespace";s:4:"Attr";s:4:"name";s:8:"EnableID";}s:7:"Allowed";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:5:"itext";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:17:"AllowedAttributes";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:15:"AllowedElements";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:14:"AllowedModules";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:12:"BlockWrapper";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:11:"CoreModules";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"CustomDoctype";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:12:"DefinitionID";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"DefinitionRev";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:3:"int";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"Doctype";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}s:7:"aliases";a:0:{}}s:19:"ForbiddenAttributes";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:17:"ForbiddenElements";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:6:"Parent";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:11:"Proprietary";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:6:"Strict";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"TidyAdd";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:9:"TidyLevel";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}s:7:"aliases";a:0:{}}s:10:"TidyRemove";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"Trusted";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:5:"XHTML";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:6:"Output";a:3:{s:21:"CommentScriptContents";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"Newline";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:10:"TidyFormat";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:4:"Test";a:1:{s:12:"ForceNoIconv";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}s:3:"URI";a:14:{s:14:"AllowedSchemes";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"lookup";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:4:"Base";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"DefaultScheme";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:12:"DefinitionID";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"DefinitionRev";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:3:"int";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:7:"Disable";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:15:"DisableExternal";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:24:"DisableExternalResources";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:16:"DisableResources";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:4:"Host";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:13:"HostBlacklist";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"list";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:12:"MakeAbsolute";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:5:"Munge";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:6:"string";s:10:"allow_null";b:1;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}s:22:"OverrideAllowedSchemes";O:32:"HTMLPurifier_ConfigDef_Directive":5:{s:5:"class";s:9:"directive";s:4:"type";s:4:"bool";s:10:"allow_null";b:0;s:7:"allowed";b:1;s:7:"aliases";a:0:{}}}}s:9:"*parser";O:31:"HTMLPurifier_VarParser_Flexible":0:{}} \ No newline at end of file +O:25:"HTMLPurifier_ConfigSchema":2:{s:8:"defaults";a:12:{s:4:"Attr";a:11:{s:19:"AllowedFrameTargets";a:0:{}s:10:"AllowedRel";a:0:{}s:10:"AllowedRev";a:0:{}s:19:"DefaultInvalidImage";s:0:"";s:22:"DefaultInvalidImageAlt";s:13:"Invalid image";s:14:"DefaultTextDir";s:3:"ltr";s:8:"EnableID";b:0;s:11:"IDBlacklist";a:0:{}s:17:"IDBlacklistRegexp";N;s:8:"IDPrefix";s:0:"";s:13:"IDPrefixLocal";s:0:"";}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";b:0;s:6:"Custom";a:0:{}s:7:"Linkify";b:0;s:15:"PurifierLinkify";b:0;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";s:3:"#%s";}s:3:"CSS";a:6:{s:14:"AllowImportant";b:0;s:11:"AllowTricky";b:0;s:17:"AllowedProperties";N;s:13:"DefinitionRev";i:1;s:12:"MaxImgLength";s:6:"1200px";s:11:"Proprietary";b:0;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";s:10:"Serializer";s:14:"SerializerPath";N;}s:4:"Core";a:15:{s:17:"AggressivelyFixLt";b:0;s:13:"CollectErrors";b:0;s:13:"ColorKeywords";a:17:{s:6:"maroon";s:7:"#800000";s:3:"red";s:7:"#FF0000";s:6:"orange";s:7:"#FFA500";s:6:"yellow";s:7:"#FFFF00";s:5:"olive";s:7:"#808000";s:6:"purple";s:7:"#800080";s:7:"fuchsia";s:7:"#FF00FF";s:5:"white";s:7:"#FFFFFF";s:4:"lime";s:7:"#00FF00";s:5:"green";s:7:"#008000";s:4:"navy";s:7:"#000080";s:4:"blue";s:7:"#0000FF";s:4:"aqua";s:7:"#00FFFF";s:4:"teal";s:7:"#008080";s:5:"black";s:7:"#000000";s:6:"silver";s:7:"#C0C0C0";s:4:"gray";s:7:"#808080";}s:25:"ConvertDocumentToFragment";b:1;s:31:"DirectLexLineNumberSyncInterval";i:0;s:8:"Encoding";s:5:"utf-8";s:21:"EscapeInvalidChildren";b:0;s:17:"EscapeInvalidTags";b:0;s:24:"EscapeNonASCIICharacters";b:0;s:14:"HiddenElements";a:2:{s:6:"script";b:1;s:5:"style";b:1;}s:8:"Language";s:2:"en";s:9:"LexerImpl";N;s:19:"MaintainLineNumbers";N;s:16:"RemoveInvalidImg";b:1;s:20:"RemoveScriptContents";N;}s:6:"Filter";a:3:{s:6:"Custom";a:0:{}s:18:"ExtractStyleBlocks";b:0;s:7:"YouTube";b:0;}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";b:1;s:23:"ExtractStyleBlocksScope";N;s:26:"ExtractStyleBlocksTidyImpl";N;}s:4:"HTML";a:23:{s:7:"Allowed";N;s:17:"AllowedAttributes";N;s:15:"AllowedElements";N;s:14:"AllowedModules";N;s:12:"BlockWrapper";s:1:"p";s:11:"CoreModules";a:7:{s:9:"Structure";b:1;s:4:"Text";b:1;s:9:"Hypertext";b:1;s:4:"List";b:1;s:22:"NonXMLCommonAttributes";b:1;s:19:"XMLCommonAttributes";b:1;s:16:"CommonAttributes";b:1;}s:13:"CustomDoctype";N;s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Doctype";N;s:19:"ForbiddenAttributes";a:0:{}s:17:"ForbiddenElements";a:0:{}s:12:"MaxImgLength";i:1200;s:6:"Parent";s:3:"div";s:11:"Proprietary";b:0;s:9:"SafeEmbed";b:0;s:10:"SafeObject";b:0;s:6:"Strict";b:0;s:7:"TidyAdd";a:0:{}s:9:"TidyLevel";s:6:"medium";s:10:"TidyRemove";a:0:{}s:7:"Trusted";b:0;s:5:"XHTML";b:1;}s:6:"Output";a:3:{s:21:"CommentScriptContents";b:1;s:7:"Newline";N;s:10:"TidyFormat";b:0;}s:4:"Test";a:1:{s:12:"ForceNoIconv";b:0;}s:3:"URI";a:16:{s:14:"AllowedSchemes";a:6:{s:4:"http";b:1;s:5:"https";b:1;s:6:"mailto";b:1;s:3:"ftp";b:1;s:4:"nntp";b:1;s:4:"news";b:1;}s:4:"Base";N;s:13:"DefaultScheme";s:4:"http";s:12:"DefinitionID";N;s:13:"DefinitionRev";i:1;s:7:"Disable";b:0;s:15:"DisableExternal";b:0;s:24:"DisableExternalResources";b:0;s:16:"DisableResources";b:0;s:4:"Host";N;s:13:"HostBlacklist";a:0:{}s:12:"MakeAbsolute";b:0;s:5:"Munge";N;s:14:"MungeResources";b:0;s:14:"MungeSecretKey";N;s:22:"OverrideAllowedSchemes";b:1;}}s:4:"info";a:12:{s:4:"Attr";a:12:{s:19:"AllowedFrameTargets";i:8;s:10:"AllowedRel";i:8;s:10:"AllowedRev";i:8;s:19:"DefaultInvalidImage";i:1;s:22:"DefaultInvalidImageAlt";i:1;s:14:"DefaultTextDir";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:2:{s:3:"ltr";b:1;s:3:"rtl";b:1;}}s:8:"EnableID";i:7;s:11:"IDBlacklist";i:9;s:17:"IDBlacklistRegexp";i:-1;s:8:"IDPrefix";i:1;s:13:"IDPrefixLocal";i:1;s:10:"DisableURI";O:8:"stdClass":3:{s:9:"namespace";s:3:"URI";s:4:"name";s:7:"Disable";s:7:"isAlias";b:1;}}s:10:"AutoFormat";a:4:{s:13:"AutoParagraph";i:7;s:6:"Custom";i:9;s:7:"Linkify";i:7;s:15:"PurifierLinkify";i:7;}s:15:"AutoFormatParam";a:1:{s:21:"PurifierLinkifyDocURL";i:1;}s:3:"CSS";a:6:{s:14:"AllowImportant";i:7;s:11:"AllowTricky";i:7;s:17:"AllowedProperties";i:-8;s:13:"DefinitionRev";i:5;s:12:"MaxImgLength";i:-1;s:11:"Proprietary";i:7;}s:5:"Cache";a:2:{s:14:"DefinitionImpl";i:-1;s:14:"SerializerPath";i:-1;}s:4:"Core";a:20:{s:15:"DefinitionCache";O:8:"stdClass":3:{s:9:"namespace";s:5:"Cache";s:4:"name";s:14:"DefinitionImpl";s:7:"isAlias";b:1;}s:17:"AggressivelyFixLt";i:7;s:13:"CollectErrors";i:7;s:13:"ColorKeywords";i:10;s:25:"ConvertDocumentToFragment";i:7;s:19:"AcceptFullDocuments";O:8:"stdClass":3:{s:9:"namespace";s:4:"Core";s:4:"name";s:25:"ConvertDocumentToFragment";s:7:"isAlias";b:1;}s:31:"DirectLexLineNumberSyncInterval";i:5;s:8:"Encoding";i:2;s:21:"EscapeInvalidChildren";i:7;s:17:"EscapeInvalidTags";i:7;s:24:"EscapeNonASCIICharacters";i:7;s:14:"HiddenElements";i:8;s:8:"Language";i:1;s:9:"LexerImpl";i:-11;s:19:"MaintainLineNumbers";i:-7;s:16:"RemoveInvalidImg";i:7;s:20:"RemoveScriptContents";i:-7;s:5:"XHTML";O:8:"stdClass":3:{s:9:"namespace";s:4:"HTML";s:4:"name";s:5:"XHTML";s:7:"isAlias";b:1;}s:21:"CommentScriptContents";O:8:"stdClass":3:{s:9:"namespace";s:6:"Output";s:4:"name";s:21:"CommentScriptContents";s:7:"isAlias";b:1;}s:10:"TidyFormat";O:8:"stdClass":3:{s:9:"namespace";s:6:"Output";s:4:"name";s:10:"TidyFormat";s:7:"isAlias";b:1;}}s:6:"Filter";a:5:{s:6:"Custom";i:9;s:18:"ExtractStyleBlocks";i:7;s:7:"YouTube";i:7;s:26:"ExtractStyleBlocksEscaping";O:8:"stdClass":3:{s:9:"namespace";s:11:"FilterParam";s:4:"name";s:26:"ExtractStyleBlocksEscaping";s:7:"isAlias";b:1;}s:23:"ExtractStyleBlocksScope";O:8:"stdClass":3:{s:9:"namespace";s:11:"FilterParam";s:4:"name";s:23:"ExtractStyleBlocksScope";s:7:"isAlias";b:1;}}s:11:"FilterParam";a:3:{s:26:"ExtractStyleBlocksEscaping";i:7;s:23:"ExtractStyleBlocksScope";i:-1;s:26:"ExtractStyleBlocksTidyImpl";i:-11;}s:4:"HTML";a:24:{s:12:"EnableAttrID";O:8:"stdClass":3:{s:9:"namespace";s:4:"Attr";s:4:"name";s:8:"EnableID";s:7:"isAlias";b:1;}s:7:"Allowed";i:-4;s:17:"AllowedAttributes";i:-8;s:15:"AllowedElements";i:-8;s:14:"AllowedModules";i:-8;s:12:"BlockWrapper";i:1;s:11:"CoreModules";i:8;s:13:"CustomDoctype";i:-1;s:12:"DefinitionID";i:-1;s:13:"DefinitionRev";i:5;s:7:"Doctype";O:8:"stdClass":3:{s:4:"type";i:1;s:10:"allow_null";b:1;s:7:"allowed";a:5:{s:22:"HTML 4.01 Transitional";b:1;s:16:"HTML 4.01 Strict";b:1;s:22:"XHTML 1.0 Transitional";b:1;s:16:"XHTML 1.0 Strict";b:1;s:9:"XHTML 1.1";b:1;}}s:19:"ForbiddenAttributes";i:8;s:17:"ForbiddenElements";i:8;s:12:"MaxImgLength";i:-5;s:6:"Parent";i:1;s:11:"Proprietary";i:7;s:9:"SafeEmbed";i:7;s:10:"SafeObject";i:7;s:6:"Strict";i:7;s:7:"TidyAdd";i:8;s:9:"TidyLevel";O:8:"stdClass":2:{s:4:"type";i:1;s:7:"allowed";a:4:{s:4:"none";b:1;s:5:"light";b:1;s:6:"medium";b:1;s:5:"heavy";b:1;}}s:10:"TidyRemove";i:8;s:7:"Trusted";i:7;s:5:"XHTML";i:7;}s:6:"Output";a:3:{s:21:"CommentScriptContents";i:7;s:7:"Newline";i:-1;s:10:"TidyFormat";i:7;}s:4:"Test";a:1:{s:12:"ForceNoIconv";i:7;}s:3:"URI";a:16:{s:14:"AllowedSchemes";i:8;s:4:"Base";i:-1;s:13:"DefaultScheme";i:1;s:12:"DefinitionID";i:-1;s:13:"DefinitionRev";i:5;s:7:"Disable";i:7;s:15:"DisableExternal";i:7;s:24:"DisableExternalResources";i:7;s:16:"DisableResources";i:7;s:4:"Host";i:-1;s:13:"HostBlacklist";i:9;s:12:"MakeAbsolute";i:7;s:5:"Munge";i:-1;s:14:"MungeResources";i:7;s:14:"MungeSecretKey";i:-1;s:22:"OverrideAllowedSchemes";i:7;}}} \ No newline at end of file diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt new file mode 100644 index 0000000000..824111e74f --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt @@ -0,0 +1,15 @@ +CSS.MaxImgLength +TYPE: string/null +DEFAULT: '1200px' +VERSION: 3.1.1 +--DESCRIPTION-- +

+ This parameter sets the maximum allowed length on img tags, + effectively the width and height 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). +

diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt new file mode 100644 index 0000000000..15d8dceccd --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt @@ -0,0 +1,13 @@ +HTML.MaxImgLength +TYPE: int/null +DEFAULT: 1200 +VERSION: 3.1.1 +--DESCRIPTION-- +

+ This directive controls the maximum number of pixels in the width and + height attributes in img 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). +

\ No newline at end of file diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt new file mode 100644 index 0000000000..98ed8f9f0b --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt @@ -0,0 +1,13 @@ +HTML.SafeEmbed +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

+ 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. + Highly experimental. +

\ No newline at end of file diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt new file mode 100644 index 0000000000..6270abea47 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt @@ -0,0 +1,13 @@ +HTML.SafeObject +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

+ 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. + Highly experimental. +

diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt index b1207aab79..7743ac2923 100644 --- a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt @@ -6,7 +6,7 @@ DEFAULT: NULL

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 %s where the url-encoded original URI should be inserted (sample: http://www.google.com/url?q=%s). @@ -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). Notice: - 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.

  • 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.
  • +

    + 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 meta tags. To revert to + previous behavior, please use %URI.MungeResources. +

    +

    + 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. +

    +

    + Starting with HTML Purifier 3.1.1, there is also these substitutions: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyDescriptionExample <a href="">
    %r1 - The URI embeds a resource
    (blank) - The URI is merely a link
    %nThe name of the tag this URI came froma
    %mThe name of the attribute this URI came fromhref
    %pThe name of the CSS property this URI came from, or blank if irrelevant
    +

    + 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. +

    diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt new file mode 100644 index 0000000000..69b1ea8306 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt @@ -0,0 +1,16 @@ +URI.MungeResources +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

    + If true, any URI munging directives like %URI.Munge + will also apply to embedded resources, such as <img src="">. + Be careful enabling this directive if you have a redirector script + that does not use the Location HTTP header; all of your images + and other embedded resources will break. +

    +

    + Warning: It is strongly advised you use this in conjunction + %URI.MungeSecretKey to mitigate the security risk of an open redirector. +

    diff --git a/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt new file mode 100644 index 0000000000..a2f5a02cc5 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -0,0 +1,29 @@ +URI.MungeSecretKey +TYPE: string/null +VERSION: 3.1.1 +DEFAULT: NULL +--DESCRIPTION-- +

    + 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: +

    + +
    $checksum === sha1($secret_key . ':' . $url)
    + +

    + If the output is TRUE, the redirector script should accept the URI. +

    + +

    + 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. +

    + +

    + Remember this has no effect if %URI.Munge is not on. +

    diff --git a/lib/htmlpurifier/HTMLPurifier/Encoder.php b/lib/htmlpurifier/HTMLPurifier/Encoder.php index 763684f789..0518814ec3 100644 --- a/lib/htmlpurifier/HTMLPurifier/Encoder.php +++ b/lib/htmlpurifier/HTMLPurifier/Encoder.php @@ -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; + } + } diff --git a/lib/htmlpurifier/HTMLPurifier/Generator.php b/lib/htmlpurifier/HTMLPurifier/Generator.php index e35ce8cf35..42ea17fe6b 100644 --- a/lib/htmlpurifier/HTMLPurifier/Generator.php +++ b/lib/htmlpurifier/HTMLPurifier/Generator.php @@ -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(); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLDefinition.php b/lib/htmlpurifier/HTMLPurifier/HTMLDefinition.php index ce299f79ce..e647228c54 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLDefinition.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLDefinition.php @@ -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]); + } + } } /** diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule.php index db78983b2b..22af154690 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule.php @@ -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) {} + } diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php index b65057cabc..0d643e8172 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php @@ -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( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php index 07d972e3bb..157fca16d7 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php @@ -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', diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php index 4a347e6f4c..4451f2bbc7 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php @@ -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( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php index 7d2a98c3f8..7a80e32716 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php @@ -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[] = diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php index 903273ea4c..dd1f04e000 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php @@ -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', diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php index 7d4f9cf2ef..a2cf2f5600 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php @@ -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'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php index 609d335103..7dfd6b3134 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php @@ -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( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php index bd8368a8f8..857b3abf01 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php @@ -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'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php index d5c7241873..bcbcd124ff 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php @@ -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( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php index 21ec79a261..caee035fcf 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php @@ -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'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php new file mode 100644 index 0000000000..8e57752199 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php @@ -0,0 +1,31 @@ +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(); + + } + +} diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php new file mode 100644 index 0000000000..a33be7d4fc --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php @@ -0,0 +1,48 @@ +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'; + + } + +} diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php index b3371b3a2b..2b7e2ced49 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php @@ -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 diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php index 3963e22c02..9775536dcc 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php @@ -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(); } diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php index d6a13fff33..dd70bddfc9 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php @@ -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'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php index 18203d046e..042d458c58 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php @@ -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); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Text.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Text.php index aff71e9ad2..3fce4ee53f 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Text.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Text.php @@ -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'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php index 1606ffc676..a95d453b40 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php @@ -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(); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php b/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php index 612c3c0139..3664e88fca 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php @@ -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 diff --git a/lib/htmlpurifier/HTMLPurifier/Injector.php b/lib/htmlpurifier/HTMLPurifier/Injector.php index c9f9f2eb55..1947c340fe 100644 --- a/lib/htmlpurifier/HTMLPurifier/Injector.php +++ b/lib/htmlpurifier/HTMLPurifier/Injector.php @@ -58,26 +58,42 @@ 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 + // 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($this->htmlDefinition->info[$element])) return $element; + if (!isset($def->info[$element])) return $element; if (!is_array($attributes)) continue; foreach ($attributes as $name) { - if (!isset($this->htmlDefinition->info[$element]->attr[$name])) return "$element.$name"; + if (!isset($def->info[$element]->attr[$name])) return "$element.$name"; } } - $this->currentNesting =& $context->get('CurrentNesting'); - $this->inputTokens =& $context->get('InputTokens'); - $this->inputIndex =& $context->get('InputIndex'); return false; } diff --git a/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php b/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php new file mode 100644 index 0000000000..f379406866 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/Injector/SafeObject.php @@ -0,0 +1,83 @@ + '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); + } + } + +} + diff --git a/lib/htmlpurifier/HTMLPurifier/Length.php b/lib/htmlpurifier/HTMLPurifier/Length.php new file mode 100644 index 0000000000..758f9bb0e4 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/Length.php @@ -0,0 +1,113 @@ + 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; + } + +} diff --git a/lib/htmlpurifier/HTMLPurifier/Printer.php b/lib/htmlpurifier/HTMLPurifier/Printer.php index 680ffa1d1a..238cf951d8 100644 --- a/lib/htmlpurifier/HTMLPurifier/Printer.php +++ b/lib/htmlpurifier/HTMLPurifier/Printer.php @@ -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); } /** diff --git a/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php b/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php index 172d7bb48e..9caf7e39a6 100644 --- a/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php +++ b/lib/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php @@ -1,5 +1,8 @@ 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) { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->config = $config; - $this->prepareGenerator($config); + $this->genConfig = $gen_config; + $this->prepareGenerator($gen_config); - $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed); + $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")); diff --git a/lib/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php b/lib/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php index 8843627f13..1ca6271158 100644 --- a/lib/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php +++ b/lib/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php @@ -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) { diff --git a/lib/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php b/lib/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php index 9de69a1185..165308dd0c 100644 --- a/lib/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php +++ b/lib/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php @@ -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 diff --git a/lib/htmlpurifier/HTMLPurifier/Token.php b/lib/htmlpurifier/HTMLPurifier/Token.php index fd2ba53f25..8803307bda 100644 --- a/lib/htmlpurifier/HTMLPurifier/Token.php +++ b/lib/htmlpurifier/HTMLPurifier/Token.php @@ -4,7 +4,6 @@ * Abstract base token class that all others inherit from. */ class HTMLPurifier_Token { - public $type; /**< Type of node to bypass is_a(). */ public $line; /**< Line number node was on in source document. Null if unknown. */ /** diff --git a/lib/htmlpurifier/HTMLPurifier/URI.php b/lib/htmlpurifier/HTMLPurifier/URI.php index 43f1b19287..cab09bfbaf 100644 --- a/lib/htmlpurifier/HTMLPurifier/URI.php +++ b/lib/htmlpurifier/HTMLPurifier/URI.php @@ -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; } diff --git a/lib/htmlpurifier/HTMLPurifier/URIDefinition.php b/lib/htmlpurifier/HTMLPurifier/URIDefinition.php index 390d97ec15..6977338ae1 100644 --- a/lib/htmlpurifier/HTMLPurifier/URIDefinition.php +++ b/lib/htmlpurifier/HTMLPurifier/URIDefinition.php @@ -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; diff --git a/lib/htmlpurifier/HTMLPurifier/URIFilter.php b/lib/htmlpurifier/HTMLPurifier/URIFilter.php index 0108258f7e..6953ed43da 100644 --- a/lib/htmlpurifier/HTMLPurifier/URIFilter.php +++ b/lib/htmlpurifier/HTMLPurifier/URIFilter.php @@ -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 diff --git a/lib/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php b/lib/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php index bd71970103..0132047962 100644 --- a/lib/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php +++ b/lib/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php @@ -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) { diff --git a/lib/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php b/lib/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php index 647f779e9a..289db51ad3 100644 --- a/lib/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php +++ b/lib/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php @@ -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 diff --git a/lib/htmlpurifier/HTMLPurifier/URIFilter/Munge.php b/lib/htmlpurifier/HTMLPurifier/URIFilter/Munge.php new file mode 100644 index 0000000000..ad6578e669 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/URIFilter/Munge.php @@ -0,0 +1,48 @@ +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); + } + +} diff --git a/lib/htmlpurifier/HTMLPurifier/UnitConverter.php b/lib/htmlpurifier/HTMLPurifier/UnitConverter.php new file mode 100644 index 0000000000..6e3046102b --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/UnitConverter.php @@ -0,0 +1,240 @@ + 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 "
    n";
    +        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n
    \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); + } + +} diff --git a/lib/htmlpurifier/HTMLPurifier/VarParser.php b/lib/htmlpurifier/HTMLPurifier/VarParser.php index 418622be55..f9dfd84a76 100644 --- a/lib/htmlpurifier/HTMLPurifier/VarParser.php +++ b/lib/htmlpurifier/HTMLPurifier/VarParser.php @@ -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]; } } diff --git a/lib/htmlpurifier/HTMLPurifier/VarParser/Flexible.php b/lib/htmlpurifier/HTMLPurifier/VarParser/Flexible.php index a2041fdd34..ec2c90f450 100644 --- a/lib/htmlpurifier/HTMLPurifier/VarParser/Flexible.php +++ b/lib/htmlpurifier/HTMLPurifier/VarParser/Flexible.php @@ -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; } diff --git a/lib/htmlpurifier/readme_moodle.txt b/lib/htmlpurifier/readme_moodle.txt index b6bf56c380..99e67dbc4f 100644 --- a/lib/htmlpurifier/readme_moodle.txt +++ b/lib/htmlpurifier/readme_moodle.txt @@ -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 , , and 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$ -- 2.39.5