‪TYPO3CMS  ‪main
Uri.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
18 namespace ‪TYPO3\CMS\Core\Http;
19 
20 use Psr\Http\Message\UriInterface;
21 
29 class ‪Uri implements UriInterface
30 {
36  public const ‪SUBDELIMITER_CHARLIST = '!\$&\'\‍(\‍)\*\+,;=';
37 
43  public const ‪UNRESERVED_CHARLIST = 'a-zA-Z0-9_\-\.~';
44 
48  protected string ‪$scheme = '';
49 
53  protected array ‪$supportedSchemes = [
54  'http' => 80,
55  'https' => 443,
56  ];
57 
61  protected string ‪$authority = '';
62 
66  protected string ‪$userInfo = '';
67 
71  protected string ‪$host = '';
72 
76  protected ?int ‪$port = null;
77 
81  protected string ‪$path = '';
82 
86  protected string ‪$query = '';
87 
91  protected string ‪$fragment = '';
92 
97  public function ‪__construct(string $uri = '')
98  {
99  if (!empty($uri)) {
100  $this->‪parseUri($uri);
101  }
102  }
103 
108  protected function ‪parseUri(string $uri): void
109  {
110  $uriParts = parse_url($uri);
111 
112  if ($uriParts === false) {
113  throw new \InvalidArgumentException('The parsedUri "' . $uri . '" appears to be malformed', 1436717322);
114  }
115 
116  if (isset($uriParts['scheme'])) {
117  $this->scheme = $this->‪sanitizeScheme($uriParts['scheme']);
118  }
119  if (isset($uriParts['user'])) {
120  $this->userInfo = $uriParts['user'];
121  if (isset($uriParts['pass'])) {
122  $this->userInfo .= ':' . $uriParts['pass'];
123  }
124  }
125  if (isset($uriParts['host'])) {
126  $this->host = $uriParts['host'];
127  }
128  if (isset($uriParts['port'])) {
129  $this->port = (int)$uriParts['port'];
130  }
131  if (isset($uriParts['path'])) {
132  $this->path = $this->‪sanitizePath($uriParts['path']);
133  }
134  if (isset($uriParts['query'])) {
135  $this->query = $this->‪sanitizeQuery($uriParts['query']);
136  }
137  if (isset($uriParts['fragment'])) {
138  $this->fragment = $this->‪sanitizeFragment($uriParts['fragment']);
139  }
140  }
141 
156  public function ‪getScheme(): string
157  {
158  return ‪$this->scheme;
159  }
160 
179  public function ‪getAuthority(): string
180  {
181  if (empty($this->host)) {
182  return '';
183  }
184 
186  if (!empty($this->userInfo)) {
187  ‪$authority = $this->userInfo . '@' . ‪$authority;
188  }
189 
190  if ($this->‪isNonStandardPort($this->scheme, $this->host, $this->port)) {
191  ‪$authority .= ':' . ‪$this->port;
192  }
193 
194  return ‪$authority;
195  }
196 
212  public function ‪getUserInfo(): string
213  {
214  return ‪$this->userInfo;
215  }
216 
228  public function ‪getHost(): string
229  {
230  return ‪$this->host;
231  }
232 
248  public function ‪getPort(): ?int
249  {
250  return $this->‪isNonStandardPort($this->scheme, $this->host, $this->port) ? $this->port : null;
251  }
252 
278  public function ‪getPath(): string
279  {
280  return ‪$this->path;
281  }
282 
303  public function ‪getQuery(): string
304  {
305  return ‪$this->query;
306  }
307 
324  public function ‪getFragment(): string
325  {
326  return ‪$this->fragment;
327  }
328 
344  public function ‪withScheme(string ‪$scheme): UriInterface
345  {
346  ‪$scheme = $this->‪sanitizeScheme($scheme);
347  $clonedObject = clone $this;
348  $clonedObject->scheme = ‪$scheme;
349  return $clonedObject;
350  }
351 
367  public function ‪withUserInfo(string $user, ?string $password = null): UriInterface
368  {
369  ‪$userInfo = $user;
370  if (!empty($password)) {
371  ‪$userInfo .= ':' . $password;
372  }
373 
374  $clonedObject = clone $this;
375  $clonedObject->userInfo = ‪$userInfo;
376  return $clonedObject;
377  }
378 
391  public function ‪withHost(string ‪$host): UriInterface
392  {
393  $clonedObject = clone $this;
394  $clonedObject->host = ‪$host;
395  return $clonedObject;
396  }
397 
415  public function ‪withPort(?int ‪$port): UriInterface
416  {
417  if (‪$port !== null) {
418  if ($port < 1 || $port > 65535) {
419  throw new \InvalidArgumentException('Invalid port "' . ‪$port . '" specified, must be a valid TCP/UDP port.', 1436717326);
420  }
421  }
422 
423  $clonedObject = clone $this;
424  $clonedObject->port = ‪$port;
425  return $clonedObject;
426  }
427 
450  public function ‪withPath(string ‪$path): UriInterface
451  {
452  if (str_contains(‪$path, '?')) {
453  throw new \InvalidArgumentException('Invalid path provided. Must not contain a query string.', 1436717330);
454  }
455 
456  if (str_contains(‪$path, '#')) {
457  throw new \InvalidArgumentException('Invalid path provided; must not contain a URI fragment', 1436717332);
458  }
459 
460  ‪$path = $this->‪sanitizePath($path);
461  $clonedObject = clone $this;
462  $clonedObject->path = ‪$path;
463  return $clonedObject;
464  }
465 
481  public function ‪withQuery(string ‪$query): UriInterface
482  {
483  if (str_contains(‪$query, '#')) {
484  throw new \InvalidArgumentException('Query string must not include a URI fragment.', 1436717336);
485  }
486 
487  ‪$query = $this->‪sanitizeQuery($query);
488  $clonedObject = clone $this;
489  $clonedObject->query = ‪$query;
490  return $clonedObject;
491  }
492 
507  public function ‪withFragment(string ‪$fragment): UriInterface
508  {
509  ‪$fragment = $this->‪sanitizeFragment($fragment);
510  $clonedObject = clone $this;
511  $clonedObject->fragment = ‪$fragment;
512  return $clonedObject;
513  }
514 
537  public function ‪__toString(): string
538  {
539  $uri = '';
540 
541  if (!empty($this->scheme)) {
542  $uri .= $this->scheme . ':';
543  }
544 
545  ‪$authority = $this->‪getAuthority();
546  if (!empty(‪$authority)) {
547  $uri .= '//' . ‪$authority;
548  }
549 
550  ‪$path = $this->‪getPath();
551  if ($path !== '' && !str_starts_with(‪$path, '/')) {
552  ‪$path = '/' . ‪$path;
553  }
554  $uri .= ‪$path;
555 
556  if ($this->query) {
557  $uri .= '?' . ‪$this->query;
558  }
559  if ($this->fragment) {
560  $uri .= '#' . ‪$this->fragment;
561  }
562  return $uri;
563  }
564 
568  protected function ‪isNonStandardPort(string ‪$scheme, string ‪$host, ?int ‪$port): bool
569  {
570  if (empty(‪$scheme)) {
571  return empty(‪$host) || !empty(‪$port);
572  }
573  if (empty(‪$host) || empty(‪$port)) {
574  return false;
575  }
576  return !isset($this->supportedSchemes[‪$scheme]) || ‪$port !== $this->supportedSchemes[‪$scheme];
577  }
578 
586  protected function ‪sanitizeScheme(string ‪$scheme): string
587  {
588  ‪$scheme = strtolower(‪$scheme);
589  ‪$scheme = preg_replace('#:(//)?$#', '', ‪$scheme);
590 
591  if (empty(‪$scheme)) {
592  return '';
593  }
594 
595  if (!array_key_exists(‪$scheme, $this->supportedSchemes)) {
596  throw new \InvalidArgumentException('Unsupported scheme "' . ‪$scheme . '"; must be any empty string or in the set (' . implode(', ', array_keys($this->supportedSchemes)) . ')', 1436717338);
597  }
598 
599  return ‪$scheme;
600  }
601 
605  protected function ‪sanitizePath(string ‪$path): string
606  {
607  return preg_replace_callback(
608  '/(?:[^' . self::UNRESERVED_CHARLIST . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
609  static function ($matches) {
610  return rawurlencode($matches[0]);
611  },
612  ‪$path
613  );
614  }
615 
620  protected function ‪sanitizeQuery(string ‪$query): string
621  {
622  if (!empty(‪$query) && str_starts_with(‪$query, '?')) {
623  ‪$query = substr(‪$query, 1);
624  }
625 
626  $parts = explode('&', ‪$query);
627  foreach ($parts as $index => $part) {
628  [$key, $value] = $this->‪splitQueryValue($part);
629  if ($value === null) {
630  $parts[$index] = $this->‪sanitizeQueryOrFragment($key);
631  continue;
632  }
633  $parts[$index] = $this->‪sanitizeQueryOrFragment($key) . '=' . $this->‪sanitizeQueryOrFragment($value);
634  }
635 
636  return implode('&', $parts);
637  }
638 
644  protected function ‪splitQueryValue(string $value): array
645  {
646  $data = explode('=', $value, 2);
647  if (count($data) === 1) {
648  $data[] = null;
649  }
650  return $data;
651  }
652 
656  protected function ‪sanitizeFragment(string ‪$fragment): string
657  {
658  if (!empty(‪$fragment) && str_starts_with(‪$fragment, '#')) {
659  ‪$fragment = substr(‪$fragment, 1);
660  }
661  return $this->‪sanitizeQueryOrFragment($fragment);
662  }
663 
667  protected function ‪sanitizeQueryOrFragment(string $value): string
668  {
669  return preg_replace_callback(
670  '/(?:[^' . self::UNRESERVED_CHARLIST . self::SUBDELIMITER_CHARLIST . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
671  static function ($matches) {
672  return rawurlencode($matches[0]);
673  },
674  $value
675  );
676  }
677 }
‪TYPO3\CMS\Core\Http\Uri\withScheme
‪static withScheme(string $scheme)
Definition: Uri.php:344
‪TYPO3\CMS\Core\Http\Uri\$supportedSchemes
‪array $supportedSchemes
Definition: Uri.php:53
‪TYPO3\CMS\Core\Http\Uri\$fragment
‪string $fragment
Definition: Uri.php:91
‪TYPO3\CMS\Core\Http\Uri\sanitizeQuery
‪sanitizeQuery(string $query)
Definition: Uri.php:620
‪TYPO3\CMS\Core\Http\Uri\__construct
‪__construct(string $uri='')
Definition: Uri.php:97
‪TYPO3\CMS\Core\Http\Uri\withQuery
‪static withQuery(string $query)
Definition: Uri.php:481
‪TYPO3\CMS\Core\Http\Uri\withUserInfo
‪static withUserInfo(string $user, ?string $password=null)
Definition: Uri.php:367
‪TYPO3\CMS\Core\Http\Uri\$host
‪string $host
Definition: Uri.php:71
‪TYPO3\CMS\Core\Http\Uri\getPath
‪string getPath()
Definition: Uri.php:278
‪TYPO3\CMS\Core\Http\Uri\sanitizePath
‪sanitizePath(string $path)
Definition: Uri.php:605
‪TYPO3\CMS\Core\Http\Uri\getPort
‪int null getPort()
Definition: Uri.php:248
‪TYPO3\CMS\Core\Http\Uri\getUserInfo
‪string getUserInfo()
Definition: Uri.php:212
‪TYPO3\CMS\Core\Http\Uri\$path
‪string $path
Definition: Uri.php:81
‪TYPO3\CMS\Core\Http\Uri\parseUri
‪parseUri(string $uri)
Definition: Uri.php:108
‪TYPO3\CMS\Core\Http\Uri\withHost
‪static withHost(string $host)
Definition: Uri.php:391
‪TYPO3\CMS\Core\Http\Uri\getAuthority
‪string getAuthority()
Definition: Uri.php:179
‪TYPO3\CMS\Core\Http\Uri\$query
‪string $query
Definition: Uri.php:86
‪TYPO3\CMS\Core\Http\Uri\__toString
‪__toString()
Definition: Uri.php:537
‪TYPO3\CMS\Core\Http\Uri\getQuery
‪string getQuery()
Definition: Uri.php:303
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:30
‪TYPO3\CMS\Core\Http\Uri\withPort
‪static withPort(?int $port)
Definition: Uri.php:415
‪TYPO3\CMS\Core\Http\Uri\SUBDELIMITER_CHARLIST
‪const SUBDELIMITER_CHARLIST
Definition: Uri.php:36
‪TYPO3\CMS\Core\Http\Uri\withPath
‪static withPath(string $path)
Definition: Uri.php:450
‪TYPO3\CMS\Core\Http\Uri\sanitizeScheme
‪string sanitizeScheme(string $scheme)
Definition: Uri.php:586
‪TYPO3\CMS\Core\Http\Uri\withFragment
‪static withFragment(string $fragment)
Definition: Uri.php:507
‪TYPO3\CMS\Core\Http\Uri\$authority
‪string $authority
Definition: Uri.php:61
‪TYPO3\CMS\Core\Http\Uri\$userInfo
‪string $userInfo
Definition: Uri.php:66
‪TYPO3\CMS\Core\Http\Uri\splitQueryValue
‪array splitQueryValue(string $value)
Definition: Uri.php:644
‪TYPO3\CMS\Core\Http\Uri\isNonStandardPort
‪isNonStandardPort(string $scheme, string $host, ?int $port)
Definition: Uri.php:568
‪TYPO3\CMS\Core\Http\Uri\getFragment
‪string getFragment()
Definition: Uri.php:324
‪TYPO3\CMS\Core\Http\Uri\UNRESERVED_CHARLIST
‪const UNRESERVED_CHARLIST
Definition: Uri.php:43
‪TYPO3\CMS\Core\Http\Uri\sanitizeFragment
‪sanitizeFragment(string $fragment)
Definition: Uri.php:656
‪TYPO3\CMS\Core\Http\Uri\$port
‪int $port
Definition: Uri.php:76
‪TYPO3\CMS\Core\Http\Uri\getHost
‪string getHost()
Definition: Uri.php:228
‪TYPO3\CMS\Core\Http\Uri\getScheme
‪string getScheme()
Definition: Uri.php:156
‪TYPO3\CMS\Core\Http\Uri\sanitizeQueryOrFragment
‪sanitizeQueryOrFragment(string $value)
Definition: Uri.php:667
‪TYPO3\CMS\Core\Http\Uri\$scheme
‪string $scheme
Definition: Uri.php:48
‪TYPO3\CMS\Core\Http
Definition: AbstractApplication.php:18