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