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