‪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  'ws' => 80,
57  'wss' => 443,
58  ];
59 
63  protected string ‪$authority = '';
64 
68  protected string ‪$userInfo = '';
69 
73  protected string ‪$host = '';
74 
78  protected ?int ‪$port = null;
79 
83  protected string ‪$path = '';
84 
88  protected string ‪$query = '';
89 
93  protected string ‪$fragment = '';
94 
99  public function ‪__construct(string $uri = '')
100  {
101  if (!empty($uri)) {
102  $this->‪parseUri($uri);
103  }
104  }
105 
110  protected function ‪parseUri(string $uri): void
111  {
112  $uriParts = parse_url($uri);
113 
114  if ($uriParts === false) {
115  throw new \InvalidArgumentException('The parsedUri "' . $uri . '" appears to be malformed', 1436717322);
116  }
117 
118  if (isset($uriParts['scheme'])) {
119  $this->scheme = $this->‪sanitizeScheme($uriParts['scheme']);
120  }
121  if (isset($uriParts['user'])) {
122  $this->userInfo = $uriParts['user'];
123  if (isset($uriParts['pass'])) {
124  $this->userInfo .= ':' . $uriParts['pass'];
125  }
126  }
127  if (isset($uriParts['host'])) {
128  $this->host = $uriParts['host'];
129  }
130  if (isset($uriParts['port'])) {
131  $this->port = (int)$uriParts['port'];
132  }
133  if (isset($uriParts['path'])) {
134  $this->path = $this->‪sanitizePath($uriParts['path']);
135  }
136  if (isset($uriParts['query'])) {
137  $this->query = $this->‪sanitizeQuery($uriParts['query']);
138  }
139  if (isset($uriParts['fragment'])) {
140  $this->fragment = $this->‪sanitizeFragment($uriParts['fragment']);
141  }
142  }
143 
158  public function ‪getScheme(): string
159  {
160  return ‪$this->scheme;
161  }
162 
181  public function ‪getAuthority(): string
182  {
183  if (empty($this->host)) {
184  return '';
185  }
186 
188  if (!empty($this->userInfo)) {
189  ‪$authority = $this->userInfo . '@' . ‪$authority;
190  }
191 
192  if ($this->‪isNonStandardPort($this->scheme, $this->host, $this->port)) {
193  ‪$authority .= ':' . ‪$this->port;
194  }
195 
196  return ‪$authority;
197  }
198 
214  public function ‪getUserInfo(): string
215  {
216  return ‪$this->userInfo;
217  }
218 
230  public function ‪getHost(): string
231  {
232  return ‪$this->host;
233  }
234 
250  public function ‪getPort(): ?int
251  {
252  return $this->‪isNonStandardPort($this->scheme, $this->host, $this->port) ? $this->port : null;
253  }
254 
280  public function ‪getPath(): string
281  {
282  return ‪$this->path;
283  }
284 
305  public function ‪getQuery(): string
306  {
307  return ‪$this->query;
308  }
309 
326  public function ‪getFragment(): string
327  {
328  return ‪$this->fragment;
329  }
330 
346  public function ‪withScheme(string ‪$scheme): UriInterface
347  {
348  ‪$scheme = $this->‪sanitizeScheme($scheme);
349  $clonedObject = clone $this;
350  $clonedObject->scheme = ‪$scheme;
351  return $clonedObject;
352  }
353 
369  public function ‪withUserInfo(string $user, ?string $password = null): UriInterface
370  {
371  ‪$userInfo = $user;
372  if (!empty($password)) {
373  ‪$userInfo .= ':' . $password;
374  }
375 
376  $clonedObject = clone $this;
377  $clonedObject->userInfo = ‪$userInfo;
378  return $clonedObject;
379  }
380 
393  public function ‪withHost(string ‪$host): UriInterface
394  {
395  $clonedObject = clone $this;
396  $clonedObject->host = ‪$host;
397  return $clonedObject;
398  }
399 
417  public function ‪withPort(?int ‪$port): UriInterface
418  {
419  if (‪$port !== null) {
420  if ($port < 1 || $port > 65535) {
421  throw new \InvalidArgumentException('Invalid port "' . ‪$port . '" specified, must be a valid TCP/UDP port.', 1436717326);
422  }
423  }
424 
425  $clonedObject = clone $this;
426  $clonedObject->port = ‪$port;
427  return $clonedObject;
428  }
429 
452  public function ‪withPath(string ‪$path): UriInterface
453  {
454  if (str_contains(‪$path, '?')) {
455  throw new \InvalidArgumentException('Invalid path provided. Must not contain a query string.', 1436717330);
456  }
457 
458  if (str_contains(‪$path, '#')) {
459  throw new \InvalidArgumentException('Invalid path provided; must not contain a URI fragment', 1436717332);
460  }
461 
462  ‪$path = $this->‪sanitizePath($path);
463  $clonedObject = clone $this;
464  $clonedObject->path = ‪$path;
465  return $clonedObject;
466  }
467 
483  public function ‪withQuery(string ‪$query): UriInterface
484  {
485  if (str_contains(‪$query, '#')) {
486  throw new \InvalidArgumentException('Query string must not include a URI fragment.', 1436717336);
487  }
488 
489  ‪$query = $this->‪sanitizeQuery($query);
490  $clonedObject = clone $this;
491  $clonedObject->query = ‪$query;
492  return $clonedObject;
493  }
494 
509  public function ‪withFragment(string ‪$fragment): UriInterface
510  {
511  ‪$fragment = $this->‪sanitizeFragment($fragment);
512  $clonedObject = clone $this;
513  $clonedObject->fragment = ‪$fragment;
514  return $clonedObject;
515  }
516 
539  public function ‪__toString(): string
540  {
541  $uri = '';
542 
543  if (!empty($this->scheme)) {
544  $uri .= $this->scheme . ':';
545  }
546 
547  ‪$authority = $this->‪getAuthority();
548  if (!empty(‪$authority)) {
549  $uri .= '//' . ‪$authority;
550  }
551 
552  ‪$path = $this->‪getPath();
553  if ($path !== '' && !str_starts_with(‪$path, '/')) {
554  ‪$path = '/' . ‪$path;
555  }
556  $uri .= ‪$path;
557 
558  if ($this->query) {
559  $uri .= '?' . ‪$this->query;
560  }
561  if ($this->fragment) {
562  $uri .= '#' . ‪$this->fragment;
563  }
564  return $uri;
565  }
566 
570  protected function ‪isNonStandardPort(string ‪$scheme, string ‪$host, ?int ‪$port): bool
571  {
572  if (empty(‪$scheme)) {
573  return empty(‪$host) || !empty(‪$port);
574  }
575  if (empty(‪$host) || empty(‪$port)) {
576  return false;
577  }
578  return !isset($this->supportedSchemes[‪$scheme]) || ‪$port !== $this->supportedSchemes[‪$scheme];
579  }
580 
588  protected function ‪sanitizeScheme(string ‪$scheme): string
589  {
590  ‪$scheme = strtolower(‪$scheme);
591  ‪$scheme = preg_replace('#:(//)?$#', '', ‪$scheme);
592 
593  if (empty(‪$scheme)) {
594  return '';
595  }
596 
597  if (!array_key_exists(‪$scheme, $this->supportedSchemes)) {
598  throw new \InvalidArgumentException('Unsupported scheme "' . ‪$scheme . '"; must be any empty string or in the set (' . implode(', ', array_keys($this->supportedSchemes)) . ')', 1436717338);
599  }
600 
601  return ‪$scheme;
602  }
603 
607  protected function ‪sanitizePath(string ‪$path): string
608  {
609  return preg_replace_callback(
610  '/(?:[^' . self::UNRESERVED_CHARLIST . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/',
611  static function ($matches) {
612  return rawurlencode($matches[0]);
613  },
614  ‪$path
615  );
616  }
617 
622  protected function ‪sanitizeQuery(string ‪$query): string
623  {
624  if (!empty(‪$query) && str_starts_with(‪$query, '?')) {
625  ‪$query = substr(‪$query, 1);
626  }
627 
628  $parts = explode('&', ‪$query);
629  foreach ($parts as $index => $part) {
630  [$key, $value] = $this->‪splitQueryValue($part);
631  if ($value === null) {
632  $parts[$index] = $this->‪sanitizeQueryOrFragment($key);
633  continue;
634  }
635  $parts[$index] = $this->‪sanitizeQueryOrFragment($key) . '=' . $this->‪sanitizeQueryOrFragment($value);
636  }
637 
638  return implode('&', $parts);
639  }
640 
646  protected function ‪splitQueryValue(string $value): array
647  {
648  $data = explode('=', $value, 2);
649  if (count($data) === 1) {
650  $data[] = null;
651  }
652  return $data;
653  }
654 
658  protected function ‪sanitizeFragment(string ‪$fragment): string
659  {
660  if (!empty(‪$fragment) && str_starts_with(‪$fragment, '#')) {
661  ‪$fragment = substr(‪$fragment, 1);
662  }
663  return $this->‪sanitizeQueryOrFragment($fragment);
664  }
665 
669  protected function ‪sanitizeQueryOrFragment(string $value): string
670  {
671  return preg_replace_callback(
672  '/(?:[^' . self::UNRESERVED_CHARLIST . self::SUBDELIMITER_CHARLIST . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/',
673  static function ($matches) {
674  return rawurlencode($matches[0]);
675  },
676  $value
677  );
678  }
679 }
‪TYPO3\CMS\Core\Http\Uri\withScheme
‪static withScheme(string $scheme)
Definition: Uri.php:346
‪TYPO3\CMS\Core\Http\Uri\$supportedSchemes
‪array $supportedSchemes
Definition: Uri.php:53
‪TYPO3\CMS\Core\Http\Uri\$fragment
‪string $fragment
Definition: Uri.php:93
‪TYPO3\CMS\Core\Http\Uri\sanitizeQuery
‪sanitizeQuery(string $query)
Definition: Uri.php:622
‪TYPO3\CMS\Core\Http\Uri\__construct
‪__construct(string $uri='')
Definition: Uri.php:99
‪TYPO3\CMS\Core\Http\Uri\withQuery
‪static withQuery(string $query)
Definition: Uri.php:483
‪TYPO3\CMS\Core\Http\Uri\withUserInfo
‪static withUserInfo(string $user, ?string $password=null)
Definition: Uri.php:369
‪TYPO3\CMS\Core\Http\Uri\$host
‪string $host
Definition: Uri.php:73
‪TYPO3\CMS\Core\Http\Uri\getPath
‪string getPath()
Definition: Uri.php:280
‪TYPO3\CMS\Core\Http\Uri\sanitizePath
‪sanitizePath(string $path)
Definition: Uri.php:607
‪TYPO3\CMS\Core\Http\Uri\getPort
‪int null getPort()
Definition: Uri.php:250
‪TYPO3\CMS\Core\Http\Uri\getUserInfo
‪string getUserInfo()
Definition: Uri.php:214
‪TYPO3\CMS\Core\Http\Uri\$path
‪string $path
Definition: Uri.php:83
‪TYPO3\CMS\Core\Http\Uri\parseUri
‪parseUri(string $uri)
Definition: Uri.php:110
‪TYPO3\CMS\Core\Http\Uri\withHost
‪static withHost(string $host)
Definition: Uri.php:393
‪TYPO3\CMS\Core\Http\Uri\getAuthority
‪string getAuthority()
Definition: Uri.php:181
‪TYPO3\CMS\Core\Http\Uri\$query
‪string $query
Definition: Uri.php:88
‪TYPO3\CMS\Core\Http\Uri\__toString
‪__toString()
Definition: Uri.php:539
‪TYPO3\CMS\Core\Http\Uri\getQuery
‪string getQuery()
Definition: Uri.php:305
‪TYPO3\CMS\Core\Http\Uri
Definition: Uri.php:30
‪TYPO3\CMS\Core\Http\Uri\withPort
‪static withPort(?int $port)
Definition: Uri.php:417
‪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:452
‪TYPO3\CMS\Core\Http\Uri\sanitizeScheme
‪string sanitizeScheme(string $scheme)
Definition: Uri.php:588
‪TYPO3\CMS\Core\Http\Uri\withFragment
‪static withFragment(string $fragment)
Definition: Uri.php:509
‪TYPO3\CMS\Core\Http\Uri\$authority
‪string $authority
Definition: Uri.php:63
‪TYPO3\CMS\Core\Http\Uri\$userInfo
‪string $userInfo
Definition: Uri.php:68
‪TYPO3\CMS\Core\Http\Uri\splitQueryValue
‪array splitQueryValue(string $value)
Definition: Uri.php:646
‪TYPO3\CMS\Core\Http\Uri\isNonStandardPort
‪isNonStandardPort(string $scheme, string $host, ?int $port)
Definition: Uri.php:570
‪TYPO3\CMS\Core\Http\Uri\getFragment
‪string getFragment()
Definition: Uri.php:326
‪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:658
‪TYPO3\CMS\Core\Http\Uri\$port
‪int $port
Definition: Uri.php:78
‪TYPO3\CMS\Core\Http\Uri\getHost
‪string getHost()
Definition: Uri.php:230
‪TYPO3\CMS\Core\Http\Uri\getScheme
‪string getScheme()
Definition: Uri.php:158
‪TYPO3\CMS\Core\Http\Uri\sanitizeQueryOrFragment
‪sanitizeQueryOrFragment(string $value)
Definition: Uri.php:669
‪TYPO3\CMS\Core\Http\Uri\$scheme
‪string $scheme
Definition: Uri.php:48
‪TYPO3\CMS\Core\Http
Definition: AbstractApplication.php:18