[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |