1<?php
2
3/**
4 * Validates Color as defined by CSS.
5 */
6class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
7{
8
9    /**
10     * @type HTMLPurifier_AttrDef_CSS_AlphaValue
11     */
12    protected $alpha;
13
14    public function __construct()
15    {
16        $this->alpha = new HTMLPurifier_AttrDef_CSS_AlphaValue();
17    }
18
19    /**
20     * @param string $color
21     * @param HTMLPurifier_Config $config
22     * @param HTMLPurifier_Context $context
23     * @return bool|string
24     */
25    public function validate($color, $config, $context)
26    {
27        static $colors = null;
28        if ($colors === null) {
29            $colors = $config->get('Core.ColorKeywords');
30        }
31
32        $color = trim($color);
33        if ($color === '') {
34            return false;
35        }
36
37        $lower = strtolower($color);
38        if (isset($colors[$lower])) {
39            return $colors[$lower];
40        }
41
42        if (preg_match('#(rgb|rgba|hsl|hsla)\(#', $color, $matches) === 1) {
43            $length = strlen($color);
44            if (strpos($color, ')') !== $length - 1) {
45                return false;
46            }
47
48            // get used function : rgb, rgba, hsl or hsla
49            $function = $matches[1];
50
51            $parameters_size = 3;
52            $alpha_channel = false;
53            if (substr($function, -1) === 'a') {
54                $parameters_size = 4;
55                $alpha_channel = true;
56            }
57
58            /*
59             * Allowed types for values :
60             * parameter_position => [type => max_value]
61             */
62            $allowed_types = array(
63                1 => array('percentage' => 100, 'integer' => 255),
64                2 => array('percentage' => 100, 'integer' => 255),
65                3 => array('percentage' => 100, 'integer' => 255),
66            );
67            $allow_different_types = false;
68
69            if (strpos($function, 'hsl') !== false) {
70                $allowed_types = array(
71                    1 => array('integer' => 360),
72                    2 => array('percentage' => 100),
73                    3 => array('percentage' => 100),
74                );
75                $allow_different_types = true;
76            }
77
78            $values = trim(str_replace($function, '', $color), ' ()');
79
80            $parts = explode(',', $values);
81            if (count($parts) !== $parameters_size) {
82                return false;
83            }
84
85            $type = false;
86            $new_parts = array();
87            $i = 0;
88
89            foreach ($parts as $part) {
90                $i++;
91                $part = trim($part);
92
93                if ($part === '') {
94                    return false;
95                }
96
97                // different check for alpha channel
98                if ($alpha_channel === true && $i === count($parts)) {
99                    $result = $this->alpha->validate($part, $config, $context);
100
101                    if ($result === false) {
102                        return false;
103                    }
104
105                    $new_parts[] = (string)$result;
106                    continue;
107                }
108
109                if (substr($part, -1) === '%') {
110                    $current_type = 'percentage';
111                } else {
112                    $current_type = 'integer';
113                }
114
115                if (!array_key_exists($current_type, $allowed_types[$i])) {
116                    return false;
117                }
118
119                if (!$type) {
120                    $type = $current_type;
121                }
122
123                if ($allow_different_types === false && $type != $current_type) {
124                    return false;
125                }
126
127                $max_value = $allowed_types[$i][$current_type];
128
129                if ($current_type == 'integer') {
130                    // Return value between range 0 -> $max_value
131                    $new_parts[] = (int)max(min($part, $max_value), 0);
132                } elseif ($current_type == 'percentage') {
133                    $new_parts[] = (float)max(min(rtrim($part, '%'), $max_value), 0) . '%';
134                }
135            }
136
137            $new_values = implode(',', $new_parts);
138
139            $color = $function . '(' . $new_values . ')';
140        } else {
141            // hexadecimal handling
142            if ($color[0] === '#') {
143                $hex = substr($color, 1);
144            } else {
145                $hex = $color;
146                $color = '#' . $color;
147            }
148            $length = strlen($hex);
149            if ($length !== 3 && $length !== 6) {
150                return false;
151            }
152            if (!ctype_xdigit($hex)) {
153                return false;
154            }
155        }
156        return $color;
157    }
158
159}
160
161// vim: et sw=4 sts=4
162