[ Index ]

PHP Cross Reference of Joomla 4.2.2 documentation

title

Body

[close]

/libraries/vendor/laminas/laminas-diactoros/src/ -> Uri.php (source)

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


Generated: Wed Sep 7 05:41:13 2022 Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer