| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 3 // SPDX-FileCopyrightText: 2004-2023 Ryan Parman, Sam Sneddon, Ryan McCue 4 // SPDX-FileCopyrightText: 2008 Steve Minutillo 5 // SPDX-License-Identifier: BSD-3-Clause 6 7 declare(strict_types=1); 8 9 namespace SimplePie; 10 11 /** 12 * IRI parser/serialiser/normaliser 13 * 14 * @property ?string $scheme 15 * @property ?string $userinfo 16 * @property ?string $host 17 * @property ?int $port 18 * @property-write int|string|null $port 19 * @property ?string $authority 20 * @property string $path 21 * @property ?string $query 22 * @property ?string $fragment 23 */ 24 class IRI 25 { 26 /** 27 * Scheme 28 * 29 * @var ?string 30 */ 31 protected $scheme = null; 32 33 /** 34 * User Information 35 * 36 * @var ?string 37 */ 38 protected $iuserinfo = null; 39 40 /** 41 * ihost 42 * 43 * @var ?string 44 */ 45 protected $ihost = null; 46 47 /** 48 * Port 49 * 50 * @var ?int 51 */ 52 protected $port = null; 53 54 /** 55 * ipath 56 * 57 * @var string 58 */ 59 protected $ipath = ''; 60 61 /** 62 * iquery 63 * 64 * @var ?string 65 */ 66 protected $iquery = null; 67 68 /** 69 * ifragment 70 * 71 * @var ?string 72 */ 73 protected $ifragment = null; 74 75 /** 76 * Normalization database 77 * 78 * Each key is the scheme, each value is an array with each key as the IRI 79 * part and value as the default value for that part. 80 * 81 * @var array<string, array<string, mixed>> 82 */ 83 protected $normalization = [ 84 'acap' => [ 85 'port' => 674 86 ], 87 'dict' => [ 88 'port' => 2628 89 ], 90 'file' => [ 91 'ihost' => 'localhost' 92 ], 93 'http' => [ 94 'port' => 80, 95 'ipath' => '/' 96 ], 97 'https' => [ 98 'port' => 443, 99 'ipath' => '/' 100 ], 101 ]; 102 103 /** 104 * Return the entire IRI when you try and read the object as a string 105 * 106 * @return string 107 */ 108 public function __toString() 109 { 110 return (string) $this->get_iri(); 111 } 112 113 /** 114 * Overload __set() to provide access via properties 115 * 116 * @param string $name Property name 117 * @param mixed $value Property value 118 * @return void 119 */ 120 public function __set(string $name, $value) 121 { 122 $callable = [$this, 'set_' . $name]; 123 if (is_callable($callable)) { 124 call_user_func($callable, $value); 125 } elseif ( 126 $name === 'iauthority' 127 || $name === 'iuserinfo' 128 || $name === 'ihost' 129 || $name === 'ipath' 130 || $name === 'iquery' 131 || $name === 'ifragment' 132 ) { 133 call_user_func([$this, 'set_' . substr($name, 1)], $value); 134 } 135 } 136 137 /** 138 * Overload __get() to provide access via properties 139 * 140 * @param string $name Property name 141 * @return mixed 142 */ 143 public function __get(string $name) 144 { 145 // isset() returns false for null, we don't want to do that 146 // Also why we use array_key_exists below instead of isset() 147 $props = get_object_vars($this); 148 149 if ( 150 $name === 'iri' || 151 $name === 'uri' || 152 $name === 'iauthority' || 153 $name === 'authority' 154 ) { 155 $return = $this->{"get_$name"}(); 156 } elseif (array_key_exists($name, $props)) { 157 $return = $this->$name; 158 } 159 // host -> ihost 160 elseif (array_key_exists($prop = 'i' . $name, $props)) { 161 $name = $prop; 162 $return = $this->$prop; 163 } 164 // ischeme -> scheme 165 elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) { 166 $name = $prop; 167 $return = $this->$prop; 168 } else { 169 trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE); 170 $return = null; 171 } 172 173 if ($return === null && isset($this->scheme, $this->normalization[$this->scheme][$name])) { 174 return $this->normalization[$this->scheme][$name]; 175 } 176 177 return $return; 178 } 179 180 /** 181 * Overload __isset() to provide access via properties 182 * 183 * @param string $name Property name 184 * @return bool 185 */ 186 public function __isset(string $name) 187 { 188 return method_exists($this, 'get_' . $name) || isset($this->$name); 189 } 190 191 /** 192 * Overload __unset() to provide access via properties 193 * 194 * @param string $name Property name 195 * @return void 196 */ 197 public function __unset(string $name) 198 { 199 $callable = [$this, 'set_' . $name]; 200 if (is_callable($callable)) { 201 call_user_func($callable, ''); 202 } 203 } 204 205 /** 206 * Create a new IRI object, from a specified string 207 * 208 * @param string|null $iri 209 */ 210 public function __construct(?string $iri = null) 211 { 212 $this->set_iri($iri); 213 } 214 215 /** 216 * Clean up 217 * @return void 218 */ 219 public function __destruct() 220 { 221 $this->set_iri(null, true); 222 $this->set_path(null, true); 223 $this->set_authority(null, true); 224 } 225 226 /** 227 * Create a new IRI object by resolving a relative IRI 228 * 229 * Returns false if $base is not absolute, otherwise an IRI. 230 * 231 * @param IRI|string $base (Absolute) Base IRI 232 * @param IRI|string $relative Relative IRI 233 * @return IRI|false 234 */ 235 public static function absolutize($base, $relative) 236 { 237 if (!($relative instanceof IRI)) { 238 $relative = new IRI($relative); 239 } 240 if (!$relative->is_valid()) { 241 return false; 242 } elseif ($relative->scheme !== null) { 243 return clone $relative; 244 } else { 245 if (!($base instanceof IRI)) { 246 $base = new IRI($base); 247 } 248 if ($base->scheme !== null && $base->is_valid()) { 249 if ($relative->get_iri() !== '') { 250 if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) { 251 $target = clone $relative; 252 $target->scheme = $base->scheme; 253 } else { 254 $target = new IRI(); 255 $target->scheme = $base->scheme; 256 $target->iuserinfo = $base->iuserinfo; 257 $target->ihost = $base->ihost; 258 $target->port = $base->port; 259 if ($relative->ipath !== '') { 260 if ($relative->ipath[0] === '/') { 261 $target->ipath = $relative->ipath; 262 } elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') { 263 $target->ipath = '/' . $relative->ipath; 264 } elseif (($last_segment = strrpos($base->ipath, '/')) !== false) { 265 $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath; 266 } else { 267 $target->ipath = $relative->ipath; 268 } 269 $target->ipath = $target->remove_dot_segments($target->ipath); 270 $target->iquery = $relative->iquery; 271 } else { 272 $target->ipath = $base->ipath; 273 if ($relative->iquery !== null) { 274 $target->iquery = $relative->iquery; 275 } elseif ($base->iquery !== null) { 276 $target->iquery = $base->iquery; 277 } 278 } 279 $target->ifragment = $relative->ifragment; 280 } 281 } else { 282 $target = clone $base; 283 $target->ifragment = null; 284 } 285 $target->scheme_normalization(); 286 return $target; 287 } 288 289 return false; 290 } 291 } 292 293 /** 294 * Parse an IRI into scheme/authority/path/query/fragment segments 295 * 296 * @param string $iri 297 * @return array{ 298 * scheme: string|null, 299 * authority: string|null, 300 * path: string, 301 * query: string|null, 302 * fragment: string|null, 303 * }|false 304 */ 305 protected function parse_iri(string $iri) 306 { 307 $iri = trim($iri, "\x20\x09\x0A\x0C\x0D"); 308 if (preg_match('/^(?:(?P<scheme>[^:\/?#]+):)?(:?\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(?:\?(?P<query>[^#]*))?(?:#(?P<fragment>.*))?$/', $iri, $match, \PREG_UNMATCHED_AS_NULL)) { 309 // TODO: Remove once we require PHP ≥ 7.4. 310 $match['query'] = $match['query'] ?? null; 311 $match['fragment'] = $match['fragment'] ?? null; 312 return $match; 313 } 314 315 // This can occur when a paragraph is accidentally parsed as a URI 316 return false; 317 } 318 319 /** 320 * Remove dot segments from a path 321 * 322 * @param string $input 323 * @return string 324 */ 325 protected function remove_dot_segments(string $input) 326 { 327 $output = ''; 328 while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') { 329 // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise, 330 if (strpos($input, '../') === 0) { 331 $input = substr($input, 3); 332 } elseif (strpos($input, './') === 0) { 333 $input = substr($input, 2); 334 } 335 // B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise, 336 elseif (strpos($input, '/./') === 0) { 337 $input = substr($input, 2); 338 } elseif ($input === '/.') { 339 $input = '/'; 340 } 341 // C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise, 342 elseif (strpos($input, '/../') === 0) { 343 $input = substr($input, 3); 344 $output = substr_replace($output, '', intval(strrpos($output, '/'))); 345 } elseif ($input === '/..') { 346 $input = '/'; 347 $output = substr_replace($output, '', intval(strrpos($output, '/'))); 348 } 349 // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise, 350 elseif ($input === '.' || $input === '..') { 351 $input = ''; 352 } 353 // E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer 354 elseif (($pos = strpos($input, '/', 1)) !== false) { 355 $output .= substr($input, 0, $pos); 356 $input = substr_replace($input, '', 0, $pos); 357 } else { 358 $output .= $input; 359 $input = ''; 360 } 361 } 362 return $output . $input; 363 } 364 365 /** 366 * Replace invalid character with percent encoding 367 * 368 * @param string $string Input string 369 * @param string $extra_chars Valid characters not in iunreserved or 370 * iprivate (this is ASCII-only) 371 * @param bool $iprivate Allow iprivate 372 * @return string 373 */ 374 protected function replace_invalid_with_pct_encoding(string $string, string $extra_chars, bool $iprivate = false) 375 { 376 // Normalize as many pct-encoded sections as possible 377 $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', [$this, 'remove_iunreserved_percent_encoded'], $string); 378 \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid"); 379 380 // Replace invalid percent characters 381 $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string); 382 \assert(\is_string($string), "For PHPStan: Should not occur, the regex is valid"); 383 384 // Add unreserved and % to $extra_chars (the latter is safe because all 385 // pct-encoded sections are now valid). 386 $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%'; 387 388 // Now replace any bytes that aren't allowed with their pct-encoded versions 389 $position = 0; 390 $strlen = strlen($string); 391 while (($position += strspn($string, $extra_chars, $position)) < $strlen) { 392 $value = ord($string[$position]); 393 $character = 0; 394 395 // Start position 396 $start = $position; 397 398 // By default we are valid 399 $valid = true; 400 401 // No one byte sequences are valid due to the while. 402 // Two byte sequence: 403 if (($value & 0xE0) === 0xC0) { 404 $character = ($value & 0x1F) << 6; 405 $length = 2; 406 $remaining = 1; 407 } 408 // Three byte sequence: 409 elseif (($value & 0xF0) === 0xE0) { 410 $character = ($value & 0x0F) << 12; 411 $length = 3; 412 $remaining = 2; 413 } 414 // Four byte sequence: 415 elseif (($value & 0xF8) === 0xF0) { 416 $character = ($value & 0x07) << 18; 417 $length = 4; 418 $remaining = 3; 419 } 420 // Invalid byte: 421 else { 422 $valid = false; 423 $length = 1; 424 $remaining = 0; 425 } 426 427 if ($remaining) { 428 if ($position + $length <= $strlen) { 429 for ($position++; $remaining; $position++) { 430 $value = ord($string[$position]); 431 432 // Check that the byte is valid, then add it to the character: 433 if (($value & 0xC0) === 0x80) { 434 $character |= ($value & 0x3F) << (--$remaining * 6); 435 } 436 // If it is invalid, count the sequence as invalid and reprocess the current byte: 437 else { 438 $valid = false; 439 $position--; 440 break; 441 } 442 } 443 } else { 444 $position = $strlen - 1; 445 $valid = false; 446 } 447 } 448 449 // Percent encode anything invalid or not in ucschar 450 if ( 451 // Invalid sequences 452 !$valid 453 // Non-shortest form sequences are invalid 454 || $length > 1 && $character <= 0x7F 455 || $length > 2 && $character <= 0x7FF 456 || $length > 3 && $character <= 0xFFFF 457 // Outside of range of ucschar codepoints 458 // Noncharacters 459 || ($character & 0xFFFE) === 0xFFFE 460 || $character >= 0xFDD0 && $character <= 0xFDEF 461 || ( 462 // Everything else not in ucschar 463 $character > 0xD7FF && $character < 0xF900 464 || $character < 0xA0 465 || $character > 0xEFFFD 466 ) 467 && ( 468 // Everything not in iprivate, if it applies 469 !$iprivate 470 || $character < 0xE000 471 || $character > 0x10FFFD 472 ) 473 ) { 474 // If we were a character, pretend we weren't, but rather an error. 475 if ($valid) { 476 $position--; 477 } 478 479 for ($j = $start; $j <= $position; $j++) { 480 $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1); 481 $j += 2; 482 $position += 2; 483 $strlen += 2; 484 } 485 } 486 } 487 488 return $string; 489 } 490 491 /** 492 * Callback function for preg_replace_callback. 493 * 494 * Removes sequences of percent encoded bytes that represent UTF-8 495 * encoded characters in iunreserved 496 * 497 * @param array{string} $match PCRE match, a capture group #0 consisting of a sequence of valid percent-encoded bytes 498 * @return string Replacement 499 */ 500 protected function remove_iunreserved_percent_encoded(array $match) 501 { 502 // As we just have valid percent encoded sequences we can just explode 503 // and ignore the first member of the returned array (an empty string). 504 $bytes = explode('%', $match[0]); 505 506 // Initialize the new string (this is what will be returned) and that 507 // there are no bytes remaining in the current sequence (unsurprising 508 // at the first byte!). 509 $string = ''; 510 $remaining = 0; 511 512 // these variables will be initialized in the loop but PHPStan is not able to detect it currently 513 $start = 0; 514 $character = 0; 515 $length = 0; 516 $valid = true; 517 518 // Loop over each and every byte, and set $value to its value 519 for ($i = 1, $len = count($bytes); $i < $len; $i++) { 520 $value = hexdec($bytes[$i]); 521 522 // If we're the first byte of sequence: 523 if (!$remaining) { 524 // Start position 525 $start = $i; 526 527 // By default we are valid 528 $valid = true; 529 530 // One byte sequence: 531 if ($value <= 0x7F) { 532 $character = $value; 533 $length = 1; 534 } 535 // Two byte sequence: 536 elseif (($value & 0xE0) === 0xC0) { 537 $character = ($value & 0x1F) << 6; 538 $length = 2; 539 $remaining = 1; 540 } 541 // Three byte sequence: 542 elseif (($value & 0xF0) === 0xE0) { 543 $character = ($value & 0x0F) << 12; 544 $length = 3; 545 $remaining = 2; 546 } 547 // Four byte sequence: 548 elseif (($value & 0xF8) === 0xF0) { 549 $character = ($value & 0x07) << 18; 550 $length = 4; 551 $remaining = 3; 552 } 553 // Invalid byte: 554 else { 555 $valid = false; 556 $remaining = 0; 557 } 558 } 559 // Continuation byte: 560 else { 561 // Check that the byte is valid, then add it to the character: 562 if (($value & 0xC0) === 0x80) { 563 $remaining--; 564 $character |= ($value & 0x3F) << ($remaining * 6); 565 } 566 // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence: 567 else { 568 $valid = false; 569 $remaining = 0; 570 $i--; 571 } 572 } 573 574 // If we've reached the end of the current byte sequence, append it to Unicode::$data 575 if (!$remaining) { 576 // Percent encode anything invalid or not in iunreserved 577 if ( 578 // Invalid sequences 579 !$valid 580 // Non-shortest form sequences are invalid 581 || $length > 1 && $character <= 0x7F 582 || $length > 2 && $character <= 0x7FF 583 || $length > 3 && $character <= 0xFFFF 584 // Outside of range of iunreserved codepoints 585 || $character < 0x2D 586 || $character > 0xEFFFD 587 // Noncharacters 588 || ($character & 0xFFFE) === 0xFFFE 589 || $character >= 0xFDD0 && $character <= 0xFDEF 590 // Everything else not in iunreserved (this is all BMP) 591 || $character === 0x2F 592 || $character > 0x39 && $character < 0x41 593 || $character > 0x5A && $character < 0x61 594 || $character > 0x7A && $character < 0x7E 595 || $character > 0x7E && $character < 0xA0 596 || $character > 0xD7FF && $character < 0xF900 597 ) { 598 for ($j = $start; $j <= $i; $j++) { 599 $string .= '%' . strtoupper($bytes[$j]); 600 } 601 } else { 602 for ($j = $start; $j <= $i; $j++) { 603 // Cast for PHPStan, this will always be a number between 0 and 0xFF so hexdec will return int. 604 $string .= chr((int) hexdec($bytes[$j])); 605 } 606 } 607 } 608 } 609 610 // If we have any bytes left over they are invalid (i.e., we are 611 // mid-way through a multi-byte sequence) 612 if ($remaining) { 613 for ($j = $start; $j < $len; $j++) { 614 $string .= '%' . strtoupper($bytes[$j]); 615 } 616 } 617 618 return $string; 619 } 620 621 /** 622 * @return void 623 */ 624 protected function scheme_normalization() 625 { 626 if ($this->scheme === null) { 627 return; 628 } 629 630 if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { 631 $this->iuserinfo = null; 632 } 633 if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { 634 $this->ihost = null; 635 } 636 if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { 637 $this->port = null; 638 } 639 if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { 640 $this->ipath = ''; 641 } 642 if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { 643 $this->iquery = null; 644 } 645 if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { 646 $this->ifragment = null; 647 } 648 } 649 650 /** 651 * Check if the object represents a valid IRI. This needs to be done on each 652 * call as some things change depending on another part of the IRI. 653 * 654 * @return bool 655 */ 656 public function is_valid() 657 { 658 if ($this->ipath === '') { 659 return true; 660 } 661 662 $isauthority = $this->iuserinfo !== null || $this->ihost !== null || 663 $this->port !== null; 664 if ($isauthority && $this->ipath[0] === '/') { 665 return true; 666 } 667 668 if (!$isauthority && (substr($this->ipath, 0, 2) === '//')) { 669 return false; 670 } 671 672 // Relative urls cannot have a colon in the first path segment (and the 673 // slashes themselves are not included so skip the first character). 674 if (!$this->scheme && !$isauthority && 675 strpos($this->ipath, ':') !== false && 676 strpos($this->ipath, '/', 1) !== false && 677 strpos($this->ipath, ':') < strpos($this->ipath, '/', 1)) { 678 return false; 679 } 680 681 return true; 682 } 683 684 /** 685 * Set the entire IRI. Returns true on success, false on failure (if there 686 * are any invalid characters). 687 * 688 * @param string|null $iri 689 * @return bool 690 */ 691 public function set_iri(?string $iri, bool $clear_cache = false) 692 { 693 static $cache; 694 if ($clear_cache) { 695 $cache = null; 696 return false; 697 } 698 if (!$cache) { 699 $cache = []; 700 } 701 702 if ($iri === null) { 703 return true; 704 } elseif (isset($cache[$iri])) { 705 [ 706 $this->scheme, 707 $this->iuserinfo, 708 $this->ihost, 709 $this->port, 710 $this->ipath, 711 $this->iquery, 712 $this->ifragment, 713 $return 714 ] = $cache[$iri]; 715 716 return $return; 717 } 718 719 $parsed = $this->parse_iri((string) $iri); 720 if (!$parsed) { 721 return false; 722 } 723 724 $return = $this->set_scheme($parsed['scheme']) 725 && $this->set_authority($parsed['authority']) 726 && $this->set_path($parsed['path']) 727 && $this->set_query($parsed['query']) 728 && $this->set_fragment($parsed['fragment']); 729 730 $cache[$iri] = [ 731 $this->scheme, 732 $this->iuserinfo, 733 $this->ihost, 734 $this->port, 735 $this->ipath, 736 $this->iquery, 737 $this->ifragment, 738 $return 739 ]; 740 741 return $return; 742 } 743 744 /** 745 * Set the scheme. Returns true on success, false on failure (if there are 746 * any invalid characters). 747 * 748 * @param string|null $scheme 749 * @return bool 750 */ 751 public function set_scheme(?string $scheme) 752 { 753 if ($scheme === null) { 754 $this->scheme = null; 755 } elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { 756 $this->scheme = null; 757 return false; 758 } else { 759 $this->scheme = strtolower($scheme); 760 } 761 return true; 762 } 763 764 /** 765 * Set the authority. Returns true on success, false on failure (if there are 766 * any invalid characters). 767 * 768 * @param string|null $authority 769 * @return bool 770 */ 771 public function set_authority(?string $authority, bool $clear_cache = false) 772 { 773 static $cache; 774 if ($clear_cache) { 775 $cache = null; 776 return false; 777 } 778 if (!$cache) { 779 $cache = []; 780 } 781 782 if ($authority === null) { 783 $this->iuserinfo = null; 784 $this->ihost = null; 785 $this->port = null; 786 return true; 787 } elseif (isset($cache[$authority])) { 788 [ 789 $this->iuserinfo, 790 $this->ihost, 791 $this->port, 792 $return 793 ] = $cache[$authority]; 794 795 return $return; 796 } 797 798 $remaining = $authority; 799 if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { 800 // Cast for PHPStan on PHP < 8.0. It does not detect that 801 // the range is not flipped so substr cannot return false. 802 $iuserinfo = (string) substr($remaining, 0, $iuserinfo_end); 803 $remaining = substr($remaining, $iuserinfo_end + 1); 804 } else { 805 $iuserinfo = null; 806 } 807 if (($port_start = strpos($remaining, ':', intval(strpos($remaining, ']')))) !== false) { 808 $port = substr($remaining, $port_start + 1); 809 if ($port === false) { 810 $port = null; 811 } 812 $remaining = substr($remaining, 0, $port_start); 813 } else { 814 $port = null; 815 } 816 817 $return = $this->set_userinfo($iuserinfo) && 818 $this->set_host($remaining) && 819 $this->set_port($port); 820 821 $cache[$authority] = [ 822 $this->iuserinfo, 823 $this->ihost, 824 $this->port, 825 $return 826 ]; 827 828 return $return; 829 } 830 831 /** 832 * Set the iuserinfo. 833 * 834 * @param string|null $iuserinfo 835 * @return bool 836 */ 837 public function set_userinfo(?string $iuserinfo) 838 { 839 if ($iuserinfo === null) { 840 $this->iuserinfo = null; 841 } else { 842 $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); 843 $this->scheme_normalization(); 844 } 845 846 return true; 847 } 848 849 /** 850 * Set the ihost. Returns true on success, false on failure (if there are 851 * any invalid characters). 852 * 853 * @param string|null $ihost 854 * @return bool 855 */ 856 public function set_host(?string $ihost) 857 { 858 if ($ihost === null) { 859 $this->ihost = null; 860 return true; 861 } elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { 862 if (\SimplePie\Net\IPv6::check_ipv6(substr($ihost, 1, -1))) { 863 $this->ihost = '[' . \SimplePie\Net\IPv6::compress(substr($ihost, 1, -1)) . ']'; 864 } else { 865 $this->ihost = null; 866 return false; 867 } 868 } else { 869 $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); 870 871 // Lowercase, but ignore pct-encoded sections (as they should 872 // remain uppercase). This must be done after the previous step 873 // as that can add unescaped characters. 874 $position = 0; 875 $strlen = strlen($ihost); 876 while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { 877 if ($ihost[$position] === '%') { 878 $position += 3; 879 } else { 880 $ihost[$position] = strtolower($ihost[$position]); 881 $position++; 882 } 883 } 884 885 $this->ihost = $ihost; 886 } 887 888 $this->scheme_normalization(); 889 890 return true; 891 } 892 893 /** 894 * Set the port. Returns true on success, false on failure (if there are 895 * any invalid characters). 896 * 897 * @param string|int|null $port 898 * @return bool 899 */ 900 public function set_port($port) 901 { 902 if ($port === null) { 903 $this->port = null; 904 return true; 905 } elseif (strspn((string) $port, '0123456789') === strlen((string) $port)) { 906 $this->port = (int) $port; 907 $this->scheme_normalization(); 908 return true; 909 } 910 911 $this->port = null; 912 return false; 913 } 914 915 /** 916 * Set the ipath. 917 * 918 * @param string|null $ipath 919 * @return bool 920 */ 921 public function set_path(?string $ipath, bool $clear_cache = false) 922 { 923 static $cache; 924 if ($clear_cache) { 925 $cache = null; 926 return false; 927 } 928 if (!$cache) { 929 $cache = []; 930 } 931 932 $ipath = (string) $ipath; 933 934 if (isset($cache[$ipath])) { 935 $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; 936 } else { 937 $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); 938 $removed = $this->remove_dot_segments($valid); 939 940 $cache[$ipath] = [$valid, $removed]; 941 $this->ipath = ($this->scheme !== null) ? $removed : $valid; 942 } 943 944 $this->scheme_normalization(); 945 return true; 946 } 947 948 /** 949 * Set the iquery. 950 * 951 * @param string|null $iquery 952 * @return bool 953 */ 954 public function set_query(?string $iquery) 955 { 956 if ($iquery === null) { 957 $this->iquery = null; 958 } else { 959 $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); 960 $this->scheme_normalization(); 961 } 962 return true; 963 } 964 965 /** 966 * Set the ifragment. 967 * 968 * @param string|null $ifragment 969 * @return bool 970 */ 971 public function set_fragment(?string $ifragment) 972 { 973 if ($ifragment === null) { 974 $this->ifragment = null; 975 } else { 976 $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); 977 $this->scheme_normalization(); 978 } 979 return true; 980 } 981 982 /** 983 * Convert an IRI to a URI (or parts thereof) 984 * 985 * @param string $string 986 * @return string 987 */ 988 public function to_uri(string $string) 989 { 990 static $non_ascii; 991 if (!$non_ascii) { 992 $non_ascii = implode('', range("\x80", "\xFF")); 993 } 994 995 $position = 0; 996 $strlen = strlen($string); 997 while (($position += strcspn($string, $non_ascii, $position)) < $strlen) { 998 $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1); 999 $position += 3; 1000 $strlen += 2; 1001 } 1002 1003 return $string; 1004 } 1005 1006 /** 1007 * Get the complete IRI 1008 * 1009 * @return string|false 1010 */ 1011 public function get_iri() 1012 { 1013 if (!$this->is_valid()) { 1014 return false; 1015 } 1016 1017 $iri = ''; 1018 if ($this->scheme !== null) { 1019 $iri .= $this->scheme . ':'; 1020 } 1021 if (($iauthority = $this->get_iauthority()) !== null) { 1022 $iri .= '//' . $iauthority; 1023 } 1024 if ($this->ipath !== '') { 1025 $iri .= $this->ipath; 1026 } elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '') { 1027 $iri .= $this->normalization[$this->scheme]['ipath']; 1028 } 1029 if ($this->iquery !== null) { 1030 $iri .= '?' . $this->iquery; 1031 } 1032 if ($this->ifragment !== null) { 1033 $iri .= '#' . $this->ifragment; 1034 } 1035 1036 return $iri; 1037 } 1038 1039 /** 1040 * Get the complete URI 1041 * 1042 * @return string 1043 */ 1044 public function get_uri() 1045 { 1046 return $this->to_uri((string) $this->get_iri()); 1047 } 1048 1049 /** 1050 * Get the complete iauthority 1051 * 1052 * @return ?string 1053 */ 1054 protected function get_iauthority() 1055 { 1056 if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null) { 1057 $iauthority = ''; 1058 if ($this->iuserinfo !== null) { 1059 $iauthority .= $this->iuserinfo . '@'; 1060 } 1061 if ($this->ihost !== null) { 1062 $iauthority .= $this->ihost; 1063 } 1064 if ($this->port !== null && $this->port !== 0) { 1065 $iauthority .= ':' . $this->port; 1066 } 1067 return $iauthority; 1068 } 1069 1070 return null; 1071 } 1072 1073 /** 1074 * Get the complete authority 1075 * 1076 * @return ?string 1077 */ 1078 protected function get_authority() 1079 { 1080 $iauthority = $this->get_iauthority(); 1081 if (is_string($iauthority)) { 1082 return $this->to_uri($iauthority); 1083 } 1084 1085 return $iauthority; 1086 } 1087 } 1088 1089 class_alias('SimplePie\IRI', 'SimplePie_IRI');
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Thu Oct 30 08:20:06 2025 | Cross-referenced by PHPXref |