123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- <?php
- /*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Symfony\Component\Validator\Constraints;
- use Symfony\Component\Validator\Constraint;
- use Symfony\Component\Validator\ConstraintValidator;
- use Symfony\Component\Validator\Exception\UnexpectedTypeException;
- use Symfony\Component\Validator\Exception\UnexpectedValueException;
- /**
- * Validates whether the value is a valid UUID (also known as GUID).
- *
- * Strict validation will allow a UUID as specified per RFC 4122.
- * Loose validation will allow any type of UUID.
- *
- * @author Colin O'Dell <colinodell@gmail.com>
- * @author Bernhard Schussek <bschussek@gmail.com>
- *
- * @see http://tools.ietf.org/html/rfc4122
- * @see https://en.wikipedia.org/wiki/Universally_unique_identifier
- */
- class UuidValidator extends ConstraintValidator
- {
- // The strict pattern matches UUIDs like this:
- // xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
- // Roughly speaking:
- // x = any hexadecimal character
- // M = any allowed version {1..6}
- // N = any allowed variant {8, 9, a, b}
- public const STRICT_LENGTH = 36;
- public const STRICT_FIRST_HYPHEN_POSITION = 8;
- public const STRICT_LAST_HYPHEN_POSITION = 23;
- public const STRICT_VERSION_POSITION = 14;
- public const STRICT_VARIANT_POSITION = 19;
- // The loose pattern validates similar yet non-compliant UUIDs.
- // Hyphens are completely optional. If present, they should only appear
- // between every fourth character:
- // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
- // xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx
- // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- // The value can also be wrapped with characters like []{}:
- // {xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx}
- // Neither the version nor the variant is validated by this pattern.
- public const LOOSE_MAX_LENGTH = 39;
- public const LOOSE_FIRST_HYPHEN_POSITION = 4;
- /**
- * {@inheritdoc}
- */
- public function validate($value, Constraint $constraint)
- {
- if (!$constraint instanceof Uuid) {
- throw new UnexpectedTypeException($constraint, Uuid::class);
- }
- if (null === $value || '' === $value) {
- return;
- }
- if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
- throw new UnexpectedValueException($value, 'string');
- }
- $value = (string) $value;
- if (null !== $constraint->normalizer) {
- $value = ($constraint->normalizer)($value);
- }
- if ($constraint->strict) {
- $this->validateStrict($value, $constraint);
- return;
- }
- $this->validateLoose($value, $constraint);
- }
- private function validateLoose(string $value, Uuid $constraint)
- {
- // Error priority:
- // 1. ERROR_INVALID_CHARACTERS
- // 2. ERROR_INVALID_HYPHEN_PLACEMENT
- // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
- // Trim any wrapping characters like [] or {} used by some legacy systems
- $trimmed = trim($value, '[]{}');
- // Position of the next expected hyphen
- $h = self::LOOSE_FIRST_HYPHEN_POSITION;
- // Expected length
- $l = self::LOOSE_MAX_LENGTH;
- for ($i = 0; $i < $l; ++$i) {
- // Check length
- if (!isset($trimmed[$i])) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::TOO_SHORT_ERROR)
- ->addViolation();
- return;
- }
- // Hyphens must occur every fifth position
- // xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx
- // ^ ^ ^ ^ ^ ^ ^
- if ('-' === $trimmed[$i]) {
- if ($i !== $h) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
- ->addViolation();
- return;
- }
- $h += 5;
- continue;
- }
- // Missing hyphens are ignored
- if ($i === $h) {
- $h += 4;
- --$l;
- }
- // Check characters
- if (!ctype_xdigit($trimmed[$i])) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
- ->addViolation();
- return;
- }
- }
- // Check length again
- if (isset($trimmed[$i])) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::TOO_LONG_ERROR)
- ->addViolation();
- }
- }
- private function validateStrict(string $value, Uuid $constraint)
- {
- // Error priority:
- // 1. ERROR_INVALID_CHARACTERS
- // 2. ERROR_INVALID_HYPHEN_PLACEMENT
- // 3. ERROR_TOO_SHORT/ERROR_TOO_LONG
- // 4. ERROR_INVALID_VERSION
- // 5. ERROR_INVALID_VARIANT
- // Position of the next expected hyphen
- $h = self::STRICT_FIRST_HYPHEN_POSITION;
- for ($i = 0; $i < self::STRICT_LENGTH; ++$i) {
- // Check length
- if (!isset($value[$i])) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::TOO_SHORT_ERROR)
- ->addViolation();
- return;
- }
- // Check hyphen placement
- // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- // ^ ^ ^ ^
- if ('-' === $value[$i]) {
- if ($i !== $h) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
- ->addViolation();
- return;
- }
- // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
- // ^
- if ($h < self::STRICT_LAST_HYPHEN_POSITION) {
- $h += 5;
- }
- continue;
- }
- // Check characters
- if (!ctype_xdigit($value[$i])) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_CHARACTERS_ERROR)
- ->addViolation();
- return;
- }
- // Missing hyphen
- if ($i === $h) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_HYPHEN_PLACEMENT_ERROR)
- ->addViolation();
- return;
- }
- }
- // Check length again
- if (isset($value[$i])) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::TOO_LONG_ERROR)
- ->addViolation();
- }
- // Check version
- if (!\in_array($value[self::STRICT_VERSION_POSITION], $constraint->versions)) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_VERSION_ERROR)
- ->addViolation();
- }
- // Check variant - first two bits must equal "10"
- // 0b10xx
- // & 0b1100 (12)
- // = 0b1000 (8)
- if (8 !== (hexdec($value[self::STRICT_VARIANT_POSITION]) & 12)) {
- $this->context->buildViolation($constraint->message)
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(Uuid::INVALID_VARIANT_ERROR)
- ->addViolation();
- }
- }
- }
|