[ Index ]

PHP Cross Reference of phpBB-3.3.11-deutsch

title

Body

[close]

/vendor/guzzlehttp/psr7/src/ -> Uri.php (source)

   1  <?php
   2  
   3  namespace GuzzleHttp\Psr7;
   4  
   5  use Psr\Http\Message\UriInterface;
   6  
   7  /**
   8   * PSR-7 URI implementation.
   9   *
  10   * @author Michael Dowling
  11   * @author Tobias Schultze
  12   * @author Matthew Weier O'Phinney
  13   */
  14  class Uri implements UriInterface
  15  {
  16      /**
  17       * Absolute http and https URIs require a host per RFC 7230 Section 2.7
  18       * but in generic URIs the host can be empty. So for http(s) URIs
  19       * we apply this default host when no host is given yet to form a
  20       * valid URI.
  21       */
  22      const HTTP_DEFAULT_HOST = 'localhost';
  23  
  24      private static $defaultPorts = [
  25          'http'  => 80,
  26          'https' => 443,
  27          'ftp' => 21,
  28          'gopher' => 70,
  29          'nntp' => 119,
  30          'news' => 119,
  31          'telnet' => 23,
  32          'tn3270' => 23,
  33          'imap' => 143,
  34          'pop' => 110,
  35          'ldap' => 389,
  36      ];
  37  
  38      private static $charUnreserved = 'a-zA-Z0-9_\-\.~';
  39      private static $charSubDelims = '!\$&\'\(\)\*\+,;=';
  40      private static $replaceQuery = ['=' => '%3D', '&' => '%26'];
  41  
  42      /** @var string Uri scheme. */
  43      private $scheme = '';
  44  
  45      /** @var string Uri user info. */
  46      private $userInfo = '';
  47  
  48      /** @var string Uri host. */
  49      private $host = '';
  50  
  51      /** @var int|null Uri port. */
  52      private $port;
  53  
  54      /** @var string Uri path. */
  55      private $path = '';
  56  
  57      /** @var string Uri query string. */
  58      private $query = '';
  59  
  60      /** @var string Uri fragment. */
  61      private $fragment = '';
  62  
  63      /**
  64       * @param string $uri URI to parse
  65       */
  66      public function __construct($uri = '')
  67      {
  68          // weak type check to also accept null until we can add scalar type hints
  69          if ($uri != '') {
  70              $parts = self::parse($uri);
  71              if ($parts === false) {
  72                  throw new \InvalidArgumentException("Unable to parse URI: $uri");
  73              }
  74              $this->applyParts($parts);
  75          }
  76      }
  77  
  78      /**
  79       * UTF-8 aware \parse_url() replacement.
  80       *
  81       * The internal function produces broken output for non ASCII domain names
  82       * (IDN) when used with locales other than "C".
  83       *
  84       * On the other hand, cURL understands IDN correctly only when UTF-8 locale
  85       * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
  86       *
  87       * @see https://bugs.php.net/bug.php?id=52923
  88       * @see https://www.php.net/manual/en/function.parse-url.php#114817
  89       * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
  90       *
  91       * @param string $url
  92       *
  93       * @return array|false
  94       */
  95      private static function parse($url)
  96      {
  97          // If IPv6
  98          $prefix = '';
  99          if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) {
 100              $prefix = $matches[1];
 101              $url = $matches[2];
 102          }
 103  
 104          $encodedUrl = preg_replace_callback(
 105              '%[^:/@?&=#]+%usD',
 106              static function ($matches) {
 107                  return urlencode($matches[0]);
 108              },
 109              $url
 110          );
 111  
 112          $result = parse_url($prefix . $encodedUrl);
 113  
 114          if ($result === false) {
 115              return false;
 116          }
 117  
 118          return array_map('urldecode', $result);
 119      }
 120  
 121      public function __toString()
 122      {
 123          return self::composeComponents(
 124              $this->scheme,
 125              $this->getAuthority(),
 126              $this->path,
 127              $this->query,
 128              $this->fragment
 129          );
 130      }
 131  
 132      /**
 133       * Composes a URI reference string from its various components.
 134       *
 135       * Usually this method does not need to be called manually but instead is used indirectly via
 136       * `Psr\Http\Message\UriInterface::__toString`.
 137       *
 138       * PSR-7 UriInterface treats an empty component the same as a missing component as
 139       * getQuery(), getFragment() etc. always return a string. This explains the slight
 140       * difference to RFC 3986 Section 5.3.
 141       *
 142       * Another adjustment is that the authority separator is added even when the authority is missing/empty
 143       * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
 144       * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
 145       * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
 146       * that format).
 147       *
 148       * @param string $scheme
 149       * @param string $authority
 150       * @param string $path
 151       * @param string $query
 152       * @param string $fragment
 153       *
 154       * @return string
 155       *
 156       * @link https://tools.ietf.org/html/rfc3986#section-5.3
 157       */
 158      public static function composeComponents($scheme, $authority, $path, $query, $fragment)
 159      {
 160          $uri = '';
 161  
 162          // weak type checks to also accept null until we can add scalar type hints
 163          if ($scheme != '') {
 164              $uri .= $scheme . ':';
 165          }
 166  
 167          if ($authority != ''|| $scheme === 'file') {
 168              $uri .= '//' . $authority;
 169          }
 170  
 171          $uri .= $path;
 172  
 173          if ($query != '') {
 174              $uri .= '?' . $query;
 175          }
 176  
 177          if ($fragment != '') {
 178              $uri .= '#' . $fragment;
 179          }
 180  
 181          return $uri;
 182      }
 183  
 184      /**
 185       * Whether the URI has the default port of the current scheme.
 186       *
 187       * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
 188       * independently of the implementation.
 189       *
 190       * @param UriInterface $uri
 191       *
 192       * @return bool
 193       */
 194      public static function isDefaultPort(UriInterface $uri)
 195      {
 196          return $uri->getPort() === null
 197              || (isset(self::$defaultPorts[$uri->getScheme()]) && $uri->getPort() === self::$defaultPorts[$uri->getScheme()]);
 198      }
 199  
 200      /**
 201       * Whether the URI is absolute, i.e. it has a scheme.
 202       *
 203       * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
 204       * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
 205       * to another URI, the base URI. Relative references can be divided into several forms:
 206       * - network-path references, e.g. '//example.com/path'
 207       * - absolute-path references, e.g. '/path'
 208       * - relative-path references, e.g. 'subpath'
 209       *
 210       * @param UriInterface $uri
 211       *
 212       * @return bool
 213       *
 214       * @see Uri::isNetworkPathReference
 215       * @see Uri::isAbsolutePathReference
 216       * @see Uri::isRelativePathReference
 217       * @link https://tools.ietf.org/html/rfc3986#section-4
 218       */
 219      public static function isAbsolute(UriInterface $uri)
 220      {
 221          return $uri->getScheme() !== '';
 222      }
 223  
 224      /**
 225       * Whether the URI is a network-path reference.
 226       *
 227       * A relative reference that begins with two slash characters is termed an network-path reference.
 228       *
 229       * @param UriInterface $uri
 230       *
 231       * @return bool
 232       *
 233       * @link https://tools.ietf.org/html/rfc3986#section-4.2
 234       */
 235      public static function isNetworkPathReference(UriInterface $uri)
 236      {
 237          return $uri->getScheme() === '' && $uri->getAuthority() !== '';
 238      }
 239  
 240      /**
 241       * Whether the URI is a absolute-path reference.
 242       *
 243       * A relative reference that begins with a single slash character is termed an absolute-path reference.
 244       *
 245       * @param UriInterface $uri
 246       *
 247       * @return bool
 248       *
 249       * @link https://tools.ietf.org/html/rfc3986#section-4.2
 250       */
 251      public static function isAbsolutePathReference(UriInterface $uri)
 252      {
 253          return $uri->getScheme() === ''
 254              && $uri->getAuthority() === ''
 255              && isset($uri->getPath()[0])
 256              && $uri->getPath()[0] === '/';
 257      }
 258  
 259      /**
 260       * Whether the URI is a relative-path reference.
 261       *
 262       * A relative reference that does not begin with a slash character is termed a relative-path reference.
 263       *
 264       * @param UriInterface $uri
 265       *
 266       * @return bool
 267       *
 268       * @link https://tools.ietf.org/html/rfc3986#section-4.2
 269       */
 270      public static function isRelativePathReference(UriInterface $uri)
 271      {
 272          return $uri->getScheme() === ''
 273              && $uri->getAuthority() === ''
 274              && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
 275      }
 276  
 277      /**
 278       * Whether the URI is a same-document reference.
 279       *
 280       * A same-document reference refers to a URI that is, aside from its fragment
 281       * component, identical to the base URI. When no base URI is given, only an empty
 282       * URI reference (apart from its fragment) is considered a same-document reference.
 283       *
 284       * @param UriInterface      $uri  The URI to check
 285       * @param UriInterface|null $base An optional base URI to compare against
 286       *
 287       * @return bool
 288       *
 289       * @link https://tools.ietf.org/html/rfc3986#section-4.4
 290       */
 291      public static function isSameDocumentReference(UriInterface $uri, UriInterface $base = null)
 292      {
 293          if ($base !== null) {
 294              $uri = UriResolver::resolve($base, $uri);
 295  
 296              return ($uri->getScheme() === $base->getScheme())
 297                  && ($uri->getAuthority() === $base->getAuthority())
 298                  && ($uri->getPath() === $base->getPath())
 299                  && ($uri->getQuery() === $base->getQuery());
 300          }
 301  
 302          return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
 303      }
 304  
 305      /**
 306       * Removes dot segments from a path and returns the new path.
 307       *
 308       * @param string $path
 309       *
 310       * @return string
 311       *
 312       * @deprecated since version 1.4. Use UriResolver::removeDotSegments instead.
 313       * @see UriResolver::removeDotSegments
 314       */
 315      public static function removeDotSegments($path)
 316      {
 317          return UriResolver::removeDotSegments($path);
 318      }
 319  
 320      /**
 321       * Converts the relative URI into a new URI that is resolved against the base URI.
 322       *
 323       * @param UriInterface        $base Base URI
 324       * @param string|UriInterface $rel  Relative URI
 325       *
 326       * @return UriInterface
 327       *
 328       * @deprecated since version 1.4. Use UriResolver::resolve instead.
 329       * @see UriResolver::resolve
 330       */
 331      public static function resolve(UriInterface $base, $rel)
 332      {
 333          if (!($rel instanceof UriInterface)) {
 334              $rel = new self($rel);
 335          }
 336  
 337          return UriResolver::resolve($base, $rel);
 338      }
 339  
 340      /**
 341       * Creates a new URI with a specific query string value removed.
 342       *
 343       * Any existing query string values that exactly match the provided key are
 344       * removed.
 345       *
 346       * @param UriInterface $uri URI to use as a base.
 347       * @param string       $key Query string key to remove.
 348       *
 349       * @return UriInterface
 350       */
 351      public static function withoutQueryValue(UriInterface $uri, $key)
 352      {
 353          $result = self::getFilteredQueryString($uri, [$key]);
 354  
 355          return $uri->withQuery(implode('&', $result));
 356      }
 357  
 358      /**
 359       * Creates a new URI with a specific query string value.
 360       *
 361       * Any existing query string values that exactly match the provided key are
 362       * removed and replaced with the given key value pair.
 363       *
 364       * A value of null will set the query string key without a value, e.g. "key"
 365       * instead of "key=value".
 366       *
 367       * @param UriInterface $uri   URI to use as a base.
 368       * @param string       $key   Key to set.
 369       * @param string|null  $value Value to set
 370       *
 371       * @return UriInterface
 372       */
 373      public static function withQueryValue(UriInterface $uri, $key, $value)
 374      {
 375          $result = self::getFilteredQueryString($uri, [$key]);
 376  
 377          $result[] = self::generateQueryString($key, $value);
 378  
 379          return $uri->withQuery(implode('&', $result));
 380      }
 381  
 382      /**
 383       * Creates a new URI with multiple specific query string values.
 384       *
 385       * It has the same behavior as withQueryValue() but for an associative array of key => value.
 386       *
 387       * @param UriInterface $uri           URI to use as a base.
 388       * @param array        $keyValueArray Associative array of key and values
 389       *
 390       * @return UriInterface
 391       */
 392      public static function withQueryValues(UriInterface $uri, array $keyValueArray)
 393      {
 394          $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
 395  
 396          foreach ($keyValueArray as $key => $value) {
 397              $result[] = self::generateQueryString($key, $value);
 398          }
 399  
 400          return $uri->withQuery(implode('&', $result));
 401      }
 402  
 403      /**
 404       * Creates a URI from a hash of `parse_url` components.
 405       *
 406       * @param array $parts
 407       *
 408       * @return UriInterface
 409       *
 410       * @link http://php.net/manual/en/function.parse-url.php
 411       *
 412       * @throws \InvalidArgumentException If the components do not form a valid URI.
 413       */
 414      public static function fromParts(array $parts)
 415      {
 416          $uri = new self();
 417          $uri->applyParts($parts);
 418          $uri->validateState();
 419  
 420          return $uri;
 421      }
 422  
 423      public function getScheme()
 424      {
 425          return $this->scheme;
 426      }
 427  
 428      public function getAuthority()
 429      {
 430          $authority = $this->host;
 431          if ($this->userInfo !== '') {
 432              $authority = $this->userInfo . '@' . $authority;
 433          }
 434  
 435          if ($this->port !== null) {
 436              $authority .= ':' . $this->port;
 437          }
 438  
 439          return $authority;
 440      }
 441  
 442      public function getUserInfo()
 443      {
 444          return $this->userInfo;
 445      }
 446  
 447      public function getHost()
 448      {
 449          return $this->host;
 450      }
 451  
 452      public function getPort()
 453      {
 454          return $this->port;
 455      }
 456  
 457      public function getPath()
 458      {
 459          return $this->path;
 460      }
 461  
 462      public function getQuery()
 463      {
 464          return $this->query;
 465      }
 466  
 467      public function getFragment()
 468      {
 469          return $this->fragment;
 470      }
 471  
 472      public function withScheme($scheme)
 473      {
 474          $scheme = $this->filterScheme($scheme);
 475  
 476          if ($this->scheme === $scheme) {
 477              return $this;
 478          }
 479  
 480          $new = clone $this;
 481          $new->scheme = $scheme;
 482          $new->removeDefaultPort();
 483          $new->validateState();
 484  
 485          return $new;
 486      }
 487  
 488      public function withUserInfo($user, $password = null)
 489      {
 490          $info = $this->filterUserInfoComponent($user);
 491          if ($password !== null) {
 492              $info .= ':' . $this->filterUserInfoComponent($password);
 493          }
 494  
 495          if ($this->userInfo === $info) {
 496              return $this;
 497          }
 498  
 499          $new = clone $this;
 500          $new->userInfo = $info;
 501          $new->validateState();
 502  
 503          return $new;
 504      }
 505  
 506      public function withHost($host)
 507      {
 508          $host = $this->filterHost($host);
 509  
 510          if ($this->host === $host) {
 511              return $this;
 512          }
 513  
 514          $new = clone $this;
 515          $new->host = $host;
 516          $new->validateState();
 517  
 518          return $new;
 519      }
 520  
 521      public function withPort($port)
 522      {
 523          $port = $this->filterPort($port);
 524  
 525          if ($this->port === $port) {
 526              return $this;
 527          }
 528  
 529          $new = clone $this;
 530          $new->port = $port;
 531          $new->removeDefaultPort();
 532          $new->validateState();
 533  
 534          return $new;
 535      }
 536  
 537      public function withPath($path)
 538      {
 539          $path = $this->filterPath($path);
 540  
 541          if ($this->path === $path) {
 542              return $this;
 543          }
 544  
 545          $new = clone $this;
 546          $new->path = $path;
 547          $new->validateState();
 548  
 549          return $new;
 550      }
 551  
 552      public function withQuery($query)
 553      {
 554          $query = $this->filterQueryAndFragment($query);
 555  
 556          if ($this->query === $query) {
 557              return $this;
 558          }
 559  
 560          $new = clone $this;
 561          $new->query = $query;
 562  
 563          return $new;
 564      }
 565  
 566      public function withFragment($fragment)
 567      {
 568          $fragment = $this->filterQueryAndFragment($fragment);
 569  
 570          if ($this->fragment === $fragment) {
 571              return $this;
 572          }
 573  
 574          $new = clone $this;
 575          $new->fragment = $fragment;
 576  
 577          return $new;
 578      }
 579  
 580      /**
 581       * Apply parse_url parts to a URI.
 582       *
 583       * @param array $parts Array of parse_url parts to apply.
 584       */
 585      private function applyParts(array $parts)
 586      {
 587          $this->scheme = isset($parts['scheme'])
 588              ? $this->filterScheme($parts['scheme'])
 589              : '';
 590          $this->userInfo = isset($parts['user'])
 591              ? $this->filterUserInfoComponent($parts['user'])
 592              : '';
 593          $this->host = isset($parts['host'])
 594              ? $this->filterHost($parts['host'])
 595              : '';
 596          $this->port = isset($parts['port'])
 597              ? $this->filterPort($parts['port'])
 598              : null;
 599          $this->path = isset($parts['path'])
 600              ? $this->filterPath($parts['path'])
 601              : '';
 602          $this->query = isset($parts['query'])
 603              ? $this->filterQueryAndFragment($parts['query'])
 604              : '';
 605          $this->fragment = isset($parts['fragment'])
 606              ? $this->filterQueryAndFragment($parts['fragment'])
 607              : '';
 608          if (isset($parts['pass'])) {
 609              $this->userInfo .= ':' . $this->filterUserInfoComponent($parts['pass']);
 610          }
 611  
 612          $this->removeDefaultPort();
 613      }
 614  
 615      /**
 616       * @param string $scheme
 617       *
 618       * @return string
 619       *
 620       * @throws \InvalidArgumentException If the scheme is invalid.
 621       */
 622      private function filterScheme($scheme)
 623      {
 624          if (!is_string($scheme)) {
 625              throw new \InvalidArgumentException('Scheme must be a string');
 626          }
 627  
 628          return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
 629      }
 630  
 631      /**
 632       * @param string $component
 633       *
 634       * @return string
 635       *
 636       * @throws \InvalidArgumentException If the user info is invalid.
 637       */
 638      private function filterUserInfoComponent($component)
 639      {
 640          if (!is_string($component)) {
 641              throw new \InvalidArgumentException('User info must be a string');
 642          }
 643  
 644          return preg_replace_callback(
 645              '/(?:[^%' . self::$charUnreserved . self::$charSubDelims . ']+|%(?![A-Fa-f0-9]{2}))/',
 646              [$this, 'rawurlencodeMatchZero'],
 647              $component
 648          );
 649      }
 650  
 651      /**
 652       * @param string $host
 653       *
 654       * @return string
 655       *
 656       * @throws \InvalidArgumentException If the host is invalid.
 657       */
 658      private function filterHost($host)
 659      {
 660          if (!is_string($host)) {
 661              throw new \InvalidArgumentException('Host must be a string');
 662          }
 663  
 664          return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
 665      }
 666  
 667      /**
 668       * @param int|null $port
 669       *
 670       * @return int|null
 671       *
 672       * @throws \InvalidArgumentException If the port is invalid.
 673       */
 674      private function filterPort($port)
 675      {
 676          if ($port === null) {
 677              return null;
 678          }
 679  
 680          $port = (int) $port;
 681          if (0 > $port || 0xffff < $port) {
 682              throw new \InvalidArgumentException(
 683                  sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
 684              );
 685          }
 686  
 687          return $port;
 688      }
 689  
 690      /**
 691       * @param UriInterface $uri
 692       * @param array        $keys
 693       *
 694       * @return array
 695       */
 696      private static function getFilteredQueryString(UriInterface $uri, array $keys)
 697      {
 698          $current = $uri->getQuery();
 699  
 700          if ($current === '') {
 701              return [];
 702          }
 703  
 704          $decodedKeys = array_map('rawurldecode', $keys);
 705  
 706          return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
 707              return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
 708          });
 709      }
 710  
 711      /**
 712       * @param string      $key
 713       * @param string|null $value
 714       *
 715       * @return string
 716       */
 717      private static function generateQueryString($key, $value)
 718      {
 719          // Query string separators ("=", "&") within the key or value need to be encoded
 720          // (while preventing double-encoding) before setting the query string. All other
 721          // chars that need percent-encoding will be encoded by withQuery().
 722          $queryString = strtr($key, self::$replaceQuery);
 723  
 724          if ($value !== null) {
 725              $queryString .= '=' . strtr($value, self::$replaceQuery);
 726          }
 727  
 728          return $queryString;
 729      }
 730  
 731      private function removeDefaultPort()
 732      {
 733          if ($this->port !== null && self::isDefaultPort($this)) {
 734              $this->port = null;
 735          }
 736      }
 737  
 738      /**
 739       * Filters the path of a URI
 740       *
 741       * @param string $path
 742       *
 743       * @return string
 744       *
 745       * @throws \InvalidArgumentException If the path is invalid.
 746       */
 747      private function filterPath($path)
 748      {
 749          if (!is_string($path)) {
 750              throw new \InvalidArgumentException('Path must be a string');
 751          }
 752  
 753          return preg_replace_callback(
 754              '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
 755              [$this, 'rawurlencodeMatchZero'],
 756              $path
 757          );
 758      }
 759  
 760      /**
 761       * Filters the query string or fragment of a URI.
 762       *
 763       * @param string $str
 764       *
 765       * @return string
 766       *
 767       * @throws \InvalidArgumentException If the query or fragment is invalid.
 768       */
 769      private function filterQueryAndFragment($str)
 770      {
 771          if (!is_string($str)) {
 772              throw new \InvalidArgumentException('Query and fragment must be a string');
 773          }
 774  
 775          return preg_replace_callback(
 776              '/(?:[^' . self::$charUnreserved . self::$charSubDelims . '%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
 777              [$this, 'rawurlencodeMatchZero'],
 778              $str
 779          );
 780      }
 781  
 782      private function rawurlencodeMatchZero(array $match)
 783      {
 784          return rawurlencode($match[0]);
 785      }
 786  
 787      private function validateState()
 788      {
 789          if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
 790              $this->host = self::HTTP_DEFAULT_HOST;
 791          }
 792  
 793          if ($this->getAuthority() === '') {
 794              if (0 === strpos($this->path, '//')) {
 795                  throw new \InvalidArgumentException('The path of a URI without an authority must not start with two slashes "//"');
 796              }
 797              if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
 798                  throw new \InvalidArgumentException('A relative URI must not have a path beginning with a segment containing a colon');
 799              }
 800          } elseif (isset($this->path[0]) && $this->path[0] !== '/') {
 801              @trigger_error(
 802                  'The path of a URI with an authority must start with a slash "/" or be empty. Automagically fixing the URI ' .
 803                  'by adding a leading slash to the path is deprecated since version 1.4 and will throw an exception instead.',
 804                  E_USER_DEPRECATED
 805              );
 806              $this->path = '/' . $this->path;
 807              //throw new \InvalidArgumentException('The path of a URI with an authority must start with a slash "/" or be empty');
 808          }
 809      }
 810  }


Generated: Sat Nov 4 14:26:03 2023 Cross-referenced by PHPXref 0.7.1