diff --git a/src/Symfony/Component/Validator/Constraints/Url.php b/src/Symfony/Component/Validator/Constraints/Url.php index 3306c1b405561..3c36d72f76492 100644 --- a/src/Symfony/Component/Validator/Constraints/Url.php +++ b/src/Symfony/Component/Validator/Constraints/Url.php @@ -104,6 +104,7 @@ class Url extends Constraint * @deprecated since Symfony 4.1, to be removed in 5.0 */ public $checkDNS = self::CHECK_DNS_TYPE_NONE; + public $relativeProtocol = false; public function __construct($options = null) { diff --git a/src/Symfony/Component/Validator/Constraints/UrlValidator.php b/src/Symfony/Component/Validator/Constraints/UrlValidator.php index 932aa14436fbc..8490d5c10e74f 100644 --- a/src/Symfony/Component/Validator/Constraints/UrlValidator.php +++ b/src/Symfony/Component/Validator/Constraints/UrlValidator.php @@ -61,7 +61,8 @@ public function validate($value, Constraint $constraint) return; } - $pattern = sprintf(static::PATTERN, implode('|', $constraint->protocols)); + $pattern = $constraint->relativeProtocol ? str_replace('(%s):', '(?:(%s):)?', static::PATTERN) : static::PATTERN; + $pattern = sprintf($pattern, implode('|', $constraint->protocols)); if (!preg_match($pattern, $value)) { $this->context->buildViolation($constraint->message) diff --git a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php index b7c1dc1d776a7..87b3f0d10ef70 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/UrlValidatorTest.php @@ -65,6 +65,30 @@ public function testValidUrls($url) $this->assertNoViolation(); } + /** + * @dataProvider getValidRelativeUrls + * @dataProvider getValidUrls + */ + public function testValidRelativeUrl($url) + { + $constraint = new Url(array( + 'relativeProtocol' => true, + )); + + $this->validator->validate($url, $constraint); + + $this->assertNoViolation(); + } + + public function getValidRelativeUrls() + { + return array( + array('//google.com'), + array('//symfony.fake/blog/'), + array('//symfony.com/search?type=&q=url+validator'), + ); + } + public function getValidUrls() { return array( @@ -147,6 +171,46 @@ public function testInvalidUrls($url) ->assertRaised(); } + /** + * @dataProvider getInvalidRelativeUrls + * @dataProvider getInvalidUrls + */ + public function testInvalidRelativeUrl($url) + { + $constraint = new Url(array( + 'message' => 'myMessage', + 'relativeProtocol' => true, + )); + + $this->validator->validate($url, $constraint); + + $this->buildViolation('myMessage') + ->setParameter('{{ value }}', '"'.$url.'"') + ->setCode(Url::INVALID_URL_ERROR) + ->assertRaised(); + } + + public function getInvalidRelativeUrls() + { + return array( + array('/google.com'), + array('//goog_le.com'), + array('//google.com::aa'), + array('//google.com:aa'), + array('//127.0.0.1:aa/'), + array('//[::1'), + array('//hello.☎/'), + array('//:password@symfony.com'), + array('//:password@@symfony.com'), + array('//username:passwordsymfony.com'), + array('//usern@me:password@symfony.com'), + array('//example.com/exploit.html?'), + array('//example.com/exploit.html?hel lo'), + array('//example.com/exploit.html?not_a%hex'), + array('//'), + ); + } + public function getInvalidUrls() { return array(