38e9a0da8585ed441247c12bd91d4011154ce91b
[yaffs-website] / vendor / symfony / validator / Constraints / UuidValidator.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Validator\Constraints;
13
14 use Symfony\Component\Validator\Constraint;
15 use Symfony\Component\Validator\ConstraintValidator;
16 use Symfony\Component\Validator\Exception\UnexpectedTypeException;
17
18 /**
19  * Validates whether the value is a valid UUID (also known as GUID).
20  *
21  * Strict validation will allow a UUID as specified per RFC 4122.
22  * Loose validation will allow any type of UUID.
23  *
24  * For better compatibility, both loose and strict, you should consider using a specialized UUID library like "ramsey/uuid" instead.
25  *
26  * @author Colin O'Dell <colinodell@gmail.com>
27  * @author Bernhard Schussek <bschussek@gmail.com>
28  *
29  * @see http://tools.ietf.org/html/rfc4122
30  * @see https://en.wikipedia.org/wiki/Universally_unique_identifier
31  * @see https://github.com/ramsey/uuid
32  */
33 class UuidValidator extends ConstraintValidator
34 {
35     // The strict pattern matches UUIDs like this:
36     // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
37
38     // Roughly speaking:
39     // x = any hexadecimal character
40     // M = any allowed version {1..5}
41     // N = any allowed variant {8, 9, a, b}
42
43     const STRICT_LENGTH = 36;
44     const STRICT_FIRST_HYPHEN_POSITION = 8;
45     const STRICT_LAST_HYPHEN_POSITION = 23;
46     const STRICT_VERSION_POSITION = 14;
47     const STRICT_VARIANT_POSITION = 19;
48
49     // The loose pattern validates similar yet non-compliant UUIDs.
50     // Hyphens are completely optional. If present, they should only appear
51     // between every fourth character:
52     // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
53     // xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx
54     // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
55
56     // The value can also be wrapped with characters like []{}:
57     // {xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx}
58
59     // Neither the version nor the variant is validated by this pattern.
60
61     const LOOSE_MAX_LENGTH = 39;
62     const LOOSE_FIRST_HYPHEN_POSITION = 4;
63
64     /**
65      * {@inheritdoc}
66      */
67     public function validate($value, Constraint $constraint)
68     {
69         if (null === $value || '' === $value) {
70             return;
71         }
72
73         if (!$constraint instanceof Uuid) {
74             throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Uuid');
75         }
76
77         if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
78             throw new UnexpectedTypeException($value, 'string');
79         }
80
81         $value = (string) $value;
82
83         if ($constraint->strict) {
84             $this->validateStrict($value, $constraint);
85
86             return;
87         }
88
89         $this->validateLoose($value, $constraint);
90     }
91
92     private function validateLoose($value, Uuid $constraint)
93     {
94         // Error priority:
95         // 1. ERROR_INVALID_CHARACTERS
96         // 2. ERROR_INVALID_HYPHEN_PLACEMENT
97         // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
98
99         // Trim any wrapping characters like [] or {} used by some legacy systems
100         $trimmed = trim($value, '[]{}');
101
102         // Position of the next expected hyphen
103         $h = self::LOOSE_FIRST_HYPHEN_POSITION;
104
105         // Expected length
106         $l = self::LOOSE_MAX_LENGTH;
107
108         for ($i = 0; $i < $l; ++$i) {
109             // Check length
110             if (!isset($trimmed[$i])) {
111                 $this->context->buildViolation($constraint->message)
112                     ->setParameter('{{ value }}', $this->formatValue($value))
113                     ->setCode(Uuid::TOO_SHORT_ERROR)
114                     ->addViolation();
115
116                 return;
117             }
118
119             // Hyphens must occur every fifth position
120             // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
121             //     ^    ^    ^    ^    ^    ^    ^
122             if ('-' === $trimmed[$i]) {
123                 if ($i !== $h) {
124                     $this->context->buildViolation($constraint->message)
125                         ->setParameter('{{ value }}', $this->formatValue($value))
126                         ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
127                         ->addViolation();
128
129                     return;
130                 }
131
132                 $h += 5;
133
134                 continue;
135             }
136
137             // Missing hyphens are ignored
138             if ($i === $h) {
139                 $h += 4;
140                 --$l;
141             }
142
143             // Check characters
144             if (!ctype_xdigit($trimmed[$i])) {
145                 $this->context->buildViolation($constraint->message)
146                     ->setParameter('{{ value }}', $this->formatValue($value))
147                     ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
148                     ->addViolation();
149
150                 return;
151             }
152         }
153
154         // Check length again
155         if (isset($trimmed[$i])) {
156             $this->context->buildViolation($constraint->message)
157                 ->setParameter('{{ value }}', $this->formatValue($value))
158                 ->setCode(Uuid::TOO_LONG_ERROR)
159                 ->addViolation();
160         }
161     }
162
163     private function validateStrict($value, Uuid $constraint)
164     {
165         // Error priority:
166         // 1. ERROR_INVALID_CHARACTERS
167         // 2. ERROR_INVALID_HYPHEN_PLACEMENT
168         // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
169         // 4. ERROR_INVALID_VERSION
170         // 5. ERROR_INVALID_VARIANT
171
172         // Position of the next expected hyphen
173         $h = self::STRICT_FIRST_HYPHEN_POSITION;
174
175         for ($i = 0; $i < self::STRICT_LENGTH; ++$i) {
176             // Check length
177             if (!isset($value[$i])) {
178                 $this->context->buildViolation($constraint->message)
179                     ->setParameter('{{ value }}', $this->formatValue($value))
180                     ->setCode(Uuid::TOO_SHORT_ERROR)
181                     ->addViolation();
182
183                 return;
184             }
185
186             // Check hyphen placement
187             // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
188             //         ^    ^    ^    ^
189             if ('-' === $value[$i]) {
190                 if ($i !== $h) {
191                     $this->context->buildViolation($constraint->message)
192                         ->setParameter('{{ value }}', $this->formatValue($value))
193                         ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
194                         ->addViolation();
195
196                     return;
197                 }
198
199                 // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
200                 //                        ^
201                 if ($h < self::STRICT_LAST_HYPHEN_POSITION) {
202                     $h += 5;
203                 }
204
205                 continue;
206             }
207
208             // Check characters
209             if (!ctype_xdigit($value[$i])) {
210                 $this->context->buildViolation($constraint->message)
211                     ->setParameter('{{ value }}', $this->formatValue($value))
212                     ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
213                     ->addViolation();
214
215                 return;
216             }
217
218             // Missing hyphen
219             if ($i === $h) {
220                 $this->context->buildViolation($constraint->message)
221                     ->setParameter('{{ value }}', $this->formatValue($value))
222                     ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
223                     ->addViolation();
224
225                 return;
226             }
227         }
228
229         // Check length again
230         if (isset($value[$i])) {
231             $this->context->buildViolation($constraint->message)
232                 ->setParameter('{{ value }}', $this->formatValue($value))
233                 ->setCode(Uuid::TOO_LONG_ERROR)
234                 ->addViolation();
235         }
236
237         // Check version
238         if (!\in_array($value[self::STRICT_VERSION_POSITION], $constraint->versions)) {
239             $this->context->buildViolation($constraint->message)
240                 ->setParameter('{{ value }}', $this->formatValue($value))
241                 ->setCode(Uuid::INVALID_VERSION_ERROR)
242                 ->addViolation();
243         }
244
245         // Check variant - first two bits must equal "10"
246         //   0b10xx
247         // & 0b1100 (12)
248         // = 0b1000 (8)
249         if (8 !== (hexdec($value[self::STRICT_VARIANT_POSITION]) & 12)) {
250             $this->context->buildViolation($constraint->message)
251                 ->setParameter('{{ value }}', $this->formatValue($value))
252                 ->setCode(Uuid::INVALID_VARIANT_ERROR)
253                 ->addViolation();
254         }
255     }
256 }