Completed
Push — master ( 2e8a37...e129ff )
by Tobias
04:00
created

Uri::getUserInfo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Psr\Http\Message\UriInterface;
8
9
/**
10
 * PSR-7 URI implementation.
11
 *
12
 * @author Michael Dowling
13
 * @author Tobias Schultze
14
 * @author Matthew Weier O'Phinney
15
 * @author Tobias Nyholm <[email protected]>
16
 */
17
class Uri implements UriInterface
18
{
19
    private static $schemes = [
20
        'http' => 80,
21
        'https' => 443,
22
    ];
23
24
    private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
25
    private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
26
27
    /** @var string Uri scheme. */
28
    private $scheme = '';
29
30
    /** @var string Uri user info. */
31
    private $userInfo = '';
32
33
    /** @var string Uri host. */
34
    private $host = '';
35
36
    /** @var int|null Uri port. */
37
    private $port;
38
39
    /** @var string Uri path. */
40
    private $path = '';
41
42
    /** @var string Uri query string. */
43
    private $query = '';
44
45
    /** @var string Uri fragment. */
46
    private $fragment = '';
47
48
    /**
49
     * @param string $uri
50
     */
51 108
    public function __construct($uri = '')
52
    {
53 108
        if ($uri != '') {
54 82
            $parts = parse_url($uri);
55 82
            if ($parts === false) {
56 4
                throw new \InvalidArgumentException("Unable to parse URI: $uri");
57
            }
58
59 78
            $this->applyParts($parts);
60
        }
61 104
    }
62
63 63
    public function __toString(): string
64
    {
65 63
        return self::createUriString(
66 63
            $this->scheme,
67 63
            $this->getAuthority(),
68 63
            $this->path,
69 63
            $this->query,
70 63
            $this->fragment
71
        );
72
    }
73
74 15
    public function getScheme(): string
75
    {
76 15
        return $this->scheme;
77
    }
78
79 69
    public function getAuthority(): string
80
    {
81 69
        if ($this->host == '') {
82 29
            return '';
83
        }
84
85 42
        $authority = $this->host;
86 42
        if ($this->userInfo != '') {
87 5
            $authority = $this->userInfo.'@'.$authority;
88
        }
89
90 42
        if ($this->port !== null) {
91 5
            $authority .= ':'.$this->port;
92
        }
93
94 42
        return $authority;
95
    }
96
97 7
    public function getUserInfo(): string
98
    {
99 7
        return $this->userInfo;
100
    }
101
102 54
    public function getHost(): string
103
    {
104 54
        return $this->host;
105
    }
106
107 42
    public function getPort()
108
    {
109 42
        return $this->port;
110
    }
111
112 17
    public function getPath(): string
113
    {
114 17
        return $this->path;
115
    }
116
117 14
    public function getQuery(): string
118
    {
119 14
        return $this->query;
120
    }
121
122 13
    public function getFragment(): string
123
    {
124 13
        return $this->fragment;
125
    }
126
127 14
    public function withScheme($scheme): self
128
    {
129 14
        $scheme = $this->filterScheme($scheme);
130
131 13
        if ($this->scheme === $scheme) {
132
            return $this;
133
        }
134
135 13
        $new = clone $this;
136 13
        $new->scheme = $scheme;
137 13
        $new->port = $new->filterPort($new->port);
138
139 13
        return $new;
140
    }
141
142 4
    public function withUserInfo($user, $password = null): self
143
    {
144 4
        $info = $user;
145 4
        if ($password != '') {
146 4
            $info .= ':'.$password;
147
        }
148
149 4
        if ($this->userInfo === $info) {
150
            return $this;
151
        }
152
153 4
        $new = clone $this;
154 4
        $new->userInfo = $info;
155
156 4
        return $new;
157
    }
158
159 13
    public function withHost($host): self
160
    {
161 13
        $host = $this->filterHost($host);
162
163 12
        if ($this->host === $host) {
164
            return $this;
165
        }
166
167 12
        $new = clone $this;
168 12
        $new->host = $host;
169
170 12
        return $new;
171
    }
172
173 8
    public function withPort($port): self
174
    {
175 8
        $port = $this->filterPort($port);
176
177 6
        if ($this->port === $port) {
178 1
            return $this;
179
        }
180
181 5
        $new = clone $this;
182 5
        $new->port = $port;
183
184 5
        return $new;
185
    }
186
187 15
    public function withPath($path): self
188
    {
189 15
        $path = $this->filterPath($path);
190
191 14
        if ($this->path === $path) {
192
            return $this;
193
        }
194
195 14
        $new = clone $this;
196 14
        $new->path = $path;
197
198 14
        return $new;
199
    }
200
201 12
    public function withQuery($query): self
202
    {
203 12
        $query = $this->filterQueryAndFragment($query);
204
205 11
        if ($this->query === $query) {
206
            return $this;
207
        }
208
209 11
        $new = clone $this;
210 11
        $new->query = $query;
211
212 11
        return $new;
213
    }
214
215 5
    public function withFragment($fragment): self
216
    {
217 5
        $fragment = $this->filterQueryAndFragment($fragment);
218
219 4
        if ($this->fragment === $fragment) {
220
            return $this;
221
        }
222
223 4
        $new = clone $this;
224 4
        $new->fragment = $fragment;
225
226 4
        return $new;
227
    }
228
229
    /**
230
     * Apply parse_url parts to a URI.
231
     *
232
     * @param array $parts Array of parse_url parts to apply
233
     */
234 78
    private function applyParts(array $parts)
235
    {
236 78
        $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
237 78
        $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
238 78
        $this->host = isset($parts['host']) ? $this->filterHost($parts['host']) : '';
239 78
        $this->port = isset($parts['port']) ? $this->filterPort($parts['port']) : null;
240 78
        $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
241 78
        $this->query = isset($parts['query']) ? $this->filterQueryAndFragment($parts['query']) : '';
242 78
        $this->fragment = isset($parts['fragment']) ? $this->filterQueryAndFragment($parts['fragment']) : '';
243 78
        if (isset($parts['pass'])) {
244 4
            $this->userInfo .= ':'.$parts['pass'];
245
        }
246 78
    }
247
248
    /**
249
     * Create a URI string from its various parts.
250
     *
251
     * @param string $scheme
252
     * @param string $authority
253
     * @param string $path
254
     * @param string $query
255
     * @param string $fragment
256
     *
257
     * @return string
258
     */
259 63
    private static function createUriString($scheme, $authority, $path, $query, $fragment): string
260
    {
261 63
        $uri = '';
262
263 63
        if ($scheme != '') {
264 38
            $uri .= $scheme.':';
265
        }
266
267 63
        if ($authority != '') {
268 38
            $uri .= '//'.$authority;
269
        }
270
271 63
        if ($path != '') {
272 51
            if ($path[0] !== '/') {
273 7
                if ($authority != '') {
274
                    // If the path is rootless and an authority is present, the path MUST be prefixed by "/"
275 7
                    $path = '/'.$path;
276
                }
277 44
            } elseif (isset($path[1]) && $path[1] === '/') {
278 1
                if ($authority == '') {
279
                    // If the path is starting with more than one "/" and no authority is present, the
280
                    // starting slashes MUST be reduced to one.
281 1
                    $path = '/'.ltrim($path, '/');
282
                }
283
            }
284
285 51
            $uri .= $path;
286
        }
287
288 63
        if ($query != '') {
289 33
            $uri .= '?'.$query;
290
        }
291
292 63
        if ($fragment != '') {
293 14
            $uri .= '#'.$fragment;
294
        }
295
296 63
        return $uri;
297
    }
298
299
    /**
300
     * Is a given port non-standard for the current scheme?
301
     *
302
     * @param string $scheme
303
     * @param int    $port
304
     *
305
     * @return bool
306
     */
307 11
    private static function isNonStandardPort($scheme, $port): bool
308
    {
309 11
        return !isset(self::$schemes[$scheme]) || $port !== self::$schemes[$scheme];
310
    }
311
312
    /**
313
     * @param string $scheme
314
     *
315
     * @throws \InvalidArgumentException If the scheme is invalid
316
     *
317
     * @return string
318
     */
319 55
    private function filterScheme($scheme): string
320
    {
321 55
        if (!is_string($scheme)) {
322 1
            throw new \InvalidArgumentException('Scheme must be a string');
323
        }
324
325 54
        return strtolower($scheme);
326
    }
327
328
    /**
329
     * @param string $host
330
     *
331
     * @throws \InvalidArgumentException If the host is invalid
332
     *
333
     * @return string
334
     */
335 57
    private function filterHost($host): string
336
    {
337 57
        if (!is_string($host)) {
338 1
            throw new \InvalidArgumentException('Host must be a string');
339
        }
340
341 56
        return strtolower($host);
342
    }
343
344
    /**
345
     * @param int|null $port
346
     *
347
     * @throws \InvalidArgumentException If the port is invalid
348
     *
349
     * @return int|null
350
     */
351 23
    private function filterPort($port)
352
    {
353 23
        if ($port === null) {
354 13
            return;
355
        }
356
357 13
        $port = (int) $port;
358 13
        if (1 > $port || 0xffff < $port) {
359 2
            throw new \InvalidArgumentException(sprintf('Invalid port: %d. Must be between 1 and 65535', $port));
360
        }
361
362 11
        return self::isNonStandardPort($this->scheme, $port) ? $port : null;
363
    }
364
365
    /**
366
     * Filters the path of a URI.
367
     *
368
     * @param string $path
369
     *
370
     * @throws \InvalidArgumentException If the path is invalid
371
     *
372
     * @return string
373
     */
374 80 View Code Duplication
    private function filterPath($path): string
375
    {
376 80
        if (!is_string($path)) {
377 1
            throw new \InvalidArgumentException('Path must be a string');
378
        }
379
380 79
        return preg_replace_callback(
381 79
            '/(?:[^'.self::$charUnreserved.self::$charSubDelims.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
382 79
            [$this, 'rawurlencodeMatchZero'],
383 79
            $path
384
        );
385
    }
386
387
    /**
388
     * Filters the query string or fragment of a URI.
389
     *
390
     * @param string $str
391
     *
392
     * @throws \InvalidArgumentException If the query or fragment is invalid
393
     *
394
     * @return string
395
     */
396 39 View Code Duplication
    private function filterQueryAndFragment($str): string
397
    {
398 39
        if (!is_string($str)) {
399 2
            throw new \InvalidArgumentException('Query and fragment must be a string');
400
        }
401
402 37
        return preg_replace_callback(
403 37
            '/(?:[^'.self::$charUnreserved.self::$charSubDelims.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
404 37
            [$this, 'rawurlencodeMatchZero'],
405 37
            $str
406
        );
407
    }
408
409 6
    private function rawurlencodeMatchZero(array $match): string
410
    {
411 6
        return rawurlencode($match[0]);
412
    }
413
}
414