Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Validator] Implemented @ip constraint
- Loading branch information
Showing
3 changed files
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
/* | ||
* This file is part of the Symfony framework. | ||
* | ||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
use Symfony\Component\Validator\Exception\ConstraintDefinitionException; | ||
|
||
/** | ||
* Validates that a value is a valid IP address | ||
* | ||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com> | ||
*/ | ||
class Ip extends \Symfony\Component\Validator\Constraint | ||
{ | ||
const V4 = '4'; | ||
const V6 = '6'; | ||
const ALL = 'all'; | ||
|
||
static protected $versions = array( | ||
self::V4, | ||
self::V6, | ||
self::ALL, | ||
); | ||
|
||
public $version = self::V4; | ||
|
||
public $message = 'This is not a valid IP address'; | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
public function __construct($options = null) | ||
{ | ||
parent::__construct($options); | ||
|
||
if (!in_array($this->version, self::$versions)) { | ||
throw new ConstraintDefinitionException(sprintf('The option "version" must be one of "%s"', implode('", "', self::$versions))); | ||
} | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
src/Symfony/Component/Validator/Constraints/IpValidator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\Validator\Constraints; | ||
|
||
/* | ||
* This file is part of the Symfony framework. | ||
* | ||
* (c) Fabien Potencier <fabien.potencier@symfony-project.com> | ||
* | ||
* This source file is subject to the MIT license that is bundled | ||
* with this source code in the file LICENSE. | ||
*/ | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | ||
|
||
/** | ||
* Validates whether a value is a valid IP address | ||
* | ||
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com> | ||
*/ | ||
class IpValidator extends ConstraintValidator | ||
{ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
public function isValid($value, Constraint $constraint) | ||
{ | ||
if (null === $value || '' === $value) { | ||
return true; | ||
} | ||
|
||
if (!is_scalar($value) && !(is_object($value) && method_exists($value, '__toString()'))) { | ||
throw new UnexpectedTypeException($value, 'string'); | ||
} | ||
|
||
$value = (string)$value; | ||
$valid = false; | ||
|
||
if ($constraint->version == Ip::V4 || $constraint->version == Ip::ALL) { | ||
$valid = $this->isValidV4($value); | ||
} | ||
|
||
if ($constraint->version == Ip::V6 || $constraint->version == Ip::ALL) { | ||
$valid = $valid || $this->isValidV6($value); | ||
} | ||
|
||
if (!$valid) { | ||
$this->setMessage($constraint->message, array('{{ value }}' => $value)); | ||
|
||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Validates that a value is a valid IPv4 address | ||
* | ||
* @param string $value | ||
*/ | ||
protected function isValidV4($value) | ||
{ | ||
if (!preg_match('/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $value, $matches)) { | ||
return false; | ||
} | ||
|
||
for ($i = 1; $i <= 4; ++$i) { | ||
if ($matches[$i] > 255) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Validates that a value is a valid IPv6 address | ||
* | ||
* @param string $value | ||
*/ | ||
protected function isValidV6($value) | ||
{ | ||
if (!preg_match('/^[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){1,5}((:[0-9a-fA-F]{0,4}){1,2}|:([\d\.]+))$/', $value, $matches)) { | ||
return false; | ||
} | ||
|
||
// allow V4 addresses mapped to V6 | ||
if (isset($matches[4]) && !$this->isValidV4($matches[4])) { | ||
return false; | ||
} | ||
|
||
// "::" is only allowed once per address | ||
if (($offset = strpos($value, '::')) !== false) { | ||
if (strpos($value, '::', $offset + 1) !== false) { | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
} |
181 changes: 181 additions & 0 deletions
181
tests/Symfony/Tests/Component/Validator/Constraints/IpValidatorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
<?php | ||
|
||
namespace Symfony\Tests\Component\Validator; | ||
|
||
use Symfony\Component\Validator\Constraints\Ip; | ||
use Symfony\Component\Validator\Constraints\IpValidator; | ||
|
||
class IpValidatorTest extends \PHPUnit_Framework_TestCase | ||
{ | ||
protected $validator; | ||
|
||
protected function setUp() | ||
{ | ||
$this->validator = new IpValidator(); | ||
} | ||
|
||
public function testNullIsValid() | ||
{ | ||
$this->assertTrue($this->validator->isValid(null, new Ip())); | ||
} | ||
|
||
public function testEmptyStringIsValid() | ||
{ | ||
$this->assertTrue($this->validator->isValid('', new Ip())); | ||
} | ||
|
||
public function testExpectsStringCompatibleType() | ||
{ | ||
$this->setExpectedException('Symfony\Component\Validator\Exception\UnexpectedTypeException'); | ||
|
||
$this->validator->isValid(new \stdClass(), new Ip()); | ||
} | ||
|
||
/** | ||
* @dataProvider getValidIpsV4 | ||
*/ | ||
public function testValidIpsV4($date) | ||
{ | ||
$this->assertTrue($this->validator->isValid($date, new Ip(array( | ||
'version' => Ip::V4, | ||
)))); | ||
} | ||
|
||
public function getValidIpsV4() | ||
{ | ||
return array( | ||
array('0.0.0.0'), | ||
array('255.255.255.255'), | ||
array('123.45.67.178'), | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider getValidIpsV6 | ||
*/ | ||
public function testValidIpsV6($date) | ||
{ | ||
$this->assertTrue($this->validator->isValid($date, new Ip(array( | ||
'version' => Ip::V6, | ||
)))); | ||
} | ||
|
||
public function getValidIpsV6() | ||
{ | ||
return array( | ||
array('2001:0db8:85a3:0000:0000:8a2e:0370:7334'), | ||
array('2001:0DB8:85A3:0000:0000:8A2E:0370:7334'), | ||
array('2001:0Db8:85a3:0000:0000:8A2e:0370:7334'), | ||
array('fe80:0000:0000:0000:0202:b3ff:fe1e:8329'), | ||
array('fe80:0:0:0:202:b3ff:fe1e:8329'), | ||
array('fe80::202:b3ff:fe1e:8329'), | ||
array('0:0:0:0:0:0:0:0'), | ||
array('::'), | ||
array('0::'), | ||
array('::0'), | ||
array('0::0'), | ||
// IPv4 mapped to IPv6 | ||
array('2001:0db8:85a3:0000:0000:8a2e:0.0.0.0'), | ||
array('::0.0.0.0'), | ||
array('::255.255.255.255'), | ||
array('::123.45.67.178'), | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider getValidIpsAll | ||
*/ | ||
public function testValidIpsAll($date) | ||
{ | ||
$this->assertTrue($this->validator->isValid($date, new Ip(array( | ||
'version' => Ip::ALL, | ||
)))); | ||
} | ||
|
||
public function getValidIpsAll() | ||
{ | ||
return array_merge($this->getValidIpsV4(), $this->getValidIpsV6()); | ||
} | ||
|
||
/** | ||
* @dataProvider getInvalidIpsV4 | ||
*/ | ||
public function testInvalidIpsV4($date) | ||
{ | ||
$this->assertFalse($this->validator->isValid($date, new Ip(array( | ||
'version' => Ip::V4, | ||
)))); | ||
} | ||
|
||
public function getInvalidIpsV4() | ||
{ | ||
return array( | ||
array('0'), | ||
array('0.0'), | ||
array('0.0.0'), | ||
array('256.0.0.0'), | ||
array('0.256.0.0'), | ||
array('0.0.256.0'), | ||
array('0.0.0.256'), | ||
array('-1.0.0.0'), | ||
array('foobar'), | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider getInvalidIpsV6 | ||
*/ | ||
public function testInvalidIpsV6($date) | ||
{ | ||
$this->assertFalse($this->validator->isValid($date, new Ip(array( | ||
'version' => Ip::V6, | ||
)))); | ||
} | ||
|
||
public function getInvalidIpsV6() | ||
{ | ||
return array( | ||
array('z001:0db8:85a3:0000:0000:8a2e:0370:7334'), | ||
array('fe80'), | ||
array('fe80:8329'), | ||
array('fe80:::202:b3ff:fe1e:8329'), | ||
array('fe80::202:b3ff::fe1e:8329'), | ||
// IPv4 mapped to IPv6 | ||
array('2001:0db8:85a3:0000:0000:8a2e:0370:0.0.0.0'), | ||
array('::0.0'), | ||
array('::0.0.0'), | ||
array('::256.0.0.0'), | ||
array('::0.256.0.0'), | ||
array('::0.0.256.0'), | ||
array('::0.0.0.256'), | ||
); | ||
} | ||
|
||
/** | ||
* @dataProvider getInvalidIpsAll | ||
*/ | ||
public function testInvalidIpsAll($date) | ||
{ | ||
$this->assertFalse($this->validator->isValid($date, new Ip(array( | ||
'version' => Ip::ALL, | ||
)))); | ||
} | ||
|
||
public function getInvalidIpsAll() | ||
{ | ||
return array_merge($this->getInvalidIpsV4(), $this->getInvalidIpsV6()); | ||
} | ||
|
||
public function testMessageIsSet() | ||
{ | ||
$constraint = new Ip(array( | ||
'message' => 'myMessage' | ||
)); | ||
|
||
$this->assertFalse($this->validator->isValid('foobar', $constraint)); | ||
$this->assertEquals($this->validator->getMessageTemplate(), 'myMessage'); | ||
$this->assertEquals($this->validator->getMessageParameters(), array( | ||
'{{ value }}' => 'foobar', | ||
)); | ||
} | ||
} |