diff --git a/INSTALL b/INSTALL index 3e10017f..849d9db1 100644 --- a/INSTALL +++ b/INSTALL @@ -23,8 +23,9 @@ August 8, 2008. These optional extensions can enhance the capabilities of HTML Purifier: - * iconv : Converts text to and from non-UTF-8 encodings - * tidy : Used for pretty-printing HTML + * iconv : Converts text to and from non-UTF-8 encodings + * bcmath : Used for unit conversion and imagecrash protection + * tidy : Used for pretty-printing HTML --------------------------------------------------------------------------- diff --git a/library/HTMLPurifier/UnitConverter.php b/library/HTMLPurifier/UnitConverter.php index 3482021c..6f4efba7 100644 --- a/library/HTMLPurifier/UnitConverter.php +++ b/library/HTMLPurifier/UnitConverter.php @@ -43,9 +43,15 @@ class HTMLPurifier_UnitConverter */ protected $internalPrecision; - public function __construct($output_precision = 4, $internal_precision = 10) { + /** + * 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'); } /** @@ -109,8 +115,8 @@ class HTMLPurifier_UnitConverter // Do the conversion if necessary if ($dest_unit !== $unit) { - $factor = bcdiv(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp); - $n = bcmul($n, $factor, $cp); + $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp); + $n = $this->mul($n, $factor, $cp); $unit = $dest_unit; } @@ -135,7 +141,7 @@ class HTMLPurifier_UnitConverter // Pre-condition: $i == 0 // Perform conversion to next system of units - $n = bcmul($n, self::$units[$state][$dest_state][1], $cp); + $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp); $unit = self::$units[$state][$dest_state][2]; $state = $dest_state; @@ -146,26 +152,11 @@ class HTMLPurifier_UnitConverter // Post-condition: $unit == $to_unit if ($unit !== $to_unit) return false; - // Calculate how many decimals we need ($rp) - // Calculations will always be carried to the decimal; this is - // a limitation with BC (we can't set the scale to be negative) - $new_log = (int) floor(log(abs($n), 10)); - $rp = $sigfigs - $new_log - 1; - $neg = $n < 0 ? '-' : ''; - // Useful for debugging: //echo "
n"; //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n\n"; - if ($rp >= 0) { - $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); - $n = bcdiv($n, '1', $rp); - } else { - if ($new_log + 1 >= $sigfigs) { - $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs)); - $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log + 1 - $sigfigs); - } - } + $n = $this->round($n, $sigfigs); if (strpos($n, '.') !== false) $n = rtrim($n, '0'); $n = rtrim($n, '.'); @@ -189,4 +180,59 @@ class HTMLPurifier_UnitConverter 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/tests/HTMLPurifier/UnitConverterTest.php b/tests/HTMLPurifier/UnitConverterTest.php index 1a8b8700..64ff8a3e 100644 --- a/tests/HTMLPurifier/UnitConverterTest.php +++ b/tests/HTMLPurifier/UnitConverterTest.php @@ -7,9 +7,18 @@ class HTMLPurifier_UnitConverterTest extends HTMLPurifier_Harness $length = HTMLPurifier_Length::make($input); if ($expect !== false) $expectl = HTMLPurifier_Length::make($expect); else $expectl = false; - $converter = new HTMLPurifier_UnitConverter(); - $result = $converter->convert($length, $unit !== null ? $unit : $expectl->getUnit()); - $this->assertIdentical($result, $expectl); + $to_unit = $unit !== null ? $unit : $expectl->getUnit(); + + $converter = new HTMLPurifier_UnitConverter(4, 10); + $result = $converter->convert($length, $to_unit); + if (!$result || !$expectl) $this->assertIdentical($result, $expectl); + else $this->assertIdentical($result->toString(), $expectl->toString()); + + $converter = new HTMLPurifier_UnitConverter(4, 10, true); + $result = $converter->convert($length, $to_unit); + if (!$result || !$expectl) $this->assertIdentical($result, $expectl); + else $this->assertIdentical($result->toString(), $expectl->toString(), 'BCMath substitute: %s'); + if ($test_negative) { $this->assertConversion( "-$input",