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

Url::enableIDN()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
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