2015-08-17 17:00:26 -07:00
< ? 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\Context\ExecutionContextInterface ;
use Symfony\Component\Validator\Constraint ;
use Symfony\Component\Validator\ConstraintValidator ;
use Symfony\Component\Validator\Constraints\Deprecated\UuidValidator as Deprecated ;
use Symfony\Component\Validator\Exception\UnexpectedTypeException ;
/**
* Validates whether the value is a valid UUID per RFC 4122.
*
* @ 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..5}
// N = any allowed variant {8, 9, a, b}
const STRICT_LENGTH = 36 ;
const STRICT_FIRST_HYPHEN_POSITION = 8 ;
const STRICT_LAST_HYPHEN_POSITION = 23 ;
const STRICT_VERSION_POSITION = 14 ;
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.
const LOOSE_MAX_LENGTH = 39 ;
const LOOSE_FIRST_HYPHEN_POSITION = 4 ;
/**
* @ deprecated since version 2.6 , to be removed in 3.0
*/
const STRICT_PATTERN = '/^[a-f0-9]{8}-[a-f0-9]{4}-[%s][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i' ;
/**
* @ deprecated since version 2.6 , to be removed in 3.0
*/
const LOOSE_PATTERN = '/^[a-f0-9]{4}(?:-?[a-f0-9]{4}){7}$/i' ;
/**
* @ deprecated since version 2.6 , to be removed in 3.0
*/
const STRICT_UUID_LENGTH = 36 ;
/**
* { @ inheritdoc }
*/
public function validate ( $value , Constraint $constraint )
{
if ( null === $value || '' === $value ) {
return ;
}
2017-02-02 16:28:38 -08:00
if ( ! $constraint instanceof Uuid ) {
throw new UnexpectedTypeException ( $constraint , __NAMESPACE__ . '\Uuid' );
}
2015-08-17 17:00:26 -07:00
if ( ! is_scalar ( $value ) && ! ( is_object ( $value ) && method_exists ( $value , '__toString' ))) {
throw new UnexpectedTypeException ( $value , 'string' );
}
$value = ( string ) $value ;
if ( $constraint -> strict ) {
$this -> validateStrict ( $value , $constraint );
return ;
}
$this -> validateLoose ( $value , $constraint );
}
private function validateLoose ( $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
2017-02-02 16:28:38 -08:00
if ( ! isset ( $trimmed [ $i ])) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_SHORT_ERROR )
-> addViolation ();
} else {
$this -> 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
// ^ ^ ^ ^ ^ ^ ^
2017-02-02 16:28:38 -08:00
if ( '-' === $trimmed [ $i ]) {
2015-08-17 17:00:26 -07:00
if ( $i !== $h ) {
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_HYPHEN_PLACEMENT_ERROR )
-> addViolation ();
} else {
$this -> 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
2017-02-02 16:28:38 -08:00
if ( ! ctype_xdigit ( $trimmed [ $i ])) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_CHARACTERS_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_CHARACTERS_ERROR )
-> addViolation ();
}
return ;
}
}
// Check length again
2017-02-02 16:28:38 -08:00
if ( isset ( $trimmed [ $i ])) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_LONG_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_LONG_ERROR )
-> addViolation ();
}
}
}
private function validateStrict ( $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
2017-02-02 16:28:38 -08:00
if ( ! isset ( $value [ $i ])) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_SHORT_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_SHORT_ERROR )
-> addViolation ();
}
return ;
}
// Check hyphen placement
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// ^ ^ ^ ^
2017-02-02 16:28:38 -08:00
if ( '-' === $value [ $i ]) {
2015-08-17 17:00:26 -07:00
if ( $i !== $h ) {
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
2017-02-02 16:28:38 -08:00
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_HYPHEN_PLACEMENT_ERROR )
-> addViolation ();
2015-08-17 17:00:26 -07:00
} else {
$this -> buildViolation ( $constraint -> message )
2017-02-02 16:28:38 -08:00
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_HYPHEN_PLACEMENT_ERROR )
-> addViolation ();
2015-08-17 17:00:26 -07:00
}
return ;
}
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// ^
if ( $h < self :: STRICT_LAST_HYPHEN_POSITION ) {
$h += 5 ;
}
continue ;
}
// Check characters
2017-02-02 16:28:38 -08:00
if ( ! ctype_xdigit ( $value [ $i ])) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_CHARACTERS_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_CHARACTERS_ERROR )
-> addViolation ();
}
return ;
}
// Missing hyphen
if ( $i === $h ) {
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_HYPHEN_PLACEMENT_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_HYPHEN_PLACEMENT_ERROR )
-> addViolation ();
}
return ;
}
}
// Check length again
2017-02-02 16:28:38 -08:00
if ( isset ( $value [ $i ])) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_LONG_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: TOO_LONG_ERROR )
-> addViolation ();
}
}
// Check version
2017-02-02 16:28:38 -08:00
if ( ! in_array ( $value [ self :: STRICT_VERSION_POSITION ], $constraint -> versions )) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_VERSION_ERROR )
-> addViolation ();
} else {
$this -> 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)
2017-02-02 16:28:38 -08:00
if (( hexdec ( $value [ self :: STRICT_VARIANT_POSITION ]) & 12 ) !== 8 ) {
2015-08-17 17:00:26 -07:00
if ( $this -> context instanceof ExecutionContextInterface ) {
$this -> context -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_VARIANT_ERROR )
-> addViolation ();
} else {
$this -> buildViolation ( $constraint -> message )
-> setParameter ( '{{ value }}' , $this -> formatValue ( $value ))
-> setCode ( Uuid :: INVALID_VARIANT_ERROR )
-> addViolation ();
}
}
}
}