[ 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, '/')); 399 } 400 elseif ($input === '/..') { 401 $input = '/'; 402 $output = substr_replace($output, '', strrpos($output, '/')); 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 if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) { 828 $port = substr($remaining, $port_start + 1); 829 if ($port === false || $port === '') { 830 $port = null; 831 } 832 $remaining = substr($remaining, 0, $port_start); 833 } 834 else { 835 $port = null; 836 } 837 838 $return = $this->set_userinfo($iuserinfo) && 839 $this->set_host($remaining) && 840 $this->set_port($port); 841 842 $cache[$authority] = array($this->iuserinfo, 843 $this->ihost, 844 $this->port, 845 $return); 846 847 return $return; 848 } 849 850 /** 851 * Set the iuserinfo. 852 * 853 * @param string $iuserinfo 854 * @return bool 855 */ 856 protected function set_userinfo($iuserinfo) { 857 if ($iuserinfo === null) { 858 $this->iuserinfo = null; 859 } 860 else { 861 $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:'); 862 $this->scheme_normalization(); 863 } 864 865 return true; 866 } 867 868 /** 869 * Set the ihost. Returns true on success, false on failure (if there are 870 * any invalid characters). 871 * 872 * @param string $ihost 873 * @return bool 874 */ 875 protected function set_host($ihost) { 876 if ($ihost === null) { 877 $this->ihost = null; 878 return true; 879 } 880 if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') { 881 if (Ipv6::check_ipv6(substr($ihost, 1, -1))) { 882 $this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']'; 883 } 884 else { 885 $this->ihost = null; 886 return false; 887 } 888 } 889 else { 890 $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;='); 891 892 // Lowercase, but ignore pct-encoded sections (as they should 893 // remain uppercase). This must be done after the previous step 894 // as that can add unescaped characters. 895 $position = 0; 896 $strlen = strlen($ihost); 897 while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) { 898 if ($ihost[$position] === '%') { 899 $position += 3; 900 } 901 else { 902 $ihost[$position] = strtolower($ihost[$position]); 903 $position++; 904 } 905 } 906 907 $this->ihost = $ihost; 908 } 909 910 $this->scheme_normalization(); 911 912 return true; 913 } 914 915 /** 916 * Set the port. Returns true on success, false on failure (if there are 917 * any invalid characters). 918 * 919 * @param string $port 920 * @return bool 921 */ 922 protected function set_port($port) { 923 if ($port === null) { 924 $this->port = null; 925 return true; 926 } 927 928 if (strspn($port, '0123456789') === strlen($port)) { 929 $this->port = (int) $port; 930 $this->scheme_normalization(); 931 return true; 932 } 933 934 $this->port = null; 935 return false; 936 } 937 938 /** 939 * Set the ipath. 940 * 941 * @param string $ipath 942 * @return bool 943 */ 944 protected function set_path($ipath) { 945 static $cache; 946 if (!$cache) { 947 $cache = array(); 948 } 949 950 $ipath = (string) $ipath; 951 952 if (isset($cache[$ipath])) { 953 $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)]; 954 } 955 else { 956 $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/'); 957 $removed = $this->remove_dot_segments($valid); 958 959 $cache[$ipath] = array($valid, $removed); 960 $this->ipath = ($this->scheme !== null) ? $removed : $valid; 961 } 962 $this->scheme_normalization(); 963 return true; 964 } 965 966 /** 967 * Set the iquery. 968 * 969 * @param string $iquery 970 * @return bool 971 */ 972 protected function set_query($iquery) { 973 if ($iquery === null) { 974 $this->iquery = null; 975 } 976 else { 977 $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true); 978 $this->scheme_normalization(); 979 } 980 return true; 981 } 982 983 /** 984 * Set the ifragment. 985 * 986 * @param string $ifragment 987 * @return bool 988 */ 989 protected function set_fragment($ifragment) { 990 if ($ifragment === null) { 991 $this->ifragment = null; 992 } 993 else { 994 $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?'); 995 $this->scheme_normalization(); 996 } 997 return true; 998 } 999 1000 /** 1001 * Convert an IRI to a URI (or parts thereof) 1002 * 1003 * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()}) 1004 * @return string|false URI if IRI is valid, false otherwise. 1005 */ 1006 protected function to_uri($iri) { 1007 if (!is_string($iri)) { 1008 return false; 1009 } 1010 1011 static $non_ascii; 1012 if (!$non_ascii) { 1013 $non_ascii = implode('', range("\x80", "\xFF")); 1014 } 1015 1016 $position = 0; 1017 $strlen = strlen($iri); 1018 while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) { 1019 $iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1); 1020 $position += 3; 1021 $strlen += 2; 1022 } 1023 1024 return $iri; 1025 } 1026 1027 /** 1028 * Get the complete IRI 1029 * 1030 * @return string|false 1031 */ 1032 protected function get_iri() { 1033 if (!$this->is_valid()) { 1034 return false; 1035 } 1036 1037 $iri = ''; 1038 if ($this->scheme !== null) { 1039 $iri .= $this->scheme . ':'; 1040 } 1041 if (($iauthority = $this->get_iauthority()) !== null) { 1042 $iri .= '//' . $iauthority; 1043 } 1044 $iri .= $this->ipath; 1045 if ($this->iquery !== null) { 1046 $iri .= '?' . $this->iquery; 1047 } 1048 if ($this->ifragment !== null) { 1049 $iri .= '#' . $this->ifragment; 1050 } 1051 1052 return $iri; 1053 } 1054 1055 /** 1056 * Get the complete URI 1057 * 1058 * @return string 1059 */ 1060 protected function get_uri() { 1061 return $this->to_uri($this->get_iri()); 1062 } 1063 1064 /** 1065 * Get the complete iauthority 1066 * 1067 * @return string|null 1068 */ 1069 protected function get_iauthority() { 1070 if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) { 1071 return null; 1072 } 1073 1074 $iauthority = ''; 1075 if ($this->iuserinfo !== null) { 1076 $iauthority .= $this->iuserinfo . '@'; 1077 } 1078 if ($this->ihost !== null) { 1079 $iauthority .= $this->ihost; 1080 } 1081 if ($this->port !== null) { 1082 $iauthority .= ':' . $this->port; 1083 } 1084 return $iauthority; 1085 } 1086 1087 /** 1088 * Get the complete authority 1089 * 1090 * @return string 1091 */ 1092 protected function get_authority() { 1093 $iauthority = $this->get_iauthority(); 1094 if (is_string($iauthority)) { 1095 return $this->to_uri($iauthority); 1096 } 1097 else { 1098 return $iauthority; 1099 } 1100 } 1101 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Tue Jan 21 05:10:11 2025 | Cross-referenced by PHPXref 0.7.1 |