friendship ended with social-app. php is my new best friend
1<?php
2
3namespace React\Http\Message;
4
5use Psr\Http\Message\UriInterface;
6
7/**
8 * Respresents a URI (or URL).
9 *
10 * This class implements the
11 * [PSR-7 `UriInterface`](https://www.php-fig.org/psr/psr-7/#35-psrhttpmessageuriinterface).
12 *
13 * This is mostly used internally to represent the URI of each HTTP request
14 * message for our HTTP client and server implementations. Likewise, you may
15 * also use this class with other HTTP implementations and for tests.
16 *
17 * @see UriInterface
18 */
19final class Uri implements UriInterface
20{
21 /** @var string */
22 private $scheme = '';
23
24 /** @var string */
25 private $userInfo = '';
26
27 /** @var string */
28 private $host = '';
29
30 /** @var ?int */
31 private $port = null;
32
33 /** @var string */
34 private $path = '';
35
36 /** @var string */
37 private $query = '';
38
39 /** @var string */
40 private $fragment = '';
41
42 /**
43 * @param string $uri
44 * @throws \InvalidArgumentException if given $uri is invalid
45 */
46 public function __construct($uri)
47 {
48 // @codeCoverageIgnoreStart
49 if (\PHP_VERSION_ID < 50407 && \strpos($uri, '//') === 0) {
50 // @link https://3v4l.org/UrAQP
51 $parts = \parse_url('http:' . $uri);
52 unset($parts['schema']);
53 } else {
54 $parts = \parse_url($uri);
55 }
56 // @codeCoverageIgnoreEnd
57
58 if ($parts === false || (isset($parts['scheme']) && !\preg_match('#^[a-z]+$#i', $parts['scheme'])) || (isset($parts['host']) && \preg_match('#[\s%+]#', $parts['host']))) {
59 throw new \InvalidArgumentException('Invalid URI given');
60 }
61
62 if (isset($parts['scheme'])) {
63 $this->scheme = \strtolower($parts['scheme']);
64 }
65
66 if (isset($parts['user']) || isset($parts['pass'])) {
67 $this->userInfo = $this->encode(isset($parts['user']) ? $parts['user'] : '', \PHP_URL_USER) . (isset($parts['pass']) ? ':' . $this->encode($parts['pass'], \PHP_URL_PASS) : '');
68 }
69
70 if (isset($parts['host'])) {
71 $this->host = \strtolower($parts['host']);
72 }
73
74 if (isset($parts['port']) && !(($parts['port'] === 80 && $this->scheme === 'http') || ($parts['port'] === 443 && $this->scheme === 'https'))) {
75 $this->port = $parts['port'];
76 }
77
78 if (isset($parts['path'])) {
79 $this->path = $this->encode($parts['path'], \PHP_URL_PATH);
80 }
81
82 if (isset($parts['query'])) {
83 $this->query = $this->encode($parts['query'], \PHP_URL_QUERY);
84 }
85
86 if (isset($parts['fragment'])) {
87 $this->fragment = $this->encode($parts['fragment'], \PHP_URL_FRAGMENT);
88 }
89 }
90
91 public function getScheme()
92 {
93 return $this->scheme;
94 }
95
96 public function getAuthority()
97 {
98 if ($this->host === '') {
99 return '';
100 }
101
102 return ($this->userInfo !== '' ? $this->userInfo . '@' : '') . $this->host . ($this->port !== null ? ':' . $this->port : '');
103 }
104
105 public function getUserInfo()
106 {
107 return $this->userInfo;
108 }
109
110 public function getHost()
111 {
112 return $this->host;
113 }
114
115 public function getPort()
116 {
117 return $this->port;
118 }
119
120 public function getPath()
121 {
122 return $this->path;
123 }
124
125 public function getQuery()
126 {
127 return $this->query;
128 }
129
130 public function getFragment()
131 {
132 return $this->fragment;
133 }
134
135 public function withScheme($scheme)
136 {
137 $scheme = \strtolower($scheme);
138 if ($scheme === $this->scheme) {
139 return $this;
140 }
141
142 if (!\preg_match('#^[a-z]*$#', $scheme)) {
143 throw new \InvalidArgumentException('Invalid URI scheme given');
144 }
145
146 $new = clone $this;
147 $new->scheme = $scheme;
148
149 if (($this->port === 80 && $scheme === 'http') || ($this->port === 443 && $scheme === 'https')) {
150 $new->port = null;
151 }
152
153 return $new;
154 }
155
156 public function withUserInfo($user, $password = null)
157 {
158 $userInfo = $this->encode($user, \PHP_URL_USER) . ($password !== null ? ':' . $this->encode($password, \PHP_URL_PASS) : '');
159 if ($userInfo === $this->userInfo) {
160 return $this;
161 }
162
163 $new = clone $this;
164 $new->userInfo = $userInfo;
165
166 return $new;
167 }
168
169 public function withHost($host)
170 {
171 $host = \strtolower($host);
172 if ($host === $this->host) {
173 return $this;
174 }
175
176 if (\preg_match('#[\s%+]#', $host) || ($host !== '' && \parse_url('http://' . $host, \PHP_URL_HOST) !== $host)) {
177 throw new \InvalidArgumentException('Invalid URI host given');
178 }
179
180 $new = clone $this;
181 $new->host = $host;
182
183 return $new;
184 }
185
186 public function withPort($port)
187 {
188 $port = $port === null ? null : (int) $port;
189 if (($port === 80 && $this->scheme === 'http') || ($port === 443 && $this->scheme === 'https')) {
190 $port = null;
191 }
192
193 if ($port === $this->port) {
194 return $this;
195 }
196
197 if ($port !== null && ($port < 1 || $port > 0xffff)) {
198 throw new \InvalidArgumentException('Invalid URI port given');
199 }
200
201 $new = clone $this;
202 $new->port = $port;
203
204 return $new;
205 }
206
207 public function withPath($path)
208 {
209 $path = $this->encode($path, \PHP_URL_PATH);
210 if ($path === $this->path) {
211 return $this;
212 }
213
214 $new = clone $this;
215 $new->path = $path;
216
217 return $new;
218 }
219
220 public function withQuery($query)
221 {
222 $query = $this->encode($query, \PHP_URL_QUERY);
223 if ($query === $this->query) {
224 return $this;
225 }
226
227 $new = clone $this;
228 $new->query = $query;
229
230 return $new;
231 }
232
233 public function withFragment($fragment)
234 {
235 $fragment = $this->encode($fragment, \PHP_URL_FRAGMENT);
236 if ($fragment === $this->fragment) {
237 return $this;
238 }
239
240 $new = clone $this;
241 $new->fragment = $fragment;
242
243 return $new;
244 }
245
246 public function __toString()
247 {
248 $uri = '';
249 if ($this->scheme !== '') {
250 $uri .= $this->scheme . ':';
251 }
252
253 $authority = $this->getAuthority();
254 if ($authority !== '') {
255 $uri .= '//' . $authority;
256 }
257
258 if ($authority !== '' && isset($this->path[0]) && $this->path[0] !== '/') {
259 $uri .= '/' . $this->path;
260 } elseif ($authority === '' && isset($this->path[0]) && $this->path[0] === '/') {
261 $uri .= '/' . \ltrim($this->path, '/');
262 } else {
263 $uri .= $this->path;
264 }
265
266 if ($this->query !== '') {
267 $uri .= '?' . $this->query;
268 }
269
270 if ($this->fragment !== '') {
271 $uri .= '#' . $this->fragment;
272 }
273
274 return $uri;
275 }
276
277 /**
278 * @param string $part
279 * @param int $component
280 * @return string
281 */
282 private function encode($part, $component)
283 {
284 return \preg_replace_callback(
285 '/(?:[^a-z0-9_\-\.~!\$&\'\(\)\*\+,;=' . ($component === \PHP_URL_PATH ? ':@\/' : ($component === \PHP_URL_QUERY || $component === \PHP_URL_FRAGMENT ? ':@\/\?' : '')) . '%]++|%(?![a-f0-9]{2}))/i',
286 function (array $match) {
287 return \rawurlencode($match[0]);
288 },
289 $part
290 );
291 }
292
293 /**
294 * [Internal] Resolve URI relative to base URI and return new absolute URI
295 *
296 * @internal
297 * @param UriInterface $base
298 * @param UriInterface $rel
299 * @return UriInterface
300 * @throws void
301 */
302 public static function resolve(UriInterface $base, UriInterface $rel)
303 {
304 if ($rel->getScheme() !== '') {
305 return $rel->getPath() === '' ? $rel : $rel->withPath(self::removeDotSegments($rel->getPath()));
306 }
307
308 $reset = false;
309 $new = $base;
310 if ($rel->getAuthority() !== '') {
311 $reset = true;
312 $userInfo = \explode(':', $rel->getUserInfo(), 2);
313 $new = $base->withUserInfo($userInfo[0], isset($userInfo[1]) ? $userInfo[1]: null)->withHost($rel->getHost())->withPort($rel->getPort());
314 }
315
316 if ($reset && $rel->getPath() === '') {
317 $new = $new->withPath('');
318 } elseif (($path = $rel->getPath()) !== '') {
319 $start = '';
320 if ($path === '' || $path[0] !== '/') {
321 $start = $base->getPath();
322 if (\substr($start, -1) !== '/') {
323 $start .= '/../';
324 }
325 }
326 $reset = true;
327 $new = $new->withPath(self::removeDotSegments($start . $path));
328 }
329 if ($reset || $rel->getQuery() !== '') {
330 $reset = true;
331 $new = $new->withQuery($rel->getQuery());
332 }
333 if ($reset || $rel->getFragment() !== '') {
334 $new = $new->withFragment($rel->getFragment());
335 }
336
337 return $new;
338 }
339
340 /**
341 * @param string $path
342 * @return string
343 */
344 private static function removeDotSegments($path)
345 {
346 $segments = array();
347 foreach (\explode('/', $path) as $segment) {
348 if ($segment === '..') {
349 \array_pop($segments);
350 } elseif ($segment !== '.' && $segment !== '') {
351 $segments[] = $segment;
352 }
353 }
354 return '/' . \implode('/', $segments) . ($path !== '/' && \substr($path, -1) === '/' ? '/' : '');
355 }
356}