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