Passed
Pull Request — master (#34)
by Anatoly
29:37
created

Uri::parseUri()   B

Complexity

Conditions 9
Paths 129

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

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