1<?php
2
3/**
4 * Validates shorthand CSS property font.
5 */
6class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
7{
8
9    /**
10     * Local copy of validators
11     * @type HTMLPurifier_AttrDef[]
12     * @note If we moved specific CSS property definitions to their own
13     *       classes instead of having them be assembled at run time by
14     *       CSSDefinition, this wouldn't be necessary.  We'd instantiate
15     *       our own copies.
16     */
17    protected $info = array();
18
19    /**
20     * @param HTMLPurifier_Config $config
21     */
22    public function __construct($config)
23    {
24        $def = $config->getCSSDefinition();
25        $this->info['font-style'] = $def->info['font-style'];
26        $this->info['font-variant'] = $def->info['font-variant'];
27        $this->info['font-weight'] = $def->info['font-weight'];
28        $this->info['font-size'] = $def->info['font-size'];
29        $this->info['line-height'] = $def->info['line-height'];
30        $this->info['font-family'] = $def->info['font-family'];
31    }
32
33    /**
34     * @param string $string
35     * @param HTMLPurifier_Config $config
36     * @param HTMLPurifier_Context $context
37     * @return bool|string
38     */
39    public function validate($string, $config, $context)
40    {
41        static $system_fonts = array(
42            'caption' => true,
43            'icon' => true,
44            'menu' => true,
45            'message-box' => true,
46            'small-caption' => true,
47            'status-bar' => true
48        );
49
50        // regular pre-processing
51        $string = $this->parseCDATA($string);
52        if ($string === '') {
53            return false;
54        }
55
56        // check if it's one of the keywords
57        $lowercase_string = strtolower($string);
58        if (isset($system_fonts[$lowercase_string])) {
59            return $lowercase_string;
60        }
61
62        $bits = explode(' ', $string); // bits to process
63        $stage = 0; // this indicates what we're looking for
64        $caught = array(); // which stage 0 properties have we caught?
65        $stage_1 = array('font-style', 'font-variant', 'font-weight');
66        $final = ''; // output
67
68        for ($i = 0, $size = count($bits); $i < $size; $i++) {
69            if ($bits[$i] === '') {
70                continue;
71            }
72            switch ($stage) {
73                case 0: // attempting to catch font-style, font-variant or font-weight
74                    foreach ($stage_1 as $validator_name) {
75                        if (isset($caught[$validator_name])) {
76                            continue;
77                        }
78                        $r = $this->info[$validator_name]->validate(
79                            $bits[$i],
80                            $config,
81                            $context
82                        );
83                        if ($r !== false) {
84                            $final .= $r . ' ';
85                            $caught[$validator_name] = true;
86                            break;
87                        }
88                    }
89                    // all three caught, continue on
90                    if (count($caught) >= 3) {
91                        $stage = 1;
92                    }
93                    if ($r !== false) {
94                        break;
95                    }
96                case 1: // attempting to catch font-size and perhaps line-height
97                    $found_slash = false;
98                    if (strpos($bits[$i], '/') !== false) {
99                        list($font_size, $line_height) =
100                            explode('/', $bits[$i]);
101                        if ($line_height === '') {
102                            // ooh, there's a space after the slash!
103                            $line_height = false;
104                            $found_slash = true;
105                        }
106                    } else {
107                        $font_size = $bits[$i];
108                        $line_height = false;
109                    }
110                    $r = $this->info['font-size']->validate(
111                        $font_size,
112                        $config,
113                        $context
114                    );
115                    if ($r !== false) {
116                        $final .= $r;
117                        // attempt to catch line-height
118                        if ($line_height === false) {
119                            // we need to scroll forward
120                            for ($j = $i + 1; $j < $size; $j++) {
121                                if ($bits[$j] === '') {
122                                    continue;
123                                }
124                                if ($bits[$j] === '/') {
125                                    if ($found_slash) {
126                                        return false;
127                                    } else {
128                                        $found_slash = true;
129                                        continue;
130                                    }
131                                }
132                                $line_height = $bits[$j];
133                                break;
134                            }
135                        } else {
136                            // slash already found
137                            $found_slash = true;
138                            $j = $i;
139                        }
140                        if ($found_slash) {
141                            $i = $j;
142                            $r = $this->info['line-height']->validate(
143                                $line_height,
144                                $config,
145                                $context
146                            );
147                            if ($r !== false) {
148                                $final .= '/' . $r;
149                            }
150                        }
151                        $final .= ' ';
152                        $stage = 2;
153                        break;
154                    }
155                    return false;
156                case 2: // attempting to catch font-family
157                    $font_family =
158                        implode(' ', array_slice($bits, $i, $size - $i));
159                    $r = $this->info['font-family']->validate(
160                        $font_family,
161                        $config,
162                        $context
163                    );
164                    if ($r !== false) {
165                        $final .= $r . ' ';
166                        // processing completed successfully
167                        return rtrim($final);
168                    }
169                    return false;
170            }
171        }
172        return false;
173    }
174}
175
176// vim: et sw=4 sts=4
177