Chris@0
|
1 <?php
|
Chris@0
|
2 /**
|
Chris@0
|
3 * Zend Framework (http://framework.zend.com/)
|
Chris@0
|
4 *
|
Chris@0
|
5 * @see http://github.com/zendframework/zend-diactoros for the canonical source repository
|
Chris@0
|
6 * @copyright Copyright (c) 2015-2016 Zend Technologies USA Inc. (http://www.zend.com)
|
Chris@0
|
7 * @license https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
|
Chris@0
|
8 */
|
Chris@0
|
9
|
Chris@0
|
10 namespace Zend\Diactoros;
|
Chris@0
|
11
|
Chris@0
|
12 use InvalidArgumentException;
|
Chris@0
|
13 use Psr\Http\Message\UriInterface;
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * Implementation of Psr\Http\UriInterface.
|
Chris@0
|
17 *
|
Chris@0
|
18 * Provides a value object representing a URI for HTTP requests.
|
Chris@0
|
19 *
|
Chris@0
|
20 * Instances of this class are considered immutable; all methods that
|
Chris@0
|
21 * might change state are implemented such that they retain the internal
|
Chris@0
|
22 * state of the current instance and return a new instance that contains the
|
Chris@0
|
23 * changed state.
|
Chris@0
|
24 */
|
Chris@0
|
25 class Uri implements UriInterface
|
Chris@0
|
26 {
|
Chris@0
|
27 /**
|
Chris@0
|
28 * Sub-delimiters used in query strings and fragments.
|
Chris@0
|
29 *
|
Chris@0
|
30 * @const string
|
Chris@0
|
31 */
|
Chris@0
|
32 const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * Unreserved characters used in paths, query strings, and fragments.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @const string
|
Chris@0
|
38 */
|
Chris@0
|
39 const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~\pL';
|
Chris@0
|
40
|
Chris@0
|
41 /**
|
Chris@0
|
42 * @var int[] Array indexed by valid scheme names to their corresponding ports.
|
Chris@0
|
43 */
|
Chris@0
|
44 protected $allowedSchemes = [
|
Chris@0
|
45 'http' => 80,
|
Chris@0
|
46 'https' => 443,
|
Chris@0
|
47 ];
|
Chris@0
|
48
|
Chris@0
|
49 /**
|
Chris@0
|
50 * @var string
|
Chris@0
|
51 */
|
Chris@0
|
52 private $scheme = '';
|
Chris@0
|
53
|
Chris@0
|
54 /**
|
Chris@0
|
55 * @var string
|
Chris@0
|
56 */
|
Chris@0
|
57 private $userInfo = '';
|
Chris@0
|
58
|
Chris@0
|
59 /**
|
Chris@0
|
60 * @var string
|
Chris@0
|
61 */
|
Chris@0
|
62 private $host = '';
|
Chris@0
|
63
|
Chris@0
|
64 /**
|
Chris@0
|
65 * @var int
|
Chris@0
|
66 */
|
Chris@0
|
67 private $port;
|
Chris@0
|
68
|
Chris@0
|
69 /**
|
Chris@0
|
70 * @var string
|
Chris@0
|
71 */
|
Chris@0
|
72 private $path = '';
|
Chris@0
|
73
|
Chris@0
|
74 /**
|
Chris@0
|
75 * @var string
|
Chris@0
|
76 */
|
Chris@0
|
77 private $query = '';
|
Chris@0
|
78
|
Chris@0
|
79 /**
|
Chris@0
|
80 * @var string
|
Chris@0
|
81 */
|
Chris@0
|
82 private $fragment = '';
|
Chris@0
|
83
|
Chris@0
|
84 /**
|
Chris@0
|
85 * generated uri string cache
|
Chris@0
|
86 * @var string|null
|
Chris@0
|
87 */
|
Chris@0
|
88 private $uriString;
|
Chris@0
|
89
|
Chris@0
|
90 /**
|
Chris@0
|
91 * @param string $uri
|
Chris@0
|
92 * @throws InvalidArgumentException on non-string $uri argument
|
Chris@0
|
93 */
|
Chris@0
|
94 public function __construct($uri = '')
|
Chris@0
|
95 {
|
Chris@0
|
96 if (! is_string($uri)) {
|
Chris@0
|
97 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
98 'URI passed to constructor must be a string; received "%s"',
|
Chris@0
|
99 (is_object($uri) ? get_class($uri) : gettype($uri))
|
Chris@0
|
100 ));
|
Chris@0
|
101 }
|
Chris@0
|
102
|
Chris@0
|
103 if (! empty($uri)) {
|
Chris@0
|
104 $this->parseUri($uri);
|
Chris@0
|
105 }
|
Chris@0
|
106 }
|
Chris@0
|
107
|
Chris@0
|
108 /**
|
Chris@0
|
109 * Operations to perform on clone.
|
Chris@0
|
110 *
|
Chris@0
|
111 * Since cloning usually is for purposes of mutation, we reset the
|
Chris@0
|
112 * $uriString property so it will be re-calculated.
|
Chris@0
|
113 */
|
Chris@0
|
114 public function __clone()
|
Chris@0
|
115 {
|
Chris@0
|
116 $this->uriString = null;
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 /**
|
Chris@0
|
120 * {@inheritdoc}
|
Chris@0
|
121 */
|
Chris@0
|
122 public function __toString()
|
Chris@0
|
123 {
|
Chris@0
|
124 if (null !== $this->uriString) {
|
Chris@0
|
125 return $this->uriString;
|
Chris@0
|
126 }
|
Chris@0
|
127
|
Chris@0
|
128 $this->uriString = static::createUriString(
|
Chris@0
|
129 $this->scheme,
|
Chris@0
|
130 $this->getAuthority(),
|
Chris@0
|
131 $this->getPath(), // Absolute URIs should use a "/" for an empty path
|
Chris@0
|
132 $this->query,
|
Chris@0
|
133 $this->fragment
|
Chris@0
|
134 );
|
Chris@0
|
135
|
Chris@0
|
136 return $this->uriString;
|
Chris@0
|
137 }
|
Chris@0
|
138
|
Chris@0
|
139 /**
|
Chris@0
|
140 * {@inheritdoc}
|
Chris@0
|
141 */
|
Chris@0
|
142 public function getScheme()
|
Chris@0
|
143 {
|
Chris@0
|
144 return $this->scheme;
|
Chris@0
|
145 }
|
Chris@0
|
146
|
Chris@0
|
147 /**
|
Chris@0
|
148 * {@inheritdoc}
|
Chris@0
|
149 */
|
Chris@0
|
150 public function getAuthority()
|
Chris@0
|
151 {
|
Chris@0
|
152 if (empty($this->host)) {
|
Chris@0
|
153 return '';
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 $authority = $this->host;
|
Chris@0
|
157 if (! empty($this->userInfo)) {
|
Chris@0
|
158 $authority = $this->userInfo . '@' . $authority;
|
Chris@0
|
159 }
|
Chris@0
|
160
|
Chris@0
|
161 if ($this->isNonStandardPort($this->scheme, $this->host, $this->port)) {
|
Chris@0
|
162 $authority .= ':' . $this->port;
|
Chris@0
|
163 }
|
Chris@0
|
164
|
Chris@0
|
165 return $authority;
|
Chris@0
|
166 }
|
Chris@0
|
167
|
Chris@0
|
168 /**
|
Chris@0
|
169 * {@inheritdoc}
|
Chris@0
|
170 */
|
Chris@0
|
171 public function getUserInfo()
|
Chris@0
|
172 {
|
Chris@0
|
173 return $this->userInfo;
|
Chris@0
|
174 }
|
Chris@0
|
175
|
Chris@0
|
176 /**
|
Chris@0
|
177 * {@inheritdoc}
|
Chris@0
|
178 */
|
Chris@0
|
179 public function getHost()
|
Chris@0
|
180 {
|
Chris@0
|
181 return $this->host;
|
Chris@0
|
182 }
|
Chris@0
|
183
|
Chris@0
|
184 /**
|
Chris@0
|
185 * {@inheritdoc}
|
Chris@0
|
186 */
|
Chris@0
|
187 public function getPort()
|
Chris@0
|
188 {
|
Chris@0
|
189 return $this->isNonStandardPort($this->scheme, $this->host, $this->port)
|
Chris@0
|
190 ? $this->port
|
Chris@0
|
191 : null;
|
Chris@0
|
192 }
|
Chris@0
|
193
|
Chris@0
|
194 /**
|
Chris@0
|
195 * {@inheritdoc}
|
Chris@0
|
196 */
|
Chris@0
|
197 public function getPath()
|
Chris@0
|
198 {
|
Chris@0
|
199 return $this->path;
|
Chris@0
|
200 }
|
Chris@0
|
201
|
Chris@0
|
202 /**
|
Chris@0
|
203 * {@inheritdoc}
|
Chris@0
|
204 */
|
Chris@0
|
205 public function getQuery()
|
Chris@0
|
206 {
|
Chris@0
|
207 return $this->query;
|
Chris@0
|
208 }
|
Chris@0
|
209
|
Chris@0
|
210 /**
|
Chris@0
|
211 * {@inheritdoc}
|
Chris@0
|
212 */
|
Chris@0
|
213 public function getFragment()
|
Chris@0
|
214 {
|
Chris@0
|
215 return $this->fragment;
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 /**
|
Chris@0
|
219 * {@inheritdoc}
|
Chris@0
|
220 */
|
Chris@0
|
221 public function withScheme($scheme)
|
Chris@0
|
222 {
|
Chris@0
|
223 if (! is_string($scheme)) {
|
Chris@0
|
224 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
225 '%s expects a string argument; received %s',
|
Chris@0
|
226 __METHOD__,
|
Chris@0
|
227 (is_object($scheme) ? get_class($scheme) : gettype($scheme))
|
Chris@0
|
228 ));
|
Chris@0
|
229 }
|
Chris@0
|
230
|
Chris@0
|
231 $scheme = $this->filterScheme($scheme);
|
Chris@0
|
232
|
Chris@0
|
233 if ($scheme === $this->scheme) {
|
Chris@0
|
234 // Do nothing if no change was made.
|
Chris@0
|
235 return clone $this;
|
Chris@0
|
236 }
|
Chris@0
|
237
|
Chris@0
|
238 $new = clone $this;
|
Chris@0
|
239 $new->scheme = $scheme;
|
Chris@0
|
240
|
Chris@0
|
241 return $new;
|
Chris@0
|
242 }
|
Chris@0
|
243
|
Chris@0
|
244 /**
|
Chris@0
|
245 * {@inheritdoc}
|
Chris@0
|
246 */
|
Chris@0
|
247 public function withUserInfo($user, $password = null)
|
Chris@0
|
248 {
|
Chris@0
|
249 if (! is_string($user)) {
|
Chris@0
|
250 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
251 '%s expects a string user argument; received %s',
|
Chris@0
|
252 __METHOD__,
|
Chris@0
|
253 (is_object($user) ? get_class($user) : gettype($user))
|
Chris@0
|
254 ));
|
Chris@0
|
255 }
|
Chris@0
|
256 if (null !== $password && ! is_string($password)) {
|
Chris@0
|
257 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
258 '%s expects a string password argument; received %s',
|
Chris@0
|
259 __METHOD__,
|
Chris@0
|
260 (is_object($password) ? get_class($password) : gettype($password))
|
Chris@0
|
261 ));
|
Chris@0
|
262 }
|
Chris@0
|
263
|
Chris@0
|
264 $info = $user;
|
Chris@0
|
265 if ($password) {
|
Chris@0
|
266 $info .= ':' . $password;
|
Chris@0
|
267 }
|
Chris@0
|
268
|
Chris@0
|
269 if ($info === $this->userInfo) {
|
Chris@0
|
270 // Do nothing if no change was made.
|
Chris@0
|
271 return clone $this;
|
Chris@0
|
272 }
|
Chris@0
|
273
|
Chris@0
|
274 $new = clone $this;
|
Chris@0
|
275 $new->userInfo = $info;
|
Chris@0
|
276
|
Chris@0
|
277 return $new;
|
Chris@0
|
278 }
|
Chris@0
|
279
|
Chris@0
|
280 /**
|
Chris@0
|
281 * {@inheritdoc}
|
Chris@0
|
282 */
|
Chris@0
|
283 public function withHost($host)
|
Chris@0
|
284 {
|
Chris@0
|
285 if (! is_string($host)) {
|
Chris@0
|
286 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
287 '%s expects a string argument; received %s',
|
Chris@0
|
288 __METHOD__,
|
Chris@0
|
289 (is_object($host) ? get_class($host) : gettype($host))
|
Chris@0
|
290 ));
|
Chris@0
|
291 }
|
Chris@0
|
292
|
Chris@0
|
293 if ($host === $this->host) {
|
Chris@0
|
294 // Do nothing if no change was made.
|
Chris@0
|
295 return clone $this;
|
Chris@0
|
296 }
|
Chris@0
|
297
|
Chris@0
|
298 $new = clone $this;
|
Chris@0
|
299 $new->host = $host;
|
Chris@0
|
300
|
Chris@0
|
301 return $new;
|
Chris@0
|
302 }
|
Chris@0
|
303
|
Chris@0
|
304 /**
|
Chris@0
|
305 * {@inheritdoc}
|
Chris@0
|
306 */
|
Chris@0
|
307 public function withPort($port)
|
Chris@0
|
308 {
|
Chris@0
|
309 if (! is_numeric($port) && $port !== null) {
|
Chris@0
|
310 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
311 'Invalid port "%s" specified; must be an integer, an integer string, or null',
|
Chris@0
|
312 (is_object($port) ? get_class($port) : gettype($port))
|
Chris@0
|
313 ));
|
Chris@0
|
314 }
|
Chris@0
|
315
|
Chris@0
|
316 if ($port !== null) {
|
Chris@0
|
317 $port = (int) $port;
|
Chris@0
|
318 }
|
Chris@0
|
319
|
Chris@0
|
320 if ($port === $this->port) {
|
Chris@0
|
321 // Do nothing if no change was made.
|
Chris@0
|
322 return clone $this;
|
Chris@0
|
323 }
|
Chris@0
|
324
|
Chris@0
|
325 if ($port !== null && $port < 1 || $port > 65535) {
|
Chris@0
|
326 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
327 'Invalid port "%d" specified; must be a valid TCP/UDP port',
|
Chris@0
|
328 $port
|
Chris@0
|
329 ));
|
Chris@0
|
330 }
|
Chris@0
|
331
|
Chris@0
|
332 $new = clone $this;
|
Chris@0
|
333 $new->port = $port;
|
Chris@0
|
334
|
Chris@0
|
335 return $new;
|
Chris@0
|
336 }
|
Chris@0
|
337
|
Chris@0
|
338 /**
|
Chris@0
|
339 * {@inheritdoc}
|
Chris@0
|
340 */
|
Chris@0
|
341 public function withPath($path)
|
Chris@0
|
342 {
|
Chris@0
|
343 if (! is_string($path)) {
|
Chris@0
|
344 throw new InvalidArgumentException(
|
Chris@0
|
345 'Invalid path provided; must be a string'
|
Chris@0
|
346 );
|
Chris@0
|
347 }
|
Chris@0
|
348
|
Chris@0
|
349 if (strpos($path, '?') !== false) {
|
Chris@0
|
350 throw new InvalidArgumentException(
|
Chris@0
|
351 'Invalid path provided; must not contain a query string'
|
Chris@0
|
352 );
|
Chris@0
|
353 }
|
Chris@0
|
354
|
Chris@0
|
355 if (strpos($path, '#') !== false) {
|
Chris@0
|
356 throw new InvalidArgumentException(
|
Chris@0
|
357 'Invalid path provided; must not contain a URI fragment'
|
Chris@0
|
358 );
|
Chris@0
|
359 }
|
Chris@0
|
360
|
Chris@0
|
361 $path = $this->filterPath($path);
|
Chris@0
|
362
|
Chris@0
|
363 if ($path === $this->path) {
|
Chris@0
|
364 // Do nothing if no change was made.
|
Chris@0
|
365 return clone $this;
|
Chris@0
|
366 }
|
Chris@0
|
367
|
Chris@0
|
368 $new = clone $this;
|
Chris@0
|
369 $new->path = $path;
|
Chris@0
|
370
|
Chris@0
|
371 return $new;
|
Chris@0
|
372 }
|
Chris@0
|
373
|
Chris@0
|
374 /**
|
Chris@0
|
375 * {@inheritdoc}
|
Chris@0
|
376 */
|
Chris@0
|
377 public function withQuery($query)
|
Chris@0
|
378 {
|
Chris@0
|
379 if (! is_string($query)) {
|
Chris@0
|
380 throw new InvalidArgumentException(
|
Chris@0
|
381 'Query string must be a string'
|
Chris@0
|
382 );
|
Chris@0
|
383 }
|
Chris@0
|
384
|
Chris@0
|
385 if (strpos($query, '#') !== false) {
|
Chris@0
|
386 throw new InvalidArgumentException(
|
Chris@0
|
387 'Query string must not include a URI fragment'
|
Chris@0
|
388 );
|
Chris@0
|
389 }
|
Chris@0
|
390
|
Chris@0
|
391 $query = $this->filterQuery($query);
|
Chris@0
|
392
|
Chris@0
|
393 if ($query === $this->query) {
|
Chris@0
|
394 // Do nothing if no change was made.
|
Chris@0
|
395 return clone $this;
|
Chris@0
|
396 }
|
Chris@0
|
397
|
Chris@0
|
398 $new = clone $this;
|
Chris@0
|
399 $new->query = $query;
|
Chris@0
|
400
|
Chris@0
|
401 return $new;
|
Chris@0
|
402 }
|
Chris@0
|
403
|
Chris@0
|
404 /**
|
Chris@0
|
405 * {@inheritdoc}
|
Chris@0
|
406 */
|
Chris@0
|
407 public function withFragment($fragment)
|
Chris@0
|
408 {
|
Chris@0
|
409 if (! is_string($fragment)) {
|
Chris@0
|
410 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
411 '%s expects a string argument; received %s',
|
Chris@0
|
412 __METHOD__,
|
Chris@0
|
413 (is_object($fragment) ? get_class($fragment) : gettype($fragment))
|
Chris@0
|
414 ));
|
Chris@0
|
415 }
|
Chris@0
|
416
|
Chris@0
|
417 $fragment = $this->filterFragment($fragment);
|
Chris@0
|
418
|
Chris@0
|
419 if ($fragment === $this->fragment) {
|
Chris@0
|
420 // Do nothing if no change was made.
|
Chris@0
|
421 return clone $this;
|
Chris@0
|
422 }
|
Chris@0
|
423
|
Chris@0
|
424 $new = clone $this;
|
Chris@0
|
425 $new->fragment = $fragment;
|
Chris@0
|
426
|
Chris@0
|
427 return $new;
|
Chris@0
|
428 }
|
Chris@0
|
429
|
Chris@0
|
430 /**
|
Chris@0
|
431 * Parse a URI into its parts, and set the properties
|
Chris@0
|
432 *
|
Chris@0
|
433 * @param string $uri
|
Chris@0
|
434 */
|
Chris@0
|
435 private function parseUri($uri)
|
Chris@0
|
436 {
|
Chris@0
|
437 $parts = parse_url($uri);
|
Chris@0
|
438
|
Chris@0
|
439 if (false === $parts) {
|
Chris@0
|
440 throw new \InvalidArgumentException(
|
Chris@0
|
441 'The source URI string appears to be malformed'
|
Chris@0
|
442 );
|
Chris@0
|
443 }
|
Chris@0
|
444
|
Chris@0
|
445 $this->scheme = isset($parts['scheme']) ? $this->filterScheme($parts['scheme']) : '';
|
Chris@0
|
446 $this->userInfo = isset($parts['user']) ? $parts['user'] : '';
|
Chris@0
|
447 $this->host = isset($parts['host']) ? $parts['host'] : '';
|
Chris@0
|
448 $this->port = isset($parts['port']) ? $parts['port'] : null;
|
Chris@0
|
449 $this->path = isset($parts['path']) ? $this->filterPath($parts['path']) : '';
|
Chris@0
|
450 $this->query = isset($parts['query']) ? $this->filterQuery($parts['query']) : '';
|
Chris@0
|
451 $this->fragment = isset($parts['fragment']) ? $this->filterFragment($parts['fragment']) : '';
|
Chris@0
|
452
|
Chris@0
|
453 if (isset($parts['pass'])) {
|
Chris@0
|
454 $this->userInfo .= ':' . $parts['pass'];
|
Chris@0
|
455 }
|
Chris@0
|
456 }
|
Chris@0
|
457
|
Chris@0
|
458 /**
|
Chris@0
|
459 * Create a URI string from its various parts
|
Chris@0
|
460 *
|
Chris@0
|
461 * @param string $scheme
|
Chris@0
|
462 * @param string $authority
|
Chris@0
|
463 * @param string $path
|
Chris@0
|
464 * @param string $query
|
Chris@0
|
465 * @param string $fragment
|
Chris@0
|
466 * @return string
|
Chris@0
|
467 */
|
Chris@0
|
468 private static function createUriString($scheme, $authority, $path, $query, $fragment)
|
Chris@0
|
469 {
|
Chris@0
|
470 $uri = '';
|
Chris@0
|
471
|
Chris@0
|
472 if (! empty($scheme)) {
|
Chris@0
|
473 $uri .= sprintf('%s:', $scheme);
|
Chris@0
|
474 }
|
Chris@0
|
475
|
Chris@0
|
476 if (! empty($authority)) {
|
Chris@0
|
477 $uri .= '//' . $authority;
|
Chris@0
|
478 }
|
Chris@0
|
479
|
Chris@0
|
480 if ($path) {
|
Chris@0
|
481 if (empty($path) || '/' !== substr($path, 0, 1)) {
|
Chris@0
|
482 $path = '/' . $path;
|
Chris@0
|
483 }
|
Chris@0
|
484
|
Chris@0
|
485 $uri .= $path;
|
Chris@0
|
486 }
|
Chris@0
|
487
|
Chris@0
|
488 if ($query) {
|
Chris@0
|
489 $uri .= sprintf('?%s', $query);
|
Chris@0
|
490 }
|
Chris@0
|
491
|
Chris@0
|
492 if ($fragment) {
|
Chris@0
|
493 $uri .= sprintf('#%s', $fragment);
|
Chris@0
|
494 }
|
Chris@0
|
495
|
Chris@0
|
496 return $uri;
|
Chris@0
|
497 }
|
Chris@0
|
498
|
Chris@0
|
499 /**
|
Chris@0
|
500 * Is a given port non-standard for the current scheme?
|
Chris@0
|
501 *
|
Chris@0
|
502 * @param string $scheme
|
Chris@0
|
503 * @param string $host
|
Chris@0
|
504 * @param int $port
|
Chris@0
|
505 * @return bool
|
Chris@0
|
506 */
|
Chris@0
|
507 private function isNonStandardPort($scheme, $host, $port)
|
Chris@0
|
508 {
|
Chris@0
|
509 if (! $scheme) {
|
Chris@0
|
510 if ($host && ! $port) {
|
Chris@0
|
511 return false;
|
Chris@0
|
512 }
|
Chris@0
|
513 return true;
|
Chris@0
|
514 }
|
Chris@0
|
515
|
Chris@0
|
516 if (! $host || ! $port) {
|
Chris@0
|
517 return false;
|
Chris@0
|
518 }
|
Chris@0
|
519
|
Chris@0
|
520 return ! isset($this->allowedSchemes[$scheme]) || $port !== $this->allowedSchemes[$scheme];
|
Chris@0
|
521 }
|
Chris@0
|
522
|
Chris@0
|
523 /**
|
Chris@0
|
524 * Filters the scheme to ensure it is a valid scheme.
|
Chris@0
|
525 *
|
Chris@0
|
526 * @param string $scheme Scheme name.
|
Chris@0
|
527 *
|
Chris@0
|
528 * @return string Filtered scheme.
|
Chris@0
|
529 */
|
Chris@0
|
530 private function filterScheme($scheme)
|
Chris@0
|
531 {
|
Chris@0
|
532 $scheme = strtolower($scheme);
|
Chris@0
|
533 $scheme = preg_replace('#:(//)?$#', '', $scheme);
|
Chris@0
|
534
|
Chris@0
|
535 if (empty($scheme)) {
|
Chris@0
|
536 return '';
|
Chris@0
|
537 }
|
Chris@0
|
538
|
Chris@0
|
539 if (! array_key_exists($scheme, $this->allowedSchemes)) {
|
Chris@0
|
540 throw new InvalidArgumentException(sprintf(
|
Chris@0
|
541 'Unsupported scheme "%s"; must be any empty string or in the set (%s)',
|
Chris@0
|
542 $scheme,
|
Chris@0
|
543 implode(', ', array_keys($this->allowedSchemes))
|
Chris@0
|
544 ));
|
Chris@0
|
545 }
|
Chris@0
|
546
|
Chris@0
|
547 return $scheme;
|
Chris@0
|
548 }
|
Chris@0
|
549
|
Chris@0
|
550 /**
|
Chris@0
|
551 * Filters the path of a URI to ensure it is properly encoded.
|
Chris@0
|
552 *
|
Chris@0
|
553 * @param string $path
|
Chris@0
|
554 * @return string
|
Chris@0
|
555 */
|
Chris@0
|
556 private function filterPath($path)
|
Chris@0
|
557 {
|
Chris@0
|
558 $path = preg_replace_callback(
|
Chris@0
|
559 '/(?:[^' . self::CHAR_UNRESERVED . ')(:@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/u',
|
Chris@0
|
560 [$this, 'urlEncodeChar'],
|
Chris@0
|
561 $path
|
Chris@0
|
562 );
|
Chris@0
|
563
|
Chris@0
|
564 if (empty($path)) {
|
Chris@0
|
565 // No path
|
Chris@0
|
566 return $path;
|
Chris@0
|
567 }
|
Chris@0
|
568
|
Chris@0
|
569 if ($path[0] !== '/') {
|
Chris@0
|
570 // Relative path
|
Chris@0
|
571 return $path;
|
Chris@0
|
572 }
|
Chris@0
|
573
|
Chris@0
|
574 // Ensure only one leading slash, to prevent XSS attempts.
|
Chris@0
|
575 return '/' . ltrim($path, '/');
|
Chris@0
|
576 }
|
Chris@0
|
577
|
Chris@0
|
578 /**
|
Chris@0
|
579 * Filter a query string to ensure it is propertly encoded.
|
Chris@0
|
580 *
|
Chris@0
|
581 * Ensures that the values in the query string are properly urlencoded.
|
Chris@0
|
582 *
|
Chris@0
|
583 * @param string $query
|
Chris@0
|
584 * @return string
|
Chris@0
|
585 */
|
Chris@0
|
586 private function filterQuery($query)
|
Chris@0
|
587 {
|
Chris@0
|
588 if (! empty($query) && strpos($query, '?') === 0) {
|
Chris@0
|
589 $query = substr($query, 1);
|
Chris@0
|
590 }
|
Chris@0
|
591
|
Chris@0
|
592 $parts = explode('&', $query);
|
Chris@0
|
593 foreach ($parts as $index => $part) {
|
Chris@0
|
594 list($key, $value) = $this->splitQueryValue($part);
|
Chris@0
|
595 if ($value === null) {
|
Chris@0
|
596 $parts[$index] = $this->filterQueryOrFragment($key);
|
Chris@0
|
597 continue;
|
Chris@0
|
598 }
|
Chris@0
|
599 $parts[$index] = sprintf(
|
Chris@0
|
600 '%s=%s',
|
Chris@0
|
601 $this->filterQueryOrFragment($key),
|
Chris@0
|
602 $this->filterQueryOrFragment($value)
|
Chris@0
|
603 );
|
Chris@0
|
604 }
|
Chris@0
|
605
|
Chris@0
|
606 return implode('&', $parts);
|
Chris@0
|
607 }
|
Chris@0
|
608
|
Chris@0
|
609 /**
|
Chris@0
|
610 * Split a query value into a key/value tuple.
|
Chris@0
|
611 *
|
Chris@0
|
612 * @param string $value
|
Chris@0
|
613 * @return array A value with exactly two elements, key and value
|
Chris@0
|
614 */
|
Chris@0
|
615 private function splitQueryValue($value)
|
Chris@0
|
616 {
|
Chris@0
|
617 $data = explode('=', $value, 2);
|
Chris@0
|
618 if (1 === count($data)) {
|
Chris@0
|
619 $data[] = null;
|
Chris@0
|
620 }
|
Chris@0
|
621 return $data;
|
Chris@0
|
622 }
|
Chris@0
|
623
|
Chris@0
|
624 /**
|
Chris@0
|
625 * Filter a fragment value to ensure it is properly encoded.
|
Chris@0
|
626 *
|
Chris@0
|
627 * @param null|string $fragment
|
Chris@0
|
628 * @return string
|
Chris@0
|
629 */
|
Chris@0
|
630 private function filterFragment($fragment)
|
Chris@0
|
631 {
|
Chris@0
|
632 if (! empty($fragment) && strpos($fragment, '#') === 0) {
|
Chris@0
|
633 $fragment = '%23' . substr($fragment, 1);
|
Chris@0
|
634 }
|
Chris@0
|
635
|
Chris@0
|
636 return $this->filterQueryOrFragment($fragment);
|
Chris@0
|
637 }
|
Chris@0
|
638
|
Chris@0
|
639 /**
|
Chris@0
|
640 * Filter a query string key or value, or a fragment.
|
Chris@0
|
641 *
|
Chris@0
|
642 * @param string $value
|
Chris@0
|
643 * @return string
|
Chris@0
|
644 */
|
Chris@0
|
645 private function filterQueryOrFragment($value)
|
Chris@0
|
646 {
|
Chris@0
|
647 return preg_replace_callback(
|
Chris@0
|
648 '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/u',
|
Chris@0
|
649 [$this, 'urlEncodeChar'],
|
Chris@0
|
650 $value
|
Chris@0
|
651 );
|
Chris@0
|
652 }
|
Chris@0
|
653
|
Chris@0
|
654 /**
|
Chris@0
|
655 * URL encode a character returned by a regex.
|
Chris@0
|
656 *
|
Chris@0
|
657 * @param array $matches
|
Chris@0
|
658 * @return string
|
Chris@0
|
659 */
|
Chris@0
|
660 private function urlEncodeChar(array $matches)
|
Chris@0
|
661 {
|
Chris@0
|
662 return rawurlencode($matches[0]);
|
Chris@0
|
663 }
|
Chris@0
|
664 }
|