1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\Validator;
11
12use RecursiveArrayIterator;
13use RecursiveIteratorIterator;
14
15class InArray extends AbstractValidator
16{
17    const NOT_IN_ARRAY = 'notInArray';
18
19    // Type of Strict check
20    /**
21     * standard in_array strict checking value and type
22     */
23    const COMPARE_STRICT = 1;
24
25    /**
26     * Non strict check but prevents "asdf" == 0 returning TRUE causing false/positive.
27     * This is the most secure option for non-strict checks and replaces strict = false
28     * This will only be effective when the input is a string
29     */
30    const COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY = 0;
31
32    /**
33     * Standard non-strict check where "asdf" == 0 returns TRUE
34     * This will be wanted when comparing "0" against int 0
35     */
36    const COMPARE_NOT_STRICT = -1;
37
38    /**
39     * @var array
40     */
41    protected $messageTemplates = array(
42        self::NOT_IN_ARRAY => 'The input was not found in the haystack',
43    );
44
45    /**
46     * Haystack of possible values
47     *
48     * @var array
49     */
50    protected $haystack;
51
52    /**
53     * Type of strict check to be used. Due to "foo" == 0 === TRUE with in_array when strict = false,
54     * an option has been added to prevent this. When $strict = 0/false, the most
55     * secure non-strict check is implemented. if $strict = -1, the default in_array non-strict
56     * behaviour is used
57     *
58     * @var int
59     */
60    protected $strict = self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY;
61
62    /**
63     * Whether a recursive search should be done
64     *
65     * @var bool
66     */
67    protected $recursive = false;
68
69    /**
70     * Returns the haystack option
71     *
72     * @return mixed
73     * @throws Exception\RuntimeException if haystack option is not set
74     */
75    public function getHaystack()
76    {
77        if ($this->haystack === null) {
78            throw new Exception\RuntimeException('haystack option is mandatory');
79        }
80        return $this->haystack;
81    }
82
83    /**
84     * Sets the haystack option
85     *
86     * @param  mixed $haystack
87     * @return InArray Provides a fluent interface
88     */
89    public function setHaystack(array $haystack)
90    {
91        $this->haystack = $haystack;
92        return $this;
93    }
94
95    /**
96     * Returns the strict option
97     *
98     * @return bool|int
99     */
100    public function getStrict()
101    {
102        // To keep BC with new strict modes
103        if ($this->strict == self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY
104            || $this->strict == self::COMPARE_STRICT
105        ) {
106            return (bool) $this->strict;
107        }
108        return $this->strict;
109    }
110
111    /**
112     * Sets the strict option mode
113     * InArray::COMPARE_STRICT | InArray::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY | InArray::COMPARE_NOT_STRICT
114     *
115     * @param  int $strict
116     * @return InArray Provides a fluent interface
117     * @throws Exception\InvalidArgumentException
118     */
119    public function setStrict($strict)
120    {
121        $checkTypes = array(
122            self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY,    // 0
123            self::COMPARE_STRICT,                                             // 1
124            self::COMPARE_NOT_STRICT                                          // -1
125        );
126
127        // validate strict value
128        if (!in_array($strict, $checkTypes)) {
129            throw new Exception\InvalidArgumentException('Strict option must be one of the COMPARE_ constants');
130        }
131
132        $this->strict = $strict;
133        return $this;
134    }
135
136    /**
137     * Returns the recursive option
138     *
139     * @return bool
140     */
141    public function getRecursive()
142    {
143        return $this->recursive;
144    }
145
146    /**
147     * Sets the recursive option
148     *
149     * @param  bool $recursive
150     * @return InArray Provides a fluent interface
151     */
152    public function setRecursive($recursive)
153    {
154        $this->recursive = (bool) $recursive;
155        return $this;
156    }
157
158    /**
159     * Returns true if and only if $value is contained in the haystack option. If the strict
160     * option is true, then the type of $value is also checked.
161     *
162     * @param mixed $value
163     * See {@link http://php.net/manual/function.in-array.php#104501}
164     * @return bool
165     */
166    public function isValid($value)
167    {
168        // we create a copy of the haystack in case we need to modify it
169        $haystack = $this->getHaystack();
170
171        // if the input is a string or float, and vulnerability protection is on
172        // we type cast the input to a string
173        if (self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY == $this->strict
174            && (is_int($value) || is_float($value))) {
175            $value = (string) $value;
176        }
177
178        $this->setValue($value);
179
180        if ($this->getRecursive()) {
181            $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($haystack));
182            foreach ($iterator as $element) {
183                if (self::COMPARE_STRICT == $this->strict) {
184                    if ($element === $value) {
185                        return true;
186                    }
187                } else {
188                    // add protection to prevent string to int vuln's
189                    $el = $element;
190                    if (self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY == $this->strict
191                        && is_string($value) && (is_int($el) || is_float($el))
192                    ) {
193                        $el = (string) $el;
194                    }
195
196                    if ($el == $value) {
197                        return true;
198                    }
199                }
200            }
201        } else {
202            /**
203             * If the check is not strict, then, to prevent "asdf" being converted to 0
204             * and returning a false positive if 0 is in haystack, we type cast
205             * the haystack to strings. To prevent "56asdf" == 56 === TRUE we also
206             * type cast values like 56 to strings as well.
207             *
208             * This occurs only if the input is a string and a haystack member is an int
209             */
210            if (self::COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY == $this->strict
211                && is_string($value)
212            ) {
213                foreach ($haystack as &$h) {
214                    if (is_int($h) || is_float($h)) {
215                        $h = (string) $h;
216                    }
217                }
218            }
219
220            if (in_array($value, $haystack, self::COMPARE_STRICT == $this->strict)) {
221                return true;
222            }
223        }
224
225        $this->error(self::NOT_IN_ARRAY);
226        return false;
227    }
228}
229