[ 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->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->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) { 673 $this->iuserinfo = null; 674 } 675 if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) { 676 $this->ihost = null; 677 } 678 if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) { 679 $this->port = null; 680 } 681 if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) { 682 $this->ipath = ''; 683 } 684 if (isset($this->ihost) && empty($this->ipath)) { 685 $this->ipath = '/'; 686 } 687 if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) { 688 $this->iquery = null; 689 } 690 if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) { 691 $this->ifragment = null; 692 } 693 } 694 695 /** 696 * Check if the object represents a valid IRI. This needs to be done on each 697 * call as some things change depending on another part of the IRI. 698 * 699 * @return bool 700 */ 701 public function is_valid() { 702 $isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null; 703 if ($this->ipath !== '' && 704 ( 705 $isauthority && $this->ipath[0] !== '/' || 706 ( 707 $this->scheme === null && 708 !$isauthority && 709 strpos($this->ipath, ':') !== false && 710 (strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/')) 711 ) 712 ) 713 ) { 714 return false; 715 } 716 717 return true; 718 } 719 720 /** 721 * Set the entire IRI. Returns true on success, false on failure (if there 722 * are any invalid characters). 723 * 724 * @param string $iri 725 * @return bool 726 */ 727 protected function set_iri($iri) { 728 static $cache; 729 if (!$cache) { 730 $cache = array(); 731 } 732 733 if ($iri === null) { 734 return true; 735 } 736 737 $iri = (string) $iri; 738 739 if (isset($cache[$iri])) { 740 list($this->scheme, 741 $this->iuserinfo, 742 $this->ihost, 743 $this->port, 744 $this->ipath, 745 $this->iquery, 746 $this->ifragment, 747 $return) = $cache[$iri]; 748 return $return; 749 } 750 751 $parsed = $this->parse_iri($iri); 752 753 $return = $this->set_scheme($parsed['scheme']) 754 && $this->set_authority($parsed['authority']) 755 && $this->set_path($parsed['path']) 756 && $this->set_query($parsed['query']) 757 && $this->set_fragment($parsed['fragment']); 758 759 $cache[$iri] = array($this->scheme, 760 $this->iuserinfo, 761 $this->ihost, 762 $this->port, 763 $this->ipath, 764 $this->iquery, 765 $this->ifragment, 766 $return); 767 return $return; 768 } 769 770 /** 771 * Set the scheme. Returns true on success, false on failure (if there are 772 * any invalid characters). 773 * 774 * @param string $scheme 775 * @return bool 776 */ 777 protected function set_scheme($scheme) { 778 if ($scheme === null) { 779 $this->scheme = null; 780 } 781 elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) { 782 $this->scheme = null; 783 return false; 784 } 785 else { 786 $this->scheme = strtolower($scheme); 787 } 788 return true; 789 } 790 791 /** 792 * Set the authority. Returns true on success, false on failure (if there are 793 * any invalid characters). 794 * 795 * @param string $authority 796 * @return bool 797 */ 798 protected function set_authority($authority) { 799 static $cache; 800 if (!$cache) { 801 $cache = array(); 802 } 803 804 if ($authority === null) { 805 $this->iuserinfo = null; 806 $this->ihost = null; 807 $this->port = null; 808 return true; 809 } 810 if (isset($cache[$authority])) { 811 list($this->iuserinfo, 812 $this->ihost, 813 $this->port, 814 $return) = $cache[$authority]; 815 816 return $return; 817 } 818 819 $remaining = $authority; 820 if (($iuserinfo_end = strrpos($remaining, '@')) !== false) { 821 $iuserinfo = substr($remaining, 0, $iuserinfo_end); 822 $remaining = substr($remaining, $iuserinfo_end + 1); 823 } 824 else { 825 $iuserinfo = null; 826 } 827 828 if (($port_start = strpos($remaining, ':', (strpos($remaining, ']') ?: 0))) !== false) { 829 $port = substr($remaining, $port_start + 1); 830 if ($port === false || $port === '') { 831 $port = null; 832 } 833 $remaining = substr($remaining, 0, $port_start); 834 } 835 else { 836 $port = null; 837 } 838 839 $return = $this->set_userinfo($iuserinfo) && 840 $this->set_host($remaining) && 841 $this->set_port($port); 842 843 $cache[$authority] = array($this->iuserinfo, 844 $this->ihost, 845 $this->port, 846 $return); 847 848 return $return; 849 } 850 851 /** 852 * Set the iuserinfo. 853 * 854 * @param string $iuserinfo 855 * @return bool 856 */ 857 protected function set_userinfo($iuserinfo) { 858 if ($iuserinfo === null) { 859 $this->iuserinfo = null; 860 } 861 else { 862 $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); 863 $this->scheme_normalization(); 864 } 865 866 return true; 867 } 868 869 /** 870 * Set the ihost. Returns true on success, false on failure (if there are 871 * any invalid characters). 872 * 873 * @param string $ihost 874 * @return bool 875 */ 876 protected function set_host($ihost) { 877 if ($ihost === null) { 878 $this->ihost = null; 879 return true; 880 } 881 if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { 882 if (Ipv6::check_ipv6(substr($ihost, 1, -1))) { 883 $this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']'; 884 } 885 else { 886 $this->ihost = null; 887 return false; 888 } 889 } 890 else { 891 $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); 892 893 // Lowercase, but ignore pct-encoded sections (as they should 894 // remain uppercase). This must be done after the previous step 895 // as that can add unescaped characters. 896 $position = 0; 897 $strlen = strlen($ihost); 898 while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { 899 if ($ihost[$position] === '%') { 900 $position += 3; 901 } 902 else { 903 $ihost[$position] = strtolower($ihost[$position]); 904 $position++; 905 } 906 } 907 908 $this->ihost = $ihost; 909 } 910 911 $this->scheme_normalization(); 912 913 return true; 914 } 915 916 /** 917 * Set the port. Returns true on success, false on failure (if there are 918 * any invalid characters). 919 * 920 * @param string $port 921 * @return bool 922 */ 923 protected function set_port($port) { 924 if ($port === null) { 925 $this->port = null; 926 return true; 927 } 928 929 if (strspn($port, '0123456789') === strlen($port)) { 930 $this->port = (int) $port; 931 $this->scheme_normalization(); 932 return true; 933 } 934 935 $this->port = null; 936 return false; 937 } 938 939 /** 940 * Set the ipath. 941 * 942 * @param string $ipath 943 * @return bool 944 */ 945 protected function set_path($ipath) { 946 static $cache; 947 if (!$cache) { 948 $cache = array(); 949 } 950 951 $ipath = (string) $ipath; 952 953 if (isset($cache[$ipath])) { 954 $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; 955 } 956 else { 957 $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); 958 $removed = $this->remove_dot_segments($valid); 959 960 $cache[$ipath] = array($valid, $removed); 961 $this->ipath = ($this->scheme !== null) ? $removed : $valid; 962 } 963 $this->scheme_normalization(); 964 return true; 965 } 966 967 /** 968 * Set the iquery. 969 * 970 * @param string $iquery 971 * @return bool 972 */ 973 protected function set_query($iquery) { 974 if ($iquery === null) { 975 $this->iquery = null; 976 } 977 else { 978 $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); 979 $this->scheme_normalization(); 980 } 981 return true; 982 } 983 984 /** 985 * Set the ifragment. 986 * 987 * @param string $ifragment 988 * @return bool 989 */ 990 protected function set_fragment($ifragment) { 991 if ($ifragment === null) { 992 $this->ifragment = null; 993 } 994 else { 995 $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); 996 $this->scheme_normalization(); 997 } 998 return true; 999 } 1000 1001 /** 1002 * Convert an IRI to a URI (or parts thereof) 1003 * 1004 * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()}) 1005 * @return string|false URI if IRI is valid, false otherwise. 1006 */ 1007 protected function to_uri($iri) { 1008 if (!is_string($iri)) { 1009 return false; 1010 } 1011 1012 static $non_ascii; 1013 if (!$non_ascii) { 1014 $non_ascii = implode('', range("\x80", "\xFF")); 1015 } 1016 1017 $position = 0; 1018 $strlen = strlen($iri); 1019 while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) { 1020 $iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1); 1021 $position += 3; 1022 $strlen += 2; 1023 } 1024 1025 return $iri; 1026 } 1027 1028 /** 1029 * Get the complete IRI 1030 * 1031 * @return string|false 1032 */ 1033 protected function get_iri() { 1034 if (!$this->is_valid()) { 1035 return false; 1036 } 1037 1038 $iri = ''; 1039 if ($this->scheme !== null) { 1040 $iri .= $this->scheme . ':'; 1041 } 1042 if (($iauthority = $this->get_iauthority()) !== null) { 1043 $iri .= '//' . $iauthority; 1044 } 1045 $iri .= $this->ipath; 1046 if ($this->iquery !== null) { 1047 $iri .= '?' . $this->iquery; 1048 } 1049 if ($this->ifragment !== null) { 1050 $iri .= '#' . $this->ifragment; 1051 } 1052 1053 return $iri; 1054 } 1055 1056 /** 1057 * Get the complete URI 1058 * 1059 * @return string 1060 */ 1061 protected function get_uri() { 1062 return $this->to_uri($this->get_iri()); 1063 } 1064 1065 /** 1066 * Get the complete iauthority 1067 * 1068 * @return string|null 1069 */ 1070 protected function get_iauthority() { 1071 if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) { 1072 return null; 1073 } 1074 1075 $iauthority = ''; 1076 if ($this->iuserinfo !== null) { 1077 $iauthority .= $this->iuserinfo . '@'; 1078 } 1079 if ($this->ihost !== null) { 1080 $iauthority .= $this->ihost; 1081 } 1082 if ($this->port !== null) { 1083 $iauthority .= ':' . $this->port; 1084 } 1085 return $iauthority; 1086 } 1087 1088 /** 1089 * Get the complete authority 1090 * 1091 * @return string 1092 */ 1093 protected function get_authority() { 1094 $iauthority = $this->get_iauthority(); 1095 if (is_string($iauthority)) { 1096 return $this->to_uri($iauthority); 1097 } 1098 else { 1099 return $iauthority; 1100 } 1101 } 1102 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Mon Mar 31 05:10:02 2025 | Cross-referenced by PHPXref 0.7.1 |