From 1faa5a441e1227e31eb8674967d857e0f44a32ad Mon Sep 17 00:00:00 2001 From: skodak Date: Wed, 24 Sep 2008 21:24:14 +0000 Subject: [PATCH] MDL-16667 security fixes in htmlpurifier 2.1.5 --- lib/htmlpurifier/HTMLPurifier.php | 4 +- lib/htmlpurifier/HTMLPurifier/AttrDef.php | 11 +- .../HTMLPurifier/AttrDef/CSS/FontFamily.php | 38 ++- .../HTMLPurifier/AttrDef/CSS/Length.php | 60 ++--- .../HTMLPurifier/AttrDef/CSS/Number.php | 5 + .../AttrDef/CSS/TextDecoration.php | 5 +- .../HTMLPurifier/AttrDef/HTML/Pixels.php | 15 +- .../HTMLPurifier/AttrDef/Switch.php | 32 +++ .../HTMLPurifier/AttrDef/URI/Email.php | 2 +- .../AttrDef/URI/Email/SimpleCheck.php | 22 ++ .../HTMLPurifier/AttrValidator.php | 6 +- .../HTMLPurifier/CSSDefinition.php | 44 +++- lib/htmlpurifier/HTMLPurifier/Config.php | 2 +- lib/htmlpurifier/HTMLPurifier/Encoder.php | 127 ++++++--- lib/htmlpurifier/HTMLPurifier/HTMLModule.php | 9 + .../HTMLPurifier/HTMLModule/Bdo.php | 2 +- .../HTMLPurifier/HTMLModule/Edit.php | 2 +- .../HTMLPurifier/HTMLModule/Hypertext.php | 2 +- .../HTMLPurifier/HTMLModule/Image.php | 27 +- .../HTMLPurifier/HTMLModule/Legacy.php | 2 +- .../HTMLPurifier/HTMLModule/List.php | 2 +- .../HTMLPurifier/HTMLModule/Object.php | 2 +- .../HTMLPurifier/HTMLModule/Presentation.php | 2 +- .../HTMLPurifier/HTMLModule/Ruby.php | 2 +- .../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 | 5 +- lib/htmlpurifier/HTMLPurifier/Length.php | 111 ++++++++ lib/htmlpurifier/HTMLPurifier/URI.php | 11 + .../HTMLPurifier/UnitConverter.php | 241 ++++++++++++++++++ lib/htmlpurifier/readme_moodle.txt | 3 +- 35 files changed, 686 insertions(+), 122 deletions(-) create mode 100644 lib/htmlpurifier/HTMLPurifier/AttrDef/Switch.php create mode 100644 lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php create mode 100644 lib/htmlpurifier/HTMLPurifier/Length.php create mode 100644 lib/htmlpurifier/HTMLPurifier/UnitConverter.php diff --git a/lib/htmlpurifier/HTMLPurifier.php b/lib/htmlpurifier/HTMLPurifier.php index a7bba317e5e..17cb1f80b81 100644 --- a/lib/htmlpurifier/HTMLPurifier.php +++ b/lib/htmlpurifier/HTMLPurifier.php @@ -22,7 +22,7 @@ */ /* - HTML Purifier 2.1.4 - Standards Compliant HTML Filtering + HTML Purifier 2.1.5 - Standards Compliant HTML Filtering Copyright (C) 2006-2007 Edward Z. Yang This library is free software; you can redistribute it and/or @@ -83,7 +83,7 @@ since 2.0.0. class HTMLPurifier { - var $version = '2.1.4'; + var $version = '2.1.5'; var $config; var $filters = array(); diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef.php b/lib/htmlpurifier/HTMLPurifier/AttrDef.php index e94ee713d2e..5417270e647 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef.php @@ -54,18 +54,15 @@ 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/FontFamily.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php index dfd89b95841..7418368a174 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php @@ -19,7 +19,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 = ''; @@ -38,13 +37,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; @@ -53,8 +79,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 095eaade3ff..79ac5bfffb0 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php @@ -1,7 +1,7 @@ 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 - */ - var $number_def; + var $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. */ - function HTMLPurifier_AttrDef_CSS_Length($non_negative = false) { - $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); + function HTMLPurifier_AttrDef_CSS_Length($min = null, $max = null) { + $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null; + $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; } - function validate($length, $config, &$context) { + function validate($string, $config, $context) { + $string = $this->parseCDATA($string); - $length = $this->parseCDATA($length); - if ($length === '') return false; - if ($length === '0') return '0'; - $strlen = strlen($length); - if ($strlen === 1) return false; // impossible! + // Optimizations + if ($string === '') return false; + if ($string === '0') return '0'; + if (strlen($string) === 1) return false; - // we assume all units are two characters - $unit = substr($length, $strlen - 2); - if (!ctype_lower($unit)) $unit = strtolower($unit); - $number = substr($length, 0, $strlen - 2); + $length = HTMLPurifier_Length::make($string); + if (!$length->isValid()) return false; - if (!isset($this->units[$unit])) return false; - - $number = $this->number_def->validate($number, $config, $context); - if ($number === false) return false; - - return $number . $unit; + if ($this->min) { + $c = $length->compareTo($this->min); + if ($c === false) return false; + if ($c < 0) return false; + } + if ($this->max) { + $c = $length->compareTo($this->max); + if ($c === false) return false; + if ($c > 0) return false; + } + return $length->toString(); } } diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php index 4f22f829078..6fd11bb42b9 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php @@ -18,6 +18,11 @@ 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. + * This might not work properly in PHP4. + */ 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 501ab2616fe..ab452780e96 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php @@ -15,10 +15,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 38bc7d684e5..ad20615141c 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php @@ -8,6 +8,12 @@ require_once 'HTMLPurifier/AttrDef.php'; class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef { + var $max; + + function HTMLPurifier_AttrDef_HTML_Pixels($max = null) { + $this->max = $max; + } + function validate($string, $config, &$context) { $string = trim($string); @@ -26,11 +32,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; } + 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 00000000000..32e9501a31a --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/Switch.php @@ -0,0 +1,32 @@ +tag = $tag; + $this->withTag = $with_tag; + $this->withoutTag = $without_tag; + } + + 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/Email.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email.php index 31c3add51ce..ababd9eae07 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email.php @@ -15,4 +15,4 @@ class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef } // sub-implementations -//moodlefix require_once 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php'; +require_once 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php'; diff --git a/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php new file mode 100644 index 00000000000..6623f1907f0 --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php @@ -0,0 +1,22 @@ +" + // that needs more percent encoding to be done + if ($string == '') return false; + $string = trim($string); + $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); + return $result ? $string : false; + } + +} + diff --git a/lib/htmlpurifier/HTMLPurifier/AttrValidator.php b/lib/htmlpurifier/HTMLPurifier/AttrValidator.php index a471b093792..f020e0080e5 100644 --- a/lib/htmlpurifier/HTMLPurifier/AttrValidator.php +++ b/lib/htmlpurifier/HTMLPurifier/AttrValidator.php @@ -40,8 +40,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 @@ -136,6 +136,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 2fc73b905d2..c90f5d91f49 100644 --- a/lib/htmlpurifier/HTMLPurifier/CSSDefinition.php +++ b/lib/htmlpurifier/HTMLPurifier/CSSDefinition.php @@ -17,6 +17,7 @@ require_once 'HTMLPurifier/AttrDef/CSS/Percentage.php'; require_once 'HTMLPurifier/AttrDef/CSS/TextDecoration.php'; require_once 'HTMLPurifier/AttrDef/CSS/URI.php'; require_once 'HTMLPurifier/AttrDef/Enum.php'; +require_once 'HTMLPurifier/AttrDef/Switch.php'; HTMLPurifier_ConfigSchema::define( 'CSS', 'DefinitionRev', 1, 'int', ' @@ -27,6 +28,20 @@ HTMLPurifier_ConfigSchema::define(

'); +HTMLPurifier_ConfigSchema::define( + 'CSS', 'MaxImgLength', '1200px', 'string/null', ' +

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

+'); + /** * Defines allowed CSS attributes and what their values are. * @see HTMLPurifier_HTMLDefinition @@ -117,7 +132,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); @@ -143,7 +158,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) )); @@ -165,7 +180,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) )); @@ -176,14 +191,25 @@ 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/Config.php b/lib/htmlpurifier/HTMLPurifier/Config.php index 1c043aeb713..bfdcc33e7a0 100644 --- a/lib/htmlpurifier/HTMLPurifier/Config.php +++ b/lib/htmlpurifier/HTMLPurifier/Config.php @@ -42,7 +42,7 @@ class HTMLPurifier_Config /** * HTML Purifier's version */ - var $version = '2.1.4'; + var $version = '2.1.5'; /** * Two-level associative array of configuration directives diff --git a/lib/htmlpurifier/HTMLPurifier/Encoder.php b/lib/htmlpurifier/HTMLPurifier/Encoder.php index 31ebb785ff7..4ec736066c7 100644 --- a/lib/htmlpurifier/HTMLPurifier/Encoder.php +++ b/lib/htmlpurifier/HTMLPurifier/Encoder.php @@ -67,6 +67,7 @@ class HTMLPurifier_Encoder */ function muteErrorHandler() {} + /** /** * Cleans a UTF-8 string for well-formedness and SGML validity * @@ -95,35 +96,13 @@ class HTMLPurifier_Encoder */ 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 @@ -234,7 +213,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; } @@ -327,14 +316,23 @@ class HTMLPurifier_Encoder * @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')) { - return @iconv($encoding, 'utf-8//IGNORE', $str); + $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') { - return @utf8_encode($str); + $str = utf8_encode($str); + restore_error_handler(); + return $str; } trigger_error('Encoding not supported', E_USER_ERROR); } @@ -346,17 +344,31 @@ class HTMLPurifier_Encoder * characters being omitted. */ 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')) { - return @iconv('utf-8', $encoding . '//IGNORE', $str); + // 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') { - return @utf8_decode($str); + $str = utf8_decode($str); + restore_error_handler(); + return $str; } trigger_error('Encoding not supported', E_USER_ERROR); } @@ -409,6 +421,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. + */ + 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/HTMLModule.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule.php index 077daff88eb..624ebcae4c7 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule.php @@ -219,5 +219,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 + */ + function setup($config) {} + } diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php index 2d9dffb6222..d481f6e2050 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php @@ -15,7 +15,7 @@ class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule 'I18N' => array('dir' => false) ); - function HTMLPurifier_HTMLModule_Bdo() { + function setup($config) { $bdo =& $this->addElement( 'bdo', true, 'Inline', 'Inline', array('Core', 'Lang'), array( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php index 37a53c337ab..fefa1ecb407 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php @@ -12,7 +12,7 @@ class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule var $name = 'Edit'; - function HTMLPurifier_HTMLModule_Edit() { + 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 74aa6929987..b827d74c0fe 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php @@ -11,7 +11,7 @@ class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule var $name = 'Hypertext'; - function HTMLPurifier_HTMLModule_Hypertext() { + function setup($config) { $a =& $this->addElement( 'a', true, 'Inline', 'Inline', 'Common', array( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php index 64ce2a09a61..b6f14f6425a 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Image.php @@ -5,6 +5,18 @@ require_once 'HTMLPurifier/HTMLModule.php'; require_once 'HTMLPurifier/AttrDef/URI.php'; require_once 'HTMLPurifier/AttrTransform/ImgRequired.php'; +HTMLPurifier_ConfigSchema::define( + 'HTML', 'MaxImgLength', 1200, 'int/null', ' +

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

+'); + /** * XHTML 1.1 Image Module provides basic image embedding. * @note There is specialized code for removing empty images in @@ -15,17 +27,26 @@ class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule var $name = 'Image'; - function HTMLPurifier_HTMLModule_Image() { + function setup($config) { + $max = $config->get('HTML', 'MaxImgLength'); $img =& $this->addElement( 'img', true, '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. A trusted module + // could overload this with the real value. + '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 f702b581549..02529c9cedc 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php @@ -25,7 +25,7 @@ class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule var $name = 'Legacy'; - function HTMLPurifier_HTMLModule_Legacy() { + function setup($config) { $this->addElement('basefont', true, 'Inline', 'Empty', false, array( 'color' => 'Color', diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php index dea99f36d46..a8c92a3c05c 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/List.php @@ -21,7 +21,7 @@ class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule var $content_sets = array('Flow' => 'List'); - function HTMLPurifier_HTMLModule_List() { + function setup($config) { $this->addElement('ol', true, 'List', 'Required: li', 'Common'); $this->addElement('ul', true, 'List', 'Required: li', 'Common'); $this->addElement('dl', true, 'List', 'Required: dt | dd', 'Common'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php index 33734772cca..ab7315633f6 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Object.php @@ -12,7 +12,7 @@ class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule var $name = 'Object'; - function HTMLPurifier_HTMLModule_Object() { + function setup($config) { $this->addElement('object', false, 'Inline', 'Optional: #PCDATA | Flow | param', 'Common', array( diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php index 9e483dc1537..b93f1db6731 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php @@ -17,7 +17,7 @@ class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule var $name = 'Presentation'; - function HTMLPurifier_HTMLModule_Presentation() { + function setup($config) { $this->addElement('b', true, 'Inline', 'Inline', 'Common'); $this->addElement('big', true, 'Inline', 'Inline', 'Common'); $this->addElement('hr', true, 'Block', 'Empty', 'Common'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php index f54324468dd..bb81275a032 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php @@ -11,7 +11,7 @@ class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule var $name = 'Ruby'; - function HTMLPurifier_HTMLModule_Ruby() { + function setup($config) { $this->addElement('ruby', true, 'Inline', 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))', 'Common'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php index d9f9db1a4de..31fb47a4c29 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php @@ -32,7 +32,7 @@ class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule var $elements = array('script', 'noscript'); var $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript'); - function HTMLPurifier_HTMLModule_Scripting() { + 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 d121d7405b5..6255035f20f 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php @@ -18,7 +18,7 @@ class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule 'Core' => array(0 => array('Style')) ); - function HTMLPurifier_HTMLModule_StyleAttribute() { + 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 2b2d41ce283..3a50f589842 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php @@ -11,7 +11,7 @@ class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule var $name = 'Tables'; - function HTMLPurifier_HTMLModule_Tables() { + function setup($config) { $this->addElement('caption', true, false, 'Inline', 'Common'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php index 57da9c3abeb..856b19c756d 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Target.php @@ -10,7 +10,7 @@ class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule var $name = 'Target'; - function HTMLPurifier_HTMLModule_Target() { + 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 ed5ef64a035..72f1c8aae29 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Text.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Text.php @@ -22,7 +22,7 @@ class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule 'Flow' => 'Heading | Block | Inline' ); - function HTMLPurifier_HTMLModule_Text() { + function setup($config) { // Inline Phrasal ------------------------------------------------- $this->addElement('abbr', true, 'Inline', 'Inline', 'Common'); diff --git a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php index 411fd47bfef..ca3edaa6ee0 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php @@ -70,7 +70,7 @@ class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule * @todo Wildcard matching and error reporting when an added or * subtracted fix has no effect. */ - function construct($config) { + 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 3fc86160201..99b3bb5c146 100644 --- a/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php +++ b/lib/htmlpurifier/HTMLPurifier/HTMLModuleManager.php @@ -342,13 +342,12 @@ class HTMLPurifier_HTMLModuleManager 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); } // setup lookup table based on all valid modules diff --git a/lib/htmlpurifier/HTMLPurifier/Length.php b/lib/htmlpurifier/HTMLPurifier/Length.php new file mode 100644 index 00000000000..702a27c2edb --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/Length.php @@ -0,0 +1,111 @@ +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. + */ + function make($s) { + if (is_a($s, '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. + */ + function validate() { + // Special case: + + static $allowedUnits = array( + 'em' => true, 'ex' => true, 'px' => true, 'in' => true, + 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true + ); + 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($allowedUnits[$this->unit])) return false; + // Hack: + $def = new HTMLPurifier_AttrDef_CSS_Number(); + $a = false; // hack hack + $result = $def->validate($this->n, $a, $a); + if ($result === false) return false; + $this->n = $result; + return true; + } + + /** + * Returns string representation of number. + */ + function toString() { + if (!$this->isValid()) return false; + return $this->n . $this->unit; + } + + /** + * Retrieves string numeric magnitude. + */ + function getN() {return $this->n;} + + /** + * Retrieves string unit. + */ + function getUnit() {return $this->unit;} + + /** + * Returns true if this length unit is valid. + */ + 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 + */ + 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/URI.php b/lib/htmlpurifier/HTMLPurifier/URI.php index c68fc48866f..be42ccb8690 100644 --- a/lib/htmlpurifier/HTMLPurifier/URI.php +++ b/lib/htmlpurifier/HTMLPurifier/URI.php @@ -131,6 +131,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/UnitConverter.php b/lib/htmlpurifier/HTMLPurifier/UnitConverter.php new file mode 100644 index 00000000000..d8791ccaa8d --- /dev/null +++ b/lib/htmlpurifier/HTMLPurifier/UnitConverter.php @@ -0,0 +1,241 @@ +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. + */ + function convert($length, $to_unit) { + + /** + * Units information array. Units are grouped into measuring systems + * (English, Metric), and are assigned an integer representing + * the conversion factor between that unit and the smallest unit in + * the system. Numeric indexes are actually magical constants that + * encode conversion data from one system to the next, with a O(n^2) + * constraint on memory (this is generally not a problem, since + * the number of measuring systems is small.) + */ + static $units = array( + 1 => array( + 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary + 'pt' => 4, + 'pc' => 48, + 'in' => 288, + 2 => array('pt', '0.352777778', 'mm'), + ), + 2 => array( + 'mm' => 1, + 'cm' => 10, + 1 => array('mm', '2.83464567', 'pt'), + ), + ); + + 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 ($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; + + // Cleanup $n for PHP 4.3.9 and 4.3.10. See http://bugs.php.net/bug.php?id=30726 + if (strncmp($n, '-.', 2) === 0) { + $n = '-0.' . substr($n, 2); + } + + // 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 = $units[$state][$dest_state][0]; + } + + // Do the conversion if necessary + if ($dest_unit !== $unit) { + $factor = $this->div($units[$state][$unit], $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, $units[$state][$dest_state][1], $cp); + $unit = $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 + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + 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. + */ + function scale($r, $scale) { + return sprintf('%.' . $scale . 'f', (float) $r); + } + +} diff --git a/lib/htmlpurifier/readme_moodle.txt b/lib/htmlpurifier/readme_moodle.txt index fbec8286c9a..40e2b3deae3 100644 --- a/lib/htmlpurifier/readme_moodle.txt +++ b/lib/htmlpurifier/readme_moodle.txt @@ -1,10 +1,9 @@ -Description of HTML Purifier v2.1.4 Lite library import into Moodle +Description of HTML Purifier v2.1.5 Lite 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 - * AttrDef/URI/Email/SimpleCheck.php - deleted to prevent errors on some systems, not used anyway skodak