Ampache PHP Cross Reference Groupware Applications

Source: /modules/requests/Requests/IRI.php - 1220 lines - 36752 bytes - Summary - Text - Print

Description: IRI parser/serialiser/normaliser

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

title

Description

title

Description

title

Description

title

title

Body