Test Failed
Pull Request — master (#34)
by Anatoly
04:34
created

Uri::parseUri()   B

Complexity

Conditions 9
Paths 129

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 17
c 0
b 0
f 0
nc 129
nop 1
dl 0
loc 27
ccs 9
cts 9
cp 1
crap 9
rs 7.8138
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-message
10
 */
11
12
namespace Sunrise\Http\Message;
13
14
use Psr\Http\Message\UriInterface;
15
use Sunrise\Http\Message\Exception\InvalidArgumentException;
16
use Sunrise\Http\Message\Uri\Component\Fragment;
17
use Sunrise\Http\Message\Uri\Component\Host;
18
use Sunrise\Http\Message\Uri\Component\Path;
19
use Sunrise\Http\Message\Uri\Component\Port;
20
use Sunrise\Http\Message\Uri\Component\Query;
21
use Sunrise\Http\Message\Uri\Component\Scheme;
22
use Sunrise\Http\Message\Uri\Component\UserInfo;
23
24
use function is_string;
25
use function ltrim;
26
use function parse_url;
27
use function strncmp;
28
29
class Uri implements UriInterface
30
{
31
    private string $scheme = '';
32
    private string $userInfo = '';
33
    private string $host = '';
34
    private ?int $port = null;
35
    private string $path = '';
36
    private string $query = '';
37
    private string $fragment = '';
38
39
    /**
40
     * @throws InvalidArgumentException
41
     */
42
    public function __construct(string $uri = '')
43
    {
44
        if ($uri === '') {
45
            return;
46
        }
47
48
        $this->parseUri($uri);
49
    }
50
51
    /**
52
     * @param mixed $uri
53
     *
54
     * @throws InvalidArgumentException
55
     */
56
    public static function create($uri): UriInterface
57
    {
58
        if ($uri instanceof UriInterface) {
59
            return $uri;
60
        }
61
62
        if (!is_string($uri)) {
63
            throw new InvalidArgumentException('URI should be a string');
64
        }
65
66
        return new self($uri);
67
    }
68
69
    /**
70
     * @inheritDoc
71
     */
72
    public function withScheme($scheme): UriInterface
73
    {
74
        $clone = clone $this;
75
        $clone->setScheme($scheme);
76
77
        return $clone;
78
    }
79
80
    /**
81
     * @inheritDoc
82
     *
83
     * @throws InvalidArgumentException
84
     */
85
    public function withUserInfo($user, $password = null): UriInterface
86
    {
87
        $clone = clone $this;
88
        $clone->setUserInfo($user, $password);
89
90
        return $clone;
91
    }
92
93
    /**
94
     * @inheritDoc
95
     */
96
    public function withHost($host): UriInterface
97
    {
98
        $clone = clone $this;
99
        $clone->setHost($host);
100
101 619
        return $clone;
102
    }
103 619
104 100
    /**
105
     * @inheritDoc
106
     */
107 523
    public function withPort($port): UriInterface
108 523
    {
109 6
        $clone = clone $this;
110
        $clone->setPort($port);
111
112 517
        return $clone;
113 99
    }
114
115
    /**
116 517
     * @inheritDoc
117 42
     */
118 42
    public function withPath($path): UriInterface
119 42
    {
120 42
        $clone = clone $this;
121
        $clone->setPath($path);
122
123 517
        return $clone;
124 114
    }
125
126
    /**
127 517
     * @inheritDoc
128 46
     */
129
    public function withQuery($query): UriInterface
130
    {
131 517
        $clone = clone $this;
132 515
        $clone->setQuery($query);
133
134
        return $clone;
135 517
    }
136 46
137
    /**
138
     * @inheritDoc
139 517
     *
140 39
     * @throws InvalidArgumentException
141
     */
142
    public function withFragment($fragment): UriInterface
143
    {
144
        $clone = clone $this;
145
        $clone->setFragment($fragment);
146
147
        return $clone;
148
    }
149
150
    /**
151
     * @inheritDoc
152
     */
153
    public function getScheme(): string
154 480
    {
155
        return $this->scheme;
156 480
    }
157 98
158
    /**
159
     * @inheritDoc
160 422
     */
161 1
    public function getUserInfo(): string
162
    {
163
        return $this->userInfo;
164 421
    }
165
166
    /**
167
     * @inheritDoc
168
     */
169
    public function getHost(): string
170
    {
171
        return $this->host;
172
    }
173 19
174
    /**
175 19
     * @inheritDoc
176 19
     */
177
    public function getPort(): ?int
178 5
    {
179
        // The 80 is the default port number for the HTTP protocol.
180
        if ($this->port === 80 && $this->scheme === 'http') {
181
            return null;
182
        }
183
184
        // The 443 is the default port number for the HTTPS protocol.
185
        if ($this->port === 443 && $this->scheme === 'https') {
186
            return null;
187 25
        }
188
189 25
        return $this->port;
190 25
    }
191
192 8
    /**
193
     * @inheritDoc
194
     */
195
    public function getPath(): string
196
    {
197
        // CVE-2015-3257
198
        if (strncmp($this->path, '//', 2) === 0) {
199
            return '/' . ltrim($this->path, '/');
200
        }
201 15
202
        return $this->path;
203 15
    }
204 15
205
    /**
206 6
     * @inheritDoc
207
     */
208
    public function getQuery(): string
209
    {
210
        return $this->query;
211
    }
212
213
    /**
214
     * @inheritDoc
215 15
     */
216
    public function getFragment(): string
217 15
    {
218 15
        return $this->fragment;
219
    }
220 4
221
    /**
222
     * @inheritDoc
223
     */
224
    public function getAuthority(): string
225
    {
226
        // The host is the basic subcomponent.
227
        if ($this->host === '') {
228
            return '';
229 14
        }
230
231 14
        $authority = $this->host;
232 14
        if ($this->userInfo !== '') {
233
            $authority = $this->userInfo . '@' . $authority;
234 5
        }
235
236
        $port = $this->getPort();
237
        if ($port !== null) {
238
            $authority = $authority . ':' . $port;
239
        }
240
241
        return $authority;
242
    }
243 13
244
    /**
245 13
     * @inheritDoc
246 13
     */
247
    public function __toString(): string
248 4
    {
249
        $uri = '';
250
251
        $scheme = $this->scheme;
252
        if ($scheme !== '') {
253
            $uri .= $scheme . ':';
254
        }
255
256
        $authority = $this->getAuthority();
257 13
        if ($authority !== '') {
258
            $uri .= '//' . $authority;
259 13
        }
260 13
261
        $path = $this->path;
262 4
        if ($path !== '') {
263
            // https://github.com/sunrise-php/uri/issues/31
264
            // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
265
            //
266
            // If a URI contains an authority component,
267
            // then the path component must either be empty
268 9
            // or begin with a slash ("/") character.
269
            if ($authority !== '' && strncmp($path, '/', 1) !== 0) {
270 9
                $path = '/' . $path;
271
            }
272
273
            // https://github.com/sunrise-php/uri/issues/31
274
            // https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
275
            //
276 8
            // If a URI does not contain an authority component,
277
            // then the path cannot begin with two slash characters ("//").
278 8
            if ($authority === '' && strncmp($path, '//', 2) === 0) {
279
                $path = '/' . ltrim($path, '/');
280
            }
281
282
            $uri .= $path;
283
        }
284 479
285
        $query = $this->query;
286 479
        if ($query !== '') {
287
            $uri .= '?' . $query;
288
        }
289
290
        $fragment = $this->fragment;
291
        if ($fragment !== '') {
292 77
            $uri .= '#' . $fragment;
293
        }
294
295 77
        return $uri;
296 3
    }
297
298
    /**
299
     * @param mixed $scheme
300 77
     *
301 2
     * @throws InvalidArgumentException
302
     */
303
    final protected function setScheme($scheme): void
304 77
    {
305
        $this->scheme = (new Scheme($scheme))->getValue();
306
    }
307
308
    /**
309
     * @param mixed $user
310 51
     * @param mixed $password
311
     *
312
     * @throws InvalidArgumentException
313 51
     */
314 8
    final protected function setUserInfo($user, $password): void
315
    {
316
        $this->userInfo = (new UserInfo($user, $password))->getValue();
317 43
    }
318
319
    /**
320
     * @param mixed $host
321
     *
322
     * @throws InvalidArgumentException
323 38
     */
324
    final protected function setHost($host): void
325 38
    {
326
        $this->host = (new Host($host))->getValue();
327
    }
328
329
    /**
330
     * @param mixed $port
331 8
     *
332
     * @throws InvalidArgumentException
333 8
     */
334
    final protected function setPort($port): void
335
    {
336
        $this->port = (new Port($port))->getValue();
337
    }
338
339 34
    /**
340
     * @param mixed $path
341
     *
342 34
     * @throws InvalidArgumentException
343 12
     */
344
    final protected function setPath($path): void
345
    {
346 27
        $this->path = (new Path($path))->getValue();
347 27
    }
348 7
349
    /**
350
     * @param mixed $query
351 27
     *
352 27
     * @throws InvalidArgumentException
353 9
     */
354
    final protected function setQuery($query): void
355
    {
356 27
        $this->query = (new Query($query))->getValue();
357
    }
358
359
    /**
360
     * @param mixed $fragment
361
     *
362 32
     * @throws InvalidArgumentException
363
     */
364 32
    final protected function setFragment($fragment): void
365
    {
366 32
        $this->fragment = (new Fragment($fragment))->getValue();
367 32
    }
368 23
369
    /**
370
     * @throws InvalidArgumentException
371 32
     */
372 32
    private function parseUri(string $uri): void
373 25
    {
374
        $components = parse_url($uri);
375
        if ($components === false) {
376 32
            throw new InvalidArgumentException('Invalid URI');
377 32
        }
378
379
        if (isset($components['scheme'])) {
380
            $this->setScheme($components['scheme']);
381
        }
382
        if (isset($components['user'])) {
383
            $this->setUserInfo($components['user'], $components['pass'] ?? null);
384 30
        }
385 1
        if (isset($components['host'])) {
386
            $this->setHost($components['host']);
387
        }
388
        if (isset($components['port'])) {
389
            $this->setPort($components['port']);
390
        }
391
        if (isset($components['path'])) {
392
            $this->setPath($components['path']);
393 30
        }
394 1
        if (isset($components['query'])) {
395
            $this->setQuery($components['query']);
396
        }
397 30
        if (isset($components['fragment'])) {
398
            $this->setFragment($components['fragment']);
399
        }
400 32
    }
401
}
402