From 0d5ab2fe13a50756090b46a4b72dbccea5912086 Mon Sep 17 00:00:00 2001 From: "f.godfrin" Date: Thu, 9 Feb 2017 23:34:19 +0100 Subject: [PATCH] Include hsl and hsla support --- library/HTMLPurifier/AttrDef/CSS/Color.php | 108 +++++++++++-------- tests/HTMLPurifier/AttrDef/CSS/ColorTest.php | 17 ++- 2 files changed, 80 insertions(+), 45 deletions(-) diff --git a/library/HTMLPurifier/AttrDef/CSS/Color.php b/library/HTMLPurifier/AttrDef/CSS/Color.php index e64bc936..2773487f 100644 --- a/library/HTMLPurifier/AttrDef/CSS/Color.php +++ b/library/HTMLPurifier/AttrDef/CSS/Color.php @@ -29,39 +29,63 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef return $colors[$lower]; } - if (preg_match('#(rgb|rgba)\(#', $color, $matches) === 1) { - // get used function : rgb or rgba - $function = $matches[1]; - if ($function == 'rgba') { - $parameters_size = 4; - } else { - $parameters_size = 3; - } - - // rgb literal handling + if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) { $length = strlen($color); if (strpos($color, ')') !== $length - 1) { return false; } - $values = substr($color, strlen($function) + 1, $length - strlen($function) - 2); + // get used function : rgb, rgba, hsl or hsla + $function = $matches[1]; + + $parameters_size = 3; + $alpha_channel = false; + if (substr($function, -1) === 'a') { + $parameters_size = 4; + $alpha_channel = true; + } + + /* + * Allowed types for values : + * parameter_position => [type => max_value] + */ + $allowed_types = [ + 1 => ['percentage' => 100, 'integer' => 255], + 2 => ['percentage' => 100, 'integer' => 255], + 3 => ['percentage' => 100, 'integer' => 255], + ]; + $allow_different_types = false; + + if (strpos($function, 'hsl') !== false) { + $allowed_types = [ + 1 => ['integer' => 360], + 2 => ['percentage' => 100], + 3 => ['percentage' => 100], + ]; + $allow_different_types = true; + } + + $values = trim(str_replace($function, '', $color), ' ()'); $parts = explode(',', $values); if (count($parts) !== $parameters_size) { return false; } - $type = false; // to ensure that they're all the same type + + $type = false; $new_parts = array(); $i = 0; + foreach ($parts as $part) { $i++; $part = trim($part); + if ($part === '') { return false; } // different check for alpha channel - if ($function === 'rgba' && $i === count($parts)) { + if ($alpha_channel === true && $i === count($parts)) { $result = (new HTMLPurifier_AttrDef_CSS_AlphaValue())->validate($part, $config, $context); if ($result === false) { @@ -72,41 +96,37 @@ class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef continue; } - $length = strlen($part); - if ($part[$length - 1] === '%') { - // handle percents - if (!$type) { - $type = 'percentage'; - } elseif ($type !== 'percentage') { - return false; - } - $num = (float)substr($part, 0, $length - 1); - if ($num < 0) { - $num = 0; - } - if ($num > 100) { - $num = 100; - } - $new_parts[] = "$num%"; + if (substr($part, -1) === '%') { + $current_type = 'percentage'; } else { - // handle integers - if (!$type) { - $type = 'integer'; - } elseif ($type !== 'integer') { - return false; - } - $num = (int)$part; - if ($num < 0) { - $num = 0; - } - if ($num > 255) { - $num = 255; - } - $new_parts[] = (string)$num; + $current_type = 'integer'; + } + + if (!array_key_exists($current_type, $allowed_types[$i])) { + return false; + } + + if (!$type) { + $type = $current_type; + } + + if ($allow_different_types === false && $type != $current_type) { + return false; + } + + $max_value = $allowed_types[$i][$current_type]; + + if ($current_type == 'integer') { + // Return value between range 0 -> $max_value + $new_parts[] = (int)max(min($part, $max_value), 0); + } elseif ($current_type == 'percentage') { + $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%'; } } + $new_values = implode(',', $new_parts); - $color = "$function($new_values)"; + + $color = $function . '(' . $new_values . ')'; } else { // hexadecimal handling if ($color[0] === '#') { diff --git a/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php b/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php index 9586f063..e6b4d856 100644 --- a/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php +++ b/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php @@ -15,14 +15,29 @@ class HTMLPurifier_AttrDef_CSS_ColorTest extends HTMLPurifier_AttrDefHarness $this->assertDef('rgb(255, 0, 0)', 'rgb(255,0,0)'); // rm spaces $this->assertDef('rgb(100%,0%,0%)'); $this->assertDef('rgb(50.5%,23.2%,43.9%)'); // decimals okay + $this->assertDef('rgb(-5,0,0)', 'rgb(0,0,0)'); // negative values + $this->assertDef('rgb(295,0,0)', 'rgb(255,0,0)'); // max values + $this->assertDef('rgb(12%,150%,0%)', 'rgb(12%,100%,0%)'); // percentage max values $this->assertDef('rgba(255, 0, 0, 0)', 'rgba(255,0,0,0)'); // rm spaces $this->assertDef('rgba(100%,0%,0%,.4)'); $this->assertDef('rgba(38.1%,59.7%,1.8%,0.7)', 'rgba(38.1%,59.7%,1.8%,.7)'); // decimals okay + $this->assertDef('hsl(275, 45%, 81%)', 'hsl(275,45%,81%)'); // rm spaces + $this->assertDef('hsl(100,0%,0%)'); + $this->assertDef('hsl(38,59.7%,1.8%)', 'hsl(38,59.7%,1.8%)'); // decimals okay + $this->assertDef('hsl(-11,-15%,25%)', 'hsl(0,0%,25%)'); // negative values + $this->assertDef('hsl(380,125%,0%)', 'hsl(360,100%,0%)'); // max values + + $this->assertDef('hsla(100, 74%, 29%, 0)', 'hsla(100,74%,29%,0)'); // rm spaces + $this->assertDef('hsla(154,87%,21%,.4)'); + $this->assertDef('hsla(45,94.3%,4.1%,0.7)', 'hsla(45,94.3%,4.1%,.7)'); // decimals okay + $this->assertDef('#G00', false); $this->assertDef('cmyk(40, 23, 43, 23)', false); - $this->assertDef('rgb(0%, 23, 68%)', false); + $this->assertDef('rgb(0%, 23, 68%)', false); // no mixed type + $this->assertDef('rgb(231, 144, 28.2%)', false); // no mixed type + $this->assertDef('hsl(18%,12%%,89%)', false); // integer, percentage, percentage // clip numbers outside sRGB gamut $this->assertDef('rgb(200%, -10%, 0%)', 'rgb(100%,0%,0%)');