Completed
Push — master ( 611069...1d6d55 )
by ignace nyamagana
04:05
created

UriParser::parse()   A

Complexity

Conditions 4
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 11
Bugs 1 Features 0
Metric Value
c 11
b 1
f 0
dl 0
loc 14
ccs 9
cts 9
cp 1
rs 9.2
cc 4
eloc 8
nc 1
nop 1
crap 4
1
<?php
2
/**
3
 * League.Uri (http://uri.thephpleague.com)
4
 *
5
 * @package   League.uri
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.2.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\UriBuilderTrait;
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 UriBuilderTrait;
34
35
    use ValidatorTrait;
36
37
    const REGEXP_URI = ',^
38
        ((?<scheme>[^:/?\#]+):)?      # URI scheme component
39
        (?<authority>//([^/?\#]*))?   # URI authority part
40
        (?<path>[^?\#]*)              # URI path component
41
        (?<query>\?([^\#]*))?         # URI query component
42
        (?<fragment>\#(.*))?          # URI fragment component
43
    ,x';
44
45
    const REGEXP_AUTHORITY = ',^(?<userinfo>(?<ucontent>.*?)@)?(?<hostname>.*?)?$,';
46
47
    const REGEXP_REVERSE_HOSTNAME = ',^((?<port>[^(\[\])]*):)?(?<host>.*)?$,';
48
49
    const REGEXP_SCHEME = ',^([a-z]([-a-z0-9+.]+)?)?$,i';
50
51
    const REGEXP_INVALID_USER = ',[/?#@:],';
52
53
    const REGEXP_INVALID_PASS = ',[/?#@],';
54
55
56
    /**
57
     * Parse a string as an URI according to the regexp form rfc3986
58
     *
59
     * @param string $uri
60
     *
61
     * @return array
62
     */
63 692
    public function parse($uri)
64
    {
65 692
        $parts = $this->extractUriParts($uri);
66
67 692
        return $this->normalizeUriHash(array_merge(
68 692
            $this->parseAuthority($parts['authority']),
69
            [
70 680
                'scheme' => '' === $parts['scheme'] ? null : $parts['scheme'],
71 680
                'path' => $parts['path'],
72 680
                'query' => '' === $parts['query'] ? null : mb_substr($parts['query'], 1, null, 'UTF-8'),
73 680
                'fragment' => '' === $parts['fragment'] ? null : mb_substr($parts['fragment'], 1, null, 'UTF-8'),
74
            ]
75 453
        ));
76
    }
77
78
    /**
79
     * Parse a string as an URI according to the regexp form rfc3986
80
     *
81
     * @see UriParser::parse
82
     *
83
     * @param string $uri The URI to parse
84
     *
85
     * @return array the array is similar to PHP's parse_url hash response
86
     */
87 692
    public function __invoke($uri)
88
    {
89 692
        return $this->parse($uri);
90
    }
91
92
    /**
93
     * Extract URI parts
94
     *
95
     * @see http://tools.ietf.org/html/rfc3986#appendix-B
96
     *
97
     * @param string $uri The URI to split
98
     *
99
     * @return string[]
100
     */
101 692
    protected function extractUriParts($uri)
102
    {
103 692
        preg_match(self::REGEXP_URI, $uri, $parts);
104 692
        $parts += ['query' => '', 'fragment' => ''];
105
106 692
        if (preg_match(self::REGEXP_SCHEME, $parts['scheme'])) {
107 686
            return $parts;
108
        }
109
110 6
        $parts['path'] = $parts['scheme'].':'.$parts['authority'].$parts['path'];
111 6
        $parts['scheme'] = '';
112 6
        $parts['authority'] = '';
113
114 6
        return $parts;
115
    }
116
117
    /**
118
     * Parse a URI authority part into its components
119
     *
120
     * @param string $authority
121
     *
122
     * @return array
123
     */
124 692
    protected function parseAuthority($authority)
125
    {
126 692
        $res = ['user' => null, 'pass' => null, 'host' => null, 'port' => null];
127 692
        if ('' === $authority) {
128 234
            return $res;
129
        }
130
131 557
        $content = mb_substr($authority, 2, null, 'UTF-8');
132 557
        if ('' === $content) {
133 3
            return ['host' => ''] + $res;
134
        }
135
136 554
        preg_match(self::REGEXP_AUTHORITY, $content, $auth);
137 554
        if ('' !== $auth['userinfo']) {
138 188
            $userinfo = explode(':', $auth['ucontent'], 2);
139 188
            $res = ['user' => array_shift($userinfo), 'pass' => array_shift($userinfo)] + $res;
140 125
        }
141
142 554
        return $this->parseHostname($auth['hostname']) + $res;
143
    }
144
145
    /**
146
     * Parse the hostname into its components Host and Port
147
     *
148
     * No validation is done on the port or host component found
149
     *
150
     * @param string $hostname
151
     *
152
     * @return array
153
     */
154 554
    protected function parseHostname($hostname)
155
    {
156 554
        $components = ['host' => null, 'port' => null];
157 554
        $hostname = strrev($hostname);
158 554
        if (preg_match(self::REGEXP_REVERSE_HOSTNAME, $hostname, $res)) {
159 554
            $components['host'] = strrev($res['host']);
160 554
            $components['port'] = strrev($res['port']);
161 369
        }
162 554
        $components['host'] = $this->filterHost($components['host']);
163 545
        $components['port'] = $this->validatePort($components['port']);
164
165 542
        return $components;
166
    }
167
168
    /**
169
     * validate the host component
170
     *
171
     * @param string $host
172
     *
173
     * @return string
174
     */
175 554
    protected function filterHost($host)
176
    {
177 554
        if (empty($this->validateIpHost($host))) {
178 539
            $this->validateStringHost($host);
179 353
        }
180
181 545
        return $host;
182
    }
183
184
    /**
185
     * @inheritdoc
186
     */
187 539
    protected function setIsAbsolute($host)
188
    {
189 539
        return ('.' == mb_substr($host, -1, 1, 'UTF-8')) ? mb_substr($host, 0, -1, 'UTF-8') : $host;
190
    }
191
192
    /**
193
     * @inheritdoc
194
     */
195 539
    protected function assertLabelsCount(array $labels)
196
    {
197 539
        if (127 <= count($labels)) {
198 3
            throw new InvalidArgumentException('Invalid Host, verify labels count');
199
        }
200 536
    }
201
}
202