Test Failed
Pull Request — master (#175)
by
unknown
12:57
created

Url   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 27
c 4
b 0
f 0
dl 0
loc 94
ccs 31
cts 31
cp 1
rs 10
wmc 15

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 33 3
A idnToAscii() 0 5 2
A convertIdn() 0 10 2
A getOptions() 0 7 1
A getPattern() 0 7 2
A validateValue() 0 18 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Validator\Rule;
6
7
use RuntimeException;
8
use Yiisoft\Validator\FormatterInterface;
9
use Yiisoft\Validator\Result;
10
use Yiisoft\Validator\Rule;
11
use Yiisoft\Validator\ValidationContext;
12
13
use function is_string;
14
use function strlen;
15
16
/**
17
 * UrlValidator validates that the attribute value is a valid HTTP or HTTPS URL.
18
 *
19
 * Note that this validator only checks if the URL scheme and host part are correct.
20
 * It does not check the remaining parts of a URL.
21
 */
22
final class Url extends Rule
23
{
24
    public function __construct(
25
        /**
26
         * @var string the regular expression used to validate a value.
27
         * The pattern may contain a `{schemes}` token that will be replaced
28
         * by a regular expression which represents the {@see $schemes}.
29
         *
30
         * Note that if you want to reuse the pattern in HTML5 input it should have ^ and $, should not have any
31
         * modifiers and should not be case-insensitive.
32
         */
33
        private string $pattern = '/^{schemes}:\/\/(([a-zA-Z0-9][a-zA-Z0-9_-]*)(\.[a-zA-Z0-9][a-zA-Z0-9_-]*)+)(?::\d{1,5})?([?\/#].*$|$)/',
34
        /**
35
         * @var array list of URI schemes which should be considered valid. By default, http and https
36
         * are considered to be valid schemes.
37
         */
38
        private array $validSchemes = ['http', 'https'],
39
        /**
40
         * @var bool whether validation process should take into account IDN (internationalized
41
         * domain names). Defaults to false meaning that validation of URLs containing IDN will always
42
         * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
43
         * extension, otherwise an exception would be thrown.
44
         */
45
        private bool $enableIDN = false,
46
        private string $message = 'This value is not a valid URL.',
47
48
        ?FormatterInterface $formatter = null,
49 15
        bool $skipOnEmpty = false,
50
        bool $skipOnError = false,
51 15
        $when = null
52
    ) {
53
        parent::__construct(formatter: $formatter, skipOnEmpty: $skipOnEmpty, skipOnError: $skipOnError, when: $when);
54 11
55
        if ($enableIDN && !function_exists('idn_to_ascii')) {
56 11
            throw new RuntimeException('In order to use IDN validation intl extension must be installed and enabled.');
57
        }
58
    }
59 11
60 10
    protected function validateValue($value, ?ValidationContext $context = null): Result
61 6
    {
62
        $result = new Result();
63
64 10
        // make sure the length is limited to avoid DOS attacks
65 7
        if (is_string($value) && strlen($value) < 2000) {
66
            if ($this->enableIDN) {
67
                $value = $this->convertIdn($value);
68
            }
69 7
70
            if (preg_match($this->getPattern(), $value)) {
71 7
                return $result;
72
            }
73
        }
74 6
75
        $result->addError($this->formatMessage($this->message));
76 6
77
        return $result;
78 6
    }
79
80
    private function idnToAscii(string $idn): string
81 6
    {
82
        $result = idn_to_ascii($idn, 0, INTL_IDNA_VARIANT_UTS46);
83 6
84 4
        return $result === false ? '' : $result;
85
    }
86
87 2
    private function convertIdn(string $value): string
88 2
    {
89 2
        if (strpos($value, '://') === false) {
90
            return $this->idnToAscii($value);
91
        }
92
93
        return preg_replace_callback(
94 10
            '/:\/\/([^\/]+)/',
95
            fn ($matches) => '://' . $this->idnToAscii($matches[1]),
96 10
            $value
97 8
        );
98
    }
99
100 2
    private function getPattern(): string
101
    {
102
        if (strpos($this->pattern, '{schemes}') !== false) {
103 2
            return str_replace('{schemes}', '((?i)' . implode('|', $this->validSchemes) . ')', $this->pattern);
104
        }
105 2
106 2
        return $this->pattern;
107 2
    }
108
109
    public function getOptions(): array
110 9
    {
111
        return array_merge(parent::getOptions(), [
112 9
            'pattern' => $this->pattern,
113 1
            'validSchemes' => $this->validSchemes,
114
            'enableIDN' => $this->enableIDN,
115
            'message' => $this->formatMessage($this->message),
116 8
        ]);
117 8
    }
118
}
119