1<?php
2
3/**
4 * Parses string representations into their corresponding native PHP
5 * variable type. The base implementation does a simple type-check.
6 */
7class HTMLPurifier_VarParser
8{
9
10    const C_STRING = 1;
11    const ISTRING = 2;
12    const TEXT = 3;
13    const ITEXT = 4;
14    const C_INT = 5;
15    const C_FLOAT = 6;
16    const C_BOOL = 7;
17    const LOOKUP = 8;
18    const ALIST = 9;
19    const HASH = 10;
20    const C_MIXED = 11;
21
22    /**
23     * Lookup table of allowed types. Mainly for backwards compatibility, but
24     * also convenient for transforming string type names to the integer constants.
25     */
26    public static $types = array(
27        'string' => self::C_STRING,
28        'istring' => self::ISTRING,
29        'text' => self::TEXT,
30        'itext' => self::ITEXT,
31        'int' => self::C_INT,
32        'float' => self::C_FLOAT,
33        'bool' => self::C_BOOL,
34        'lookup' => self::LOOKUP,
35        'list' => self::ALIST,
36        'hash' => self::HASH,
37        'mixed' => self::C_MIXED
38    );
39
40    /**
41     * Lookup table of types that are string, and can have aliases or
42     * allowed value lists.
43     */
44    public static $stringTypes = array(
45        self::C_STRING => true,
46        self::ISTRING => true,
47        self::TEXT => true,
48        self::ITEXT => true,
49    );
50
51    /**
52     * Validate a variable according to type.
53     * It may return NULL as a valid type if $allow_null is true.
54     *
55     * @param mixed $var Variable to validate
56     * @param int $type Type of variable, see HTMLPurifier_VarParser->types
57     * @param bool $allow_null Whether or not to permit null as a value
58     * @return string Validated and type-coerced variable
59     * @throws HTMLPurifier_VarParserException
60     */
61    final public function parse($var, $type, $allow_null = false)
62    {
63        if (is_string($type)) {
64            if (!isset(HTMLPurifier_VarParser::$types[$type])) {
65                throw new HTMLPurifier_VarParserException("Invalid type '$type'");
66            } else {
67                $type = HTMLPurifier_VarParser::$types[$type];
68            }
69        }
70        $var = $this->parseImplementation($var, $type, $allow_null);
71        if ($allow_null && $var === null) {
72            return null;
73        }
74        // These are basic checks, to make sure nothing horribly wrong
75        // happened in our implementations.
76        switch ($type) {
77            case (self::C_STRING):
78            case (self::ISTRING):
79            case (self::TEXT):
80            case (self::ITEXT):
81                if (!is_string($var)) {
82                    break;
83                }
84                if ($type == self::ISTRING || $type == self::ITEXT) {
85                    $var = strtolower($var);
86                }
87                return $var;
88            case (self::C_INT):
89                if (!is_int($var)) {
90                    break;
91                }
92                return $var;
93            case (self::C_FLOAT):
94                if (!is_float($var)) {
95                    break;
96                }
97                return $var;
98            case (self::C_BOOL):
99                if (!is_bool($var)) {
100                    break;
101                }
102                return $var;
103            case (self::LOOKUP):
104            case (self::ALIST):
105            case (self::HASH):
106                if (!is_array($var)) {
107                    break;
108                }
109                if ($type === self::LOOKUP) {
110                    foreach ($var as $k) {
111                        if ($k !== true) {
112                            $this->error('Lookup table contains value other than true');
113                        }
114                    }
115                } elseif ($type === self::ALIST) {
116                    $keys = array_keys($var);
117                    if (array_keys($keys) !== $keys) {
118                        $this->error('Indices for list are not uniform');
119                    }
120                }
121                return $var;
122            case (self::C_MIXED):
123                return $var;
124            default:
125                $this->errorInconsistent(get_class($this), $type);
126        }
127        $this->errorGeneric($var, $type);
128    }
129
130    /**
131     * Actually implements the parsing. Base implementation does not
132     * do anything to $var. Subclasses should overload this!
133     * @param mixed $var
134     * @param int $type
135     * @param bool $allow_null
136     * @return string
137     */
138    protected function parseImplementation($var, $type, $allow_null)
139    {
140        return $var;
141    }
142
143    /**
144     * Throws an exception.
145     * @throws HTMLPurifier_VarParserException
146     */
147    protected function error($msg)
148    {
149        throw new HTMLPurifier_VarParserException($msg);
150    }
151
152    /**
153     * Throws an inconsistency exception.
154     * @note This should not ever be called. It would be called if we
155     *       extend the allowed values of HTMLPurifier_VarParser without
156     *       updating subclasses.
157     * @param string $class
158     * @param int $type
159     * @throws HTMLPurifier_Exception
160     */
161    protected function errorInconsistent($class, $type)
162    {
163        throw new HTMLPurifier_Exception(
164            "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) .
165            " not implemented"
166        );
167    }
168
169    /**
170     * Generic error for if a type didn't work.
171     * @param mixed $var
172     * @param int $type
173     */
174    protected function errorGeneric($var, $type)
175    {
176        $vtype = gettype($var);
177        $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype");
178    }
179
180    /**
181     * @param int $type
182     * @return string
183     */
184    public static function getTypeName($type)
185    {
186        static $lookup;
187        if (!$lookup) {
188            // Lazy load the alternative lookup table
189            $lookup = array_flip(HTMLPurifier_VarParser::$types);
190        }
191        if (!isset($lookup[$type])) {
192            return 'unknown';
193        }
194        return $lookup[$type];
195    }
196}
197
198// vim: et sw=4 sts=4
199