[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/simplepie/simplepie/library/SimplePie/ -> IRI.php (source)

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