Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Validator\Constraints; Chris@0: Chris@0: use Symfony\Component\Validator\Constraint; Chris@0: use Symfony\Component\Validator\ConstraintValidator; Chris@14: use Symfony\Component\Validator\Exception\InvalidOptionsException; Chris@0: use Symfony\Component\Validator\Exception\UnexpectedTypeException; Chris@0: Chris@0: /** Chris@0: * @author Bernhard Schussek Chris@0: */ Chris@0: class UrlValidator extends ConstraintValidator Chris@0: { Chris@0: const PATTERN = '~^ Chris@0: (%s):// # protocol Chris@14: (([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth Chris@0: ( Chris@14: ([\pL\pN\pS\-\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name Chris@0: | # or Chris@0: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address Chris@0: | # or Chris@0: \[ Chris@0: (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) Chris@0: \] # an IPv6 address Chris@0: ) Chris@0: (:[0-9]+)? # a port (optional) Chris@0: (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path Chris@0: (?:\? (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) Chris@0: (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) Chris@0: $~ixu'; Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function validate($value, Constraint $constraint) Chris@0: { Chris@0: if (!$constraint instanceof Url) { Chris@0: throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Url'); Chris@0: } Chris@0: Chris@12: if (null === $value || '' === $value) { Chris@0: return; Chris@0: } Chris@0: Chris@17: if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { Chris@0: throw new UnexpectedTypeException($value, 'string'); Chris@0: } Chris@0: Chris@0: $value = (string) $value; Chris@0: if ('' === $value) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $pattern = sprintf(static::PATTERN, implode('|', $constraint->protocols)); Chris@0: Chris@0: if (!preg_match($pattern, $value)) { Chris@0: $this->context->buildViolation($constraint->message) Chris@0: ->setParameter('{{ value }}', $this->formatValue($value)) Chris@0: ->setCode(Url::INVALID_URL_ERROR) Chris@0: ->addViolation(); Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: if ($constraint->checkDNS) { Chris@14: // backwards compatibility Chris@14: if (true === $constraint->checkDNS) { Chris@14: $constraint->checkDNS = Url::CHECK_DNS_TYPE_ANY; Chris@14: @trigger_error(sprintf('Use of the boolean TRUE for the "checkDNS" option in %s is deprecated. Use Url::CHECK_DNS_TYPE_ANY instead.', Url::class), E_USER_DEPRECATED); Chris@14: } Chris@14: Chris@17: if (!\in_array($constraint->checkDNS, [ Chris@14: Url::CHECK_DNS_TYPE_ANY, Chris@14: Url::CHECK_DNS_TYPE_A, Chris@14: Url::CHECK_DNS_TYPE_A6, Chris@14: Url::CHECK_DNS_TYPE_AAAA, Chris@14: Url::CHECK_DNS_TYPE_CNAME, Chris@14: Url::CHECK_DNS_TYPE_MX, Chris@14: Url::CHECK_DNS_TYPE_NAPTR, Chris@14: Url::CHECK_DNS_TYPE_NS, Chris@14: Url::CHECK_DNS_TYPE_PTR, Chris@14: Url::CHECK_DNS_TYPE_SOA, Chris@14: Url::CHECK_DNS_TYPE_SRV, Chris@14: Url::CHECK_DNS_TYPE_TXT, Chris@17: ], true)) { Chris@17: throw new InvalidOptionsException(sprintf('Invalid value for option "checkDNS" in constraint %s', \get_class($constraint)), ['checkDNS']); Chris@14: } Chris@14: Chris@0: $host = parse_url($value, PHP_URL_HOST); Chris@0: Chris@17: if (!\is_string($host) || !checkdnsrr($host, $constraint->checkDNS)) { Chris@0: $this->context->buildViolation($constraint->dnsMessage) Chris@0: ->setParameter('{{ value }}', $this->formatValue($host)) Chris@0: ->setCode(Url::INVALID_URL_ERROR) Chris@0: ->addViolation(); Chris@0: } Chris@0: } Chris@0: } Chris@0: }