Completed
Pull Request — master (#30)
by ignace nyamagana
16:17 queued 10s
created

UriParser::__invoke()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * League.Url (http://url.thephpleague.com)
4
 *
5
 * @package   League.url
6
 * @author    Ignace Nyamagana Butera <[email protected]>
7
 * @copyright 2013-2015 Ignace Nyamagana Butera
8
 * @license   https://github.com/thephpleague/uri/blob/master/LICENSE (MIT License)
9
 * @version   4.0.0
10
 * @link      https://github.com/thephpleague/uri/
11
 */
12
namespace League\Uri;
13
14
use InvalidArgumentException;
15
use League\Uri\Components\HostIpTrait;
16
use League\Uri\Components\HostnameTrait;
17
use League\Uri\Schemes\Generic\PathFormatterTrait;
18
use League\Uri\Types\ValidatorTrait;
19
20
/**
21
 * a class to parse a URI string according to RFC3986
22
 *
23
 * @package League.uri
24
 * @author  Ignace Nyamagana Butera <[email protected]>
25
 * @since   4.0.0
26
 */
27
class UriParser
28
{
29
    use HostIpTrait;
30
31
    use HostnameTrait;
32
33
    use PathFormatterTrait;
34
35
    use ValidatorTrait;
36
37
    const REGEXP_URI = ',^((?<scheme>[^:/?\#]+):)?
38
        (?<authority>//([^/?\#]*))?
39
        (?<path>[^?\#]*)
40
        (?<query>\?([^\#]*))?
41
        (?<fragment>\#(.*))?,x';
42
43
    const REGEXP_AUTHORITY = ',^(?<userinfo>(?<ucontent>.*?)@)?(?<hostname>.*?)?$,';
44
45
    const REGEXP_REVERSE_HOSTNAME = ',^((?<port>[^(\[\])]*):)?(?<host>.*)?$,';
46
47
    const REGEXP_SCHEME = ',^([a-z]([-a-z0-9+.]+)?)?$,i';
48
49
    const REGEXP_INVALID_USER = ',[/?#@:],';
50
51
    const REGEXP_INVALID_PASS = ',[/?#@],';
52
53
    /**
54
     * default components hash table
55
     *
56
     * @var array
57
     */
58
    protected $components = [
59
        'scheme' => null, 'user' => null, 'pass' => null, 'host' => null,
60
        'port' => null, 'path' => null, 'query' => null, 'fragment' => null,
61
    ];
62
63
    /**
64
     * Parse a string as an URI according to the regexp form rfc3986
65
     *
66
     * @param string $uri The URI to parse
67
     *
68
     * @return array the array is similar to PHP's parse_url hash response
69
     */
70 440
    public function parse($uri)
71
    {
72 440
        $parts = $this->extractUriParts($uri);
73
74 440
        return $this->normalizeUriHash(array_merge(
75 440
            $this->parseAuthority($parts['authority']),
76
            [
77 432
                'scheme' => empty($parts['scheme']) ? null : $parts['scheme'],
78 432
                'path' => $parts['path'],
79 432
                'query' => empty($parts['query']) ? null : mb_substr($parts['query'], 1, null, 'UTF-8'),
80 432
                'fragment' => empty($parts['fragment']) ? null : mb_substr($parts['fragment'], 1, null, 'UTF-8'),
81
            ]
82 432
        ));
83
    }
84
85
    /**
86
     * Parse a string as an URI according to the regexp form rfc3986
87
     *
88
     * @param string $uri The URI to parse
89
     *
90
     * @return array the array is similar to PHP's parse_url hash response
91
     */
92
    public function __invoke($uri)
93
    {
94 440
        return $this->parse($uri);
95
    }
96 440
97 440
    /**
98
     * Extract URI parts
99 440
     *
100 436
     * @see http://tools.ietf.org/html/rfc3986#appendix-B
101
     *
102
     * @param string $uri The URI to split
103 4
     *
104 4
     * @return string[]
105 4
     */
106
    protected function extractUriParts($uri)
107 4
    {
108
        preg_match(self::REGEXP_URI, $uri, $parts);
109
        $parts += ['query' => '', 'fragment' => ''];
110
111
        if (preg_match(self::REGEXP_SCHEME, $parts['scheme'])) {
112
            return $parts;
113
        }
114
115
        $parts['path'] = $parts['scheme'].':'.$parts['authority'].$parts['path'];
116
        $parts['scheme'] = '';
117
        $parts['authority'] = '';
118 434
119
        return $parts;
120 434
    }
121
122
    /**
123
     * Normalize URI components hash
124
     *
125
     * @param array $components a hash representation of the URI components
126
     *                          similar to PHP parse_url function result
127
     *
128
     * @return array
129
     */
130 440
    public function normalizeUriHash(array $components)
131
    {
132 440
        return array_replace($this->components, $components);
133 440
    }
134 154
135
    /**
136
     * Parse a URI authority part into its components
137 352
     *
138 352
     * @param string $authority
139 2
     *
140
     * @return array
141
     */
142 350
    protected function parseAuthority($authority)
143 350
    {
144 108
        $res = ['user' => null, 'pass' => null, 'host' => null, 'port' => null];
145 108
        if (empty($authority)) {
146 108
            return $res;
147
        }
148 350
149
        $content = mb_substr($authority, 2, null, 'UTF-8');
150
        if (empty($content)) {
151
            return ['host' => ''] + $res;
152
        }
153
154
        preg_match(self::REGEXP_AUTHORITY, $content, $auth);
155
        if (!empty($auth['userinfo'])) {
156
            $userinfo = explode(':', $auth['ucontent'], 2);
157
            $res = ['user' => array_shift($userinfo), 'pass' => array_shift($userinfo)] + $res;
158
        }
159
160 350
        return $this->parseHostname($auth['hostname']) + $res;
161
    }
162 350
163 350
    /**
164 350
     * Parse the hostname into its components Host and Port
165 350
     *
166 350
     * No validation is done on the port or host component found
167 350
     *
168 350
     * @param string $hostname
169 344
     *
170
     * @return array
171 342
     */
172
    protected function parseHostname($hostname)
173
    {
174
        $components = ['host' => null, 'port' => null];
175
        $hostname = strrev($hostname);
176
        if (preg_match(self::REGEXP_REVERSE_HOSTNAME, $hostname, $res)) {
177
            $components['host'] = strrev($res['host']);
178
            $components['port'] = strrev($res['port']);
179
        }
180
        $components['host'] = $this->filterHost($components['host']);
181 350
        $components['port'] = $this->validatePort($components['port']);
182
183 350
        return $components;
184 340
    }
185 334
186
    /**
187 344
     * validate the host component
188
     *
189
     * @param string $host
190
     *
191
     * @return int|null
192
     */
193 340
    protected function filterHost($host)
194
    {
195 340
        if (empty($this->validateIpHost($host))) {
196
            $this->validateStringHost($host);
197
        }
198
199
        return $host;
200
    }
201 340
202
    /**
203 340
     * @inheritdoc
204 2
     */
205
    protected function setIsAbsolute($host)
206 338
    {
207
        return ('.' == mb_substr($host, -1, 1, 'UTF-8')) ? mb_substr($host, 0, -1, 'UTF-8') : $host;
208
    }
209
210
    /**
211
     * @inheritdoc
212
     */
213
    protected function assertLabelsCount(array $labels)
214
    {
215
        if (127 <= count($labels)) {
216 46
            throw new InvalidArgumentException('Invalid Host, verify labels count');
217
        }
218 46
    }
219 44
220 4
    /**
221
     * Format the user info
222 40
     *
223 38
     * @param string $user
224 36
     * @param string $pass
225 36
     *
226 38
     * @return string
227
     */
228
    public function buildUserInfo($user, $pass)
229
    {
230
        $userinfo = $this->filterUser($user);
231
        if (null === $userinfo) {
232
            return '';
233
        }
234
        $pass = $this->filterPass($pass);
235
        if (null !== $pass) {
236
            $userinfo .= ':'.$pass;
237
        }
238 46
        return $userinfo.'@';
239
    }
240 46
241 44
    /**
242
     * Filter and format the user for URI string representation
243
     *
244 2
     * @param null|string $user
245
     *
246
     * @throws InvalidArgumentException If the user is invalid
247
     *
248
     * @return null|string
249
     */
250
    protected function filterUser($user)
251
    {
252
        if (!preg_match(self::REGEXP_INVALID_USER, $user)) {
253
            return $user;
254
        }
255
256 40
        throw new InvalidArgumentException('The user component contains invalid characters');
257
    }
258 40
259 38
    /**
260
     * Filter and format the pass for URI string representation
261
     *
262 2
     * @param null|string $pass
263
     *
264
     * @throws InvalidArgumentException If the pass is invalid
265
     *
266
     * @return null|string
267
     */
268
    protected function filterPass($pass)
269
    {
270
        if (!preg_match(self::REGEXP_INVALID_PASS, $pass)) {
271
            return $pass;
272
        }
273
274
        throw new InvalidArgumentException('The user component contains invalid characters');
275
    }
276
}
277