Origin::parseElements()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 3
rs 10
1
<?php
2
3
namespace MadWizard\WebAuthn\Web;
4
5
use MadWizard\WebAuthn\Exception\ParseException;
6
use MadWizard\WebAuthn\Format\SerializableTrait;
7
use Serializable;
8
use function mb_strtolower;
9
10
final class Origin implements Serializable
11
{
12
    use SerializableTrait;
13
14
    /**
15
     * @var string
16
     */
17
    private $scheme;
18
19
    /**
20
     * @var string
21
     */
22
    private $host;
23
24
    /**
25
     * @var int
26
     */
27
    private $port;
28
29
    private const PARSE_REGEXP = '#^(?<scheme>[A-Za-z][-._~0-9A-Za-z]*)://(?<host>([^:]+))(:(?<port>[0-9]{1,5}))?$#';
30
31 39
    private function __construct(string $scheme, string $host, int $port)
32
    {
33 39
        $this->scheme = $scheme;
34 39
        $this->host = $host;
35 39
        $this->port = $port;
36 39
    }
37
38
    // TODO: stricter parsing/canonalization according to spec
39 45
    public static function parse(string $origin): Origin
40
    {
41 45
        [$scheme, $host, $port] = self::parseElements($origin);
42
43 42
        if (!self::isValidHost($host)) {
44 2
            throw new ParseException(sprintf("Invalid host name '%s'.", $host));
45
        }
46 40
        if ($port === null) {
47 38
            $port = self::defaultPort($scheme);
48
        }
49
50 39
        if ($port === 0 || $port >= 2 ** 16) {
51
            throw new ParseException(sprintf('Invalid port number %d.', $port));
52
        }
53 39
        return new Origin($scheme, $host, $port);
54
    }
55
56 38
    private static function defaultPort(string $scheme): int
57
    {
58 38
        if ($scheme === 'https') {
59 27
            return 443;
60
        }
61 22
        if ($scheme === 'http') {
62 21
            return 80;
63
        }
64 1
        throw new ParseException(sprintf("No default port number for scheme '%s'.", $scheme));
65
    }
66
67 42
    private static function isValidHost(string $host): bool
68
    {
69 42
        if ($host === '') {
70
            return false;
71
        }
72
73 42
        if (filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
74 1
            return true;
75
        }
76
77
        // TODO ipv6 - needs adjustment in regexp
78
//      if ($host[0] === '[' && $host[-1] === ']' && filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
79
//          return true;
80
//      }
81 41
        if (filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
82 39
            return true;
83
        }
84 2
        return false;
85
    }
86
87 45
    private static function parseElements(string $origin): array
88
    {
89 45
        if (!preg_match(self::PARSE_REGEXP, $origin, $matches)) {
90 4
            throw new ParseException(sprintf("Could not parse origin '%s'.", $origin));
91
        }
92 42
        $scheme = strtolower($matches['scheme']);
93 42
        $host = mb_strtolower($matches['host'], 'UTF-8');
94
95 42
        $port = isset($matches['port']) ? (int) $matches['port'] : null;
96 42
        return [$scheme, $host, $port];
97
    }
98
99 9
    public function equals(Origin $origin): bool
100
    {
101 9
        return $this->host === $origin->host &&
102 9
            $this->port === $origin->port &&
103 9
            $this->scheme === $origin->scheme;
104
    }
105
106 3
    public function toString(): string
107
    {
108 3
        if ($this->usesDefaultPort()) {
109 2
            return sprintf('%s://%s', $this->scheme, $this->host);
110
        }
111 1
        return sprintf('%s://%s:%d', $this->scheme, $this->host, $this->port);
112
    }
113
114 3
    private function usesDefaultPort(): bool
115
    {
116 3
        if ($this->scheme === 'http' && $this->port === 80) {
117 1
            return true;
118
        }
119 2
        if ($this->scheme === 'https' && $this->port === 443) {
120 1
            return true;
121
        }
122 1
        return false;
123
    }
124
125 10
    public function getHost(): string
126
    {
127 10
        return $this->host;
128
    }
129
130 3
    public function getScheme(): string
131
    {
132 3
        return $this->scheme;
133
    }
134
135 3
    public function getPort(): int
136
    {
137 3
        return $this->port;
138
    }
139
140 1
    public function __serialize(): array
141
    {
142
        return [
143 1
            'scheme' => $this->scheme,
144 1
            'host' => $this->host,
145 1
            'port' => $this->port,
146
        ];
147
    }
148
149 1
    public function __unserialize(array $data): void
150
    {
151 1
        $this->scheme = $data['scheme'];
152 1
        $this->host = $data['host'];
153 1
        $this->port = $data['port'];
154 1
    }
155
}
156