MDL-56001 lib: Upgrade simplepie to 1.4.2
[moodle.git] / lib / simplepie / library / SimplePie / IRI.php
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  */
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;
64         /**
65          * User Information
66          *
67          * @var string
68          */
69         protected $iuserinfo = null;
71         /**
72          * ihost
73          *
74          * @var string
75          */
76         protected $ihost = null;
78         /**
79          * Port
80          *
81          * @var string
82          */
83         protected $port = null;
85         /**
86          * ipath
87          *
88          * @var string
89          */
90         protected $ipath = '';
92         /**
93          * iquery
94          *
95          * @var string
96          */
97         protected $iquery = null;
99         /**
100          * ifragment
101          *
102          * @var string
103          */
104         protected $ifragment = null;
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         );
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         }
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         }
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);
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                 }
210                 if ($return === null && isset($this->normalization[$this->scheme][$name]))
211                 {
212                         return $this->normalization[$this->scheme][$name];
213                 }
214                 else
215                 {
216                         return $return;
217                 }
218         }
220         /**
221          * Overload __isset() to provide access via properties
222          *
223          * @param string $name Property name
224          * @return bool
225          */
226         public function __isset($name)
227         {
228                 if (method_exists($this, 'get_' . $name) || isset($this->$name))
229                 {
230                         return true;
231                 }
232                 else
233                 {
234                         return false;
235                 }
236         }
238         /**
239          * Overload __unset() to provide access via properties
240          *
241          * @param string $name Property name
242          */
243         public function __unset($name)
244         {
245                 if (method_exists($this, 'set_' . $name))
246                 {
247                         call_user_func(array($this, 'set_' . $name), '');
248                 }
249         }
251         /**
252          * Create a new IRI object, from a specified string
253          *
254          * @param string $iri
255          */
256         public function __construct($iri = null)
257         {
258                 $this->set_iri($iri);
259         }
261         /**
262          * Clean up
263          */
264         public function __destruct() {
265             $this->set_iri(null, true);
266             $this->set_path(null, true);
267             $this->set_authority(null, true);
268         }
270         /**
271          * Create a new IRI object by resolving a relative IRI
272          *
273          * Returns false if $base is not absolute, otherwise an IRI.
274          *
275          * @param IRI|string $base (Absolute) Base IRI
276          * @param IRI|string $relative Relative IRI
277          * @return IRI|false
278          */
279         public static function absolutize($base, $relative)
280         {
281                 if (!($relative instanceof SimplePie_IRI))
282                 {
283                         $relative = new SimplePie_IRI($relative);
284                 }
285                 if (!$relative->is_valid())
286                 {
287                         return false;
288                 }
289                 elseif ($relative->scheme !== null)
290                 {
291                         return clone $relative;
292                 }
293                 else
294                 {
295                         if (!($base instanceof SimplePie_IRI))
296                         {
297                                 $base = new SimplePie_IRI($base);
298                         }
299                         if ($base->scheme !== null && $base->is_valid())
300                         {
301                                 if ($relative->get_iri() !== '')
302                                 {
303                                         if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null)
304                                         {
305                                                 $target = clone $relative;
306                                                 $target->scheme = $base->scheme;
307                                         }
308                                         else
309                                         {
310                                                 $target = new SimplePie_IRI;
311                                                 $target->scheme = $base->scheme;
312                                                 $target->iuserinfo = $base->iuserinfo;
313                                                 $target->ihost = $base->ihost;
314                                                 $target->port = $base->port;
315                                                 if ($relative->ipath !== '')
316                                                 {
317                                                         if ($relative->ipath[0] === '/')
318                                                         {
319                                                                 $target->ipath = $relative->ipath;
320                                                         }
321                                                         elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '')
322                                                         {
323                                                                 $target->ipath = '/' . $relative->ipath;
324                                                         }
325                                                         elseif (($last_segment = strrpos($base->ipath, '/')) !== false)
326                                                         {
327                                                                 $target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
328                                                         }
329                                                         else
330                                                         {
331                                                                 $target->ipath = $relative->ipath;
332                                                         }
333                                                         $target->ipath = $target->remove_dot_segments($target->ipath);
334                                                         $target->iquery = $relative->iquery;
335                                                 }
336                                                 else
337                                                 {
338                                                         $target->ipath = $base->ipath;
339                                                         if ($relative->iquery !== null)
340                                                         {
341                                                                 $target->iquery = $relative->iquery;
342                                                         }
343                                                         elseif ($base->iquery !== null)
344                                                         {
345                                                                 $target->iquery = $base->iquery;
346                                                         }
347                                                 }
348                                                 $target->ifragment = $relative->ifragment;
349                                         }
350                                 }
351                                 else
352                                 {
353                                         $target = clone $base;
354                                         $target->ifragment = null;
355                                 }
356                                 $target->scheme_normalization();
357                                 return $target;
358                         }
359                         else
360                         {
361                                 return false;
362                         }
363                 }
364         }
366         /**
367          * Parse an IRI into scheme/authority/path/query/fragment segments
368          *
369          * @param string $iri
370          * @return array
371          */
372         protected function parse_iri($iri)
373         {
374                 $iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
375                 if (preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match))
376                 {
377                         if ($match[1] === '')
378                         {
379                                 $match['scheme'] = null;
380                         }
381                         if (!isset($match[3]) || $match[3] === '')
382                         {
383                                 $match['authority'] = null;
384                         }
385                         if (!isset($match[5]))
386                         {
387                                 $match['path'] = '';
388                         }
389                         if (!isset($match[6]) || $match[6] === '')
390                         {
391                                 $match['query'] = null;
392                         }
393                         if (!isset($match[8]) || $match[8] === '')
394                         {
395                                 $match['fragment'] = null;
396                         }
397                         return $match;
398                 }
399                 else
400                 {
401                         // This can occur when a paragraph is accidentally parsed as a URI
402                         return false;
403                 }
404         }
406         /**
407          * Remove dot segments from a path
408          *
409          * @param string $input
410          * @return string
411          */
412         protected function remove_dot_segments($input)
413         {
414                 $output = '';
415                 while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..')
416                 {
417                         // A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
418                         if (strpos($input, '../') === 0)
419                         {
420                                 $input = substr($input, 3);
421                         }
422                         elseif (strpos($input, './') === 0)
423                         {
424                                 $input = substr($input, 2);
425                         }
426                         // 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,
427                         elseif (strpos($input, '/./') === 0)
428                         {
429                                 $input = substr($input, 2);
430                         }
431                         elseif ($input === '/.')
432                         {
433                                 $input = '/';
434                         }
435                         // 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,
436                         elseif (strpos($input, '/../') === 0)
437                         {
438                                 $input = substr($input, 3);
439                                 $output = substr_replace($output, '', strrpos($output, '/'));
440                         }
441                         elseif ($input === '/..')
442                         {
443                                 $input = '/';
444                                 $output = substr_replace($output, '', strrpos($output, '/'));
445                         }
446                         // D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
447                         elseif ($input === '.' || $input === '..')
448                         {
449                                 $input = '';
450                         }
451                         // 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
452                         elseif (($pos = strpos($input, '/', 1)) !== false)
453                         {
454                                 $output .= substr($input, 0, $pos);
455                                 $input = substr_replace($input, '', 0, $pos);
456                         }
457                         else
458                         {
459                                 $output .= $input;
460                                 $input = '';
461                         }
462                 }
463                 return $output . $input;
464         }
466         /**
467          * Replace invalid character with percent encoding
468          *
469          * @param string $string Input string
470          * @param string $extra_chars Valid characters not in iunreserved or
471          *                            iprivate (this is ASCII-only)
472          * @param bool $iprivate Allow iprivate
473          * @return string
474          */
475         protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false)
476         {
477                 // Normalize as many pct-encoded sections as possible
478                 $string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string);
480                 // Replace invalid percent characters
481                 $string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
483                 // Add unreserved and % to $extra_chars (the latter is safe because all
484                 // pct-encoded sections are now valid).
485                 $extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';
487                 // Now replace any bytes that aren't allowed with their pct-encoded versions
488                 $position = 0;
489                 $strlen = strlen($string);
490                 while (($position += strspn($string, $extra_chars, $position)) < $strlen)
491                 {
492                         $value = ord($string[$position]);
494                         // Start position
495                         $start = $position;
497                         // By default we are valid
498                         $valid = true;
500                         // No one byte sequences are valid due to the while.
501                         // Two byte sequence:
502                         if (($value & 0xE0) === 0xC0)
503                         {
504                                 $character = ($value & 0x1F) << 6;
505                                 $length = 2;
506                                 $remaining = 1;
507                         }
508                         // Three byte sequence:
509                         elseif (($value & 0xF0) === 0xE0)
510                         {
511                                 $character = ($value & 0x0F) << 12;
512                                 $length = 3;
513                                 $remaining = 2;
514                         }
515                         // Four byte sequence:
516                         elseif (($value & 0xF8) === 0xF0)
517                         {
518                                 $character = ($value & 0x07) << 18;
519                                 $length = 4;
520                                 $remaining = 3;
521                         }
522                         // Invalid byte:
523                         else
524                         {
525                                 $valid = false;
526                                 $length = 1;
527                                 $remaining = 0;
528                         }
530                         if ($remaining)
531                         {
532                                 if ($position + $length <= $strlen)
533                                 {
534                                         for ($position++; $remaining; $position++)
535                                         {
536                                                 $value = ord($string[$position]);
538                                                 // Check that the byte is valid, then add it to the character:
539                                                 if (($value & 0xC0) === 0x80)
540                                                 {
541                                                         $character |= ($value & 0x3F) << (--$remaining * 6);
542                                                 }
543                                                 // If it is invalid, count the sequence as invalid and reprocess the current byte:
544                                                 else
545                                                 {
546                                                         $valid = false;
547                                                         $position--;
548                                                         break;
549                                                 }
550                                         }
551                                 }
552                                 else
553                                 {
554                                         $position = $strlen - 1;
555                                         $valid = false;
556                                 }
557                         }
559                         // Percent encode anything invalid or not in ucschar
560                         if (
561                                 // Invalid sequences
562                                 !$valid
563                                 // Non-shortest form sequences are invalid
564                                 || $length > 1 && $character <= 0x7F
565                                 || $length > 2 && $character <= 0x7FF
566                                 || $length > 3 && $character <= 0xFFFF
567                                 // Outside of range of ucschar codepoints
568                                 // Noncharacters
569                                 || ($character & 0xFFFE) === 0xFFFE
570                                 || $character >= 0xFDD0 && $character <= 0xFDEF
571                                 || (
572                                         // Everything else not in ucschar
573                                            $character > 0xD7FF && $character < 0xF900
574                                         || $character < 0xA0
575                                         || $character > 0xEFFFD
576                                 )
577                                 && (
578                                         // Everything not in iprivate, if it applies
579                                            !$iprivate
580                                         || $character < 0xE000
581                                         || $character > 0x10FFFD
582                                 )
583                         )
584                         {
585                                 // If we were a character, pretend we weren't, but rather an error.
586                                 if ($valid)
587                                         $position--;
589                                 for ($j = $start; $j <= $position; $j++)
590                                 {
591                                         $string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
592                                         $j += 2;
593                                         $position += 2;
594                                         $strlen += 2;
595                                 }
596                         }
597                 }
599                 return $string;
600         }
602         /**
603          * Callback function for preg_replace_callback.
604          *
605          * Removes sequences of percent encoded bytes that represent UTF-8
606          * encoded characters in iunreserved
607          *
608          * @param array $match PCRE match
609          * @return string Replacement
610          */
611         protected function remove_iunreserved_percent_encoded($match)
612         {
613                 // As we just have valid percent encoded sequences we can just explode
614                 // and ignore the first member of the returned array (an empty string).
615                 $bytes = explode('%', $match[0]);
617                 // Initialize the new string (this is what will be returned) and that
618                 // there are no bytes remaining in the current sequence (unsurprising
619                 // at the first byte!).
620                 $string = '';
621                 $remaining = 0;
623                 // Loop over each and every byte, and set $value to its value
624                 for ($i = 1, $len = count($bytes); $i < $len; $i++)
625                 {
626                         $value = hexdec($bytes[$i]);
628                         // If we're the first byte of sequence:
629                         if (!$remaining)
630                         {
631                                 // Start position
632                                 $start = $i;
634                                 // By default we are valid
635                                 $valid = true;
637                                 // One byte sequence:
638                                 if ($value <= 0x7F)
639                                 {
640                                         $character = $value;
641                                         $length = 1;
642                                 }
643                                 // Two byte sequence:
644                                 elseif (($value & 0xE0) === 0xC0)
645                                 {
646                                         $character = ($value & 0x1F) << 6;
647                                         $length = 2;
648                                         $remaining = 1;
649                                 }
650                                 // Three byte sequence:
651                                 elseif (($value & 0xF0) === 0xE0)
652                                 {
653                                         $character = ($value & 0x0F) << 12;
654                                         $length = 3;
655                                         $remaining = 2;
656                                 }
657                                 // Four byte sequence:
658                                 elseif (($value & 0xF8) === 0xF0)
659                                 {
660                                         $character = ($value & 0x07) << 18;
661                                         $length = 4;
662                                         $remaining = 3;
663                                 }
664                                 // Invalid byte:
665                                 else
666                                 {
667                                         $valid = false;
668                                         $remaining = 0;
669                                 }
670                         }
671                         // Continuation byte:
672                         else
673                         {
674                                 // Check that the byte is valid, then add it to the character:
675                                 if (($value & 0xC0) === 0x80)
676                                 {
677                                         $remaining--;
678                                         $character |= ($value & 0x3F) << ($remaining * 6);
679                                 }
680                                 // If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
681                                 else
682                                 {
683                                         $valid = false;
684                                         $remaining = 0;
685                                         $i--;
686                                 }
687                         }
689                         // If we've reached the end of the current byte sequence, append it to Unicode::$data
690                         if (!$remaining)
691                         {
692                                 // Percent encode anything invalid or not in iunreserved
693                                 if (
694                                         // Invalid sequences
695                                         !$valid
696                                         // Non-shortest form sequences are invalid
697                                         || $length > 1 && $character <= 0x7F
698                                         || $length > 2 && $character <= 0x7FF
699                                         || $length > 3 && $character <= 0xFFFF
700                                         // Outside of range of iunreserved codepoints
701                                         || $character < 0x2D
702                                         || $character > 0xEFFFD
703                                         // Noncharacters
704                                         || ($character & 0xFFFE) === 0xFFFE
705                                         || $character >= 0xFDD0 && $character <= 0xFDEF
706                                         // Everything else not in iunreserved (this is all BMP)
707                                         || $character === 0x2F
708                                         || $character > 0x39 && $character < 0x41
709                                         || $character > 0x5A && $character < 0x61
710                                         || $character > 0x7A && $character < 0x7E
711                                         || $character > 0x7E && $character < 0xA0
712                                         || $character > 0xD7FF && $character < 0xF900
713                                 )
714                                 {
715                                         for ($j = $start; $j <= $i; $j++)
716                                         {
717                                                 $string .= '%' . strtoupper($bytes[$j]);
718                                         }
719                                 }
720                                 else
721                                 {
722                                         for ($j = $start; $j <= $i; $j++)
723                                         {
724                                                 $string .= chr(hexdec($bytes[$j]));
725                                         }
726                                 }
727                         }
728                 }
730                 // If we have any bytes left over they are invalid (i.e., we are
731                 // mid-way through a multi-byte sequence)
732                 if ($remaining)
733                 {
734                         for ($j = $start; $j < $len; $j++)
735                         {
736                                 $string .= '%' . strtoupper($bytes[$j]);
737                         }
738                 }
740                 return $string;
741         }
743         protected function scheme_normalization()
744         {
745                 if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo'])
746                 {
747                         $this->iuserinfo = null;
748                 }
749                 if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost'])
750                 {
751                         $this->ihost = null;
752                 }
753                 if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port'])
754                 {
755                         $this->port = null;
756                 }
757                 if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath'])
758                 {
759                         $this->ipath = '';
760                 }
761                 if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery'])
762                 {
763                         $this->iquery = null;
764                 }
765                 if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment'])
766                 {
767                         $this->ifragment = null;
768                 }
769         }
771         /**
772          * Check if the object represents a valid IRI. This needs to be done on each
773          * call as some things change depending on another part of the IRI.
774          *
775          * @return bool
776          */
777         public function is_valid()
778         {
779                 if ($this->ipath === '') return true;
781                 $isauthority = $this->iuserinfo !== null || $this->ihost !== null ||
782                         $this->port !== null;
783                 if ($isauthority && $this->ipath[0] === '/') return true;
785                 if (!$isauthority && (substr($this->ipath, 0, 2) === '//')) return false;
787                 // Relative urls cannot have a colon in the first path segment (and the
788                 // slashes themselves are not included so skip the first character).
789                 if (!$this->scheme && !$isauthority &&
790                     strpos($this->ipath, ':') !== false &&
791                     strpos($this->ipath, '/', 1) !== false &&
792                     strpos($this->ipath, ':') < strpos($this->ipath, '/', 1)) return false;
794                 return true;
795         }
797         /**
798          * Set the entire IRI. Returns true on success, false on failure (if there
799          * are any invalid characters).
800          *
801          * @param string $iri
802          * @return bool
803          */
804         public function set_iri($iri, $clear_cache = false)
805         {
806                 static $cache;
807                 if ($clear_cache) 
808                 {
809                         $cache = null;
810                         return;
811                 }
812                 if (!$cache)
813                 {
814                         $cache = array();
815                 }
817                 if ($iri === null)
818                 {
819                         return true;
820                 }
821                 elseif (isset($cache[$iri]))
822                 {
823                         list($this->scheme,
824                                  $this->iuserinfo,
825                                  $this->ihost,
826                                  $this->port,
827                                  $this->ipath,
828                                  $this->iquery,
829                                  $this->ifragment,
830                                  $return) = $cache[$iri];
831                         return $return;
832                 }
833                 else
834                 {
835                         $parsed = $this->parse_iri((string) $iri);
836                         if (!$parsed)
837                         {
838                                 return false;
839                         }
841                         $return = $this->set_scheme($parsed['scheme'])
842                                 && $this->set_authority($parsed['authority'])
843                                 && $this->set_path($parsed['path'])
844                                 && $this->set_query($parsed['query'])
845                                 && $this->set_fragment($parsed['fragment']);
847                         $cache[$iri] = array($this->scheme,
848                                                                  $this->iuserinfo,
849                                                                  $this->ihost,
850                                                                  $this->port,
851                                                                  $this->ipath,
852                                                                  $this->iquery,
853                                                                  $this->ifragment,
854                                                                  $return);
855                         return $return;
856                 }
857         }
859         /**
860          * Set the scheme. Returns true on success, false on failure (if there are
861          * any invalid characters).
862          *
863          * @param string $scheme
864          * @return bool
865          */
866         public function set_scheme($scheme)
867         {
868                 if ($scheme === null)
869                 {
870                         $this->scheme = null;
871                 }
872                 elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme))
873                 {
874                         $this->scheme = null;
875                         return false;
876                 }
877                 else
878                 {
879                         $this->scheme = strtolower($scheme);
880                 }
881                 return true;
882         }
884         /**
885          * Set the authority. Returns true on success, false on failure (if there are
886          * any invalid characters).
887          *
888          * @param string $authority
889          * @return bool
890          */
891         public function set_authority($authority, $clear_cache = false)
892         {
893                 static $cache;
894                 if ($clear_cache)
895                 {
896                         $cache = null;
897                         return;
898                 }
899                 if (!$cache)
900                         $cache = array();
902                 if ($authority === null)
903                 {
904                         $this->iuserinfo = null;
905                         $this->ihost = null;
906                         $this->port = null;
907                         return true;
908                 }
909                 elseif (isset($cache[$authority]))
910                 {
911                         list($this->iuserinfo,
912                                  $this->ihost,
913                                  $this->port,
914                                  $return) = $cache[$authority];
916                         return $return;
917                 }
918                 else
919                 {
920                         $remaining = $authority;
921                         if (($iuserinfo_end = strrpos($remaining, '@')) !== false)
922                         {
923                                 $iuserinfo = substr($remaining, 0, $iuserinfo_end);
924                                 $remaining = substr($remaining, $iuserinfo_end + 1);
925                         }
926                         else
927                         {
928                                 $iuserinfo = null;
929                         }
930                         if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false)
931                         {
932                                 if (($port = substr($remaining, $port_start + 1)) === false)
933                                 {
934                                         $port = null;
935                                 }
936                                 $remaining = substr($remaining, 0, $port_start);
937                         }
938                         else
939                         {
940                                 $port = null;
941                         }
943                         $return = $this->set_userinfo($iuserinfo) &&
944                                           $this->set_host($remaining) &&
945                                           $this->set_port($port);
947                         $cache[$authority] = array($this->iuserinfo,
948                                                                            $this->ihost,
949                                                                            $this->port,
950                                                                            $return);
952                         return $return;
953                 }
954         }
956         /**
957          * Set the iuserinfo.
958          *
959          * @param string $iuserinfo
960          * @return bool
961          */
962         public function set_userinfo($iuserinfo)
963         {
964                 if ($iuserinfo === null)
965                 {
966                         $this->iuserinfo = null;
967                 }
968                 else
969                 {
970                         $this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
971                         $this->scheme_normalization();
972                 }
974                 return true;
975         }
977         /**
978          * Set the ihost. Returns true on success, false on failure (if there are
979          * any invalid characters).
980          *
981          * @param string $ihost
982          * @return bool
983          */
984         public function set_host($ihost)
985         {
986                 if ($ihost === null)
987                 {
988                         $this->ihost = null;
989                         return true;
990                 }
991                 elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']')
992                 {
993                         if (SimplePie_Net_IPv6::check_ipv6(substr($ihost, 1, -1)))
994                         {
995                                 $this->ihost = '[' . SimplePie_Net_IPv6::compress(substr($ihost, 1, -1)) . ']';
996                         }
997                         else
998                         {
999                                 $this->ihost = null;
1000                                 return false;
1001                         }
1002                 }
1003                 else
1004                 {
1005                         $ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');
1007                         // Lowercase, but ignore pct-encoded sections (as they should
1008                         // remain uppercase). This must be done after the previous step
1009                         // as that can add unescaped characters.
1010                         $position = 0;
1011                         $strlen = strlen($ihost);
1012                         while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen)
1013                         {
1014                                 if ($ihost[$position] === '%')
1015                                 {
1016                                         $position += 3;
1017                                 }
1018                                 else
1019                                 {
1020                                         $ihost[$position] = strtolower($ihost[$position]);
1021                                         $position++;
1022                                 }
1023                         }
1025                         $this->ihost = $ihost;
1026                 }
1028                 $this->scheme_normalization();
1030                 return true;
1031         }
1033         /**
1034          * Set the port. Returns true on success, false on failure (if there are
1035          * any invalid characters).
1036          *
1037          * @param string $port
1038          * @return bool
1039          */
1040         public function set_port($port)
1041         {
1042                 if ($port === null)
1043                 {
1044                         $this->port = null;
1045                         return true;
1046                 }
1047                 elseif (strspn($port, '0123456789') === strlen($port))
1048                 {
1049                         $this->port = (int) $port;
1050                         $this->scheme_normalization();
1051                         return true;
1052                 }
1053                 else
1054                 {
1055                         $this->port = null;
1056                         return false;
1057                 }
1058         }
1060         /**
1061          * Set the ipath.
1062          *
1063          * @param string $ipath
1064          * @return bool
1065          */
1066         public function set_path($ipath, $clear_cache = false)
1067         {
1068                 static $cache;
1069                 if ($clear_cache) 
1070                 {
1071                         $cache = null;
1072                         return;
1073                 }
1074                 if (!$cache)
1075                 {
1076                         $cache = array();
1077                 }
1079                 $ipath = (string) $ipath;
1081                 if (isset($cache[$ipath]))
1082                 {
1083                         $this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
1084                 }
1085                 else
1086                 {
1087                         $valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
1088                         $removed = $this->remove_dot_segments($valid);
1090                         $cache[$ipath] = array($valid, $removed);
1091                         $this->ipath =  ($this->scheme !== null) ? $removed : $valid;
1092                 }
1094                 $this->scheme_normalization();
1095                 return true;
1096         }
1098         /**
1099          * Set the iquery.
1100          *
1101          * @param string $iquery
1102          * @return bool
1103          */
1104         public function set_query($iquery)
1105         {
1106                 if ($iquery === null)
1107                 {
1108                         $this->iquery = null;
1109                 }
1110                 else
1111                 {
1112                         $this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
1113                         $this->scheme_normalization();
1114                 }
1115                 return true;
1116         }
1118         /**
1119          * Set the ifragment.
1120          *
1121          * @param string $ifragment
1122          * @return bool
1123          */
1124         public function set_fragment($ifragment)
1125         {
1126                 if ($ifragment === null)
1127                 {
1128                         $this->ifragment = null;
1129                 }
1130                 else
1131                 {
1132                         $this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
1133                         $this->scheme_normalization();
1134                 }
1135                 return true;
1136         }
1138         /**
1139          * Convert an IRI to a URI (or parts thereof)
1140          *
1141          * @return string
1142          */
1143         public function to_uri($string)
1144         {
1145                 static $non_ascii;
1146                 if (!$non_ascii)
1147                 {
1148                         $non_ascii = implode('', range("\x80", "\xFF"));
1149                 }
1151                 $position = 0;
1152                 $strlen = strlen($string);
1153                 while (($position += strcspn($string, $non_ascii, $position)) < $strlen)
1154                 {
1155                         $string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
1156                         $position += 3;
1157                         $strlen += 2;
1158                 }
1160                 return $string;
1161         }
1163         /**
1164          * Get the complete IRI
1165          *
1166          * @return string
1167          */
1168         public function get_iri()
1169         {
1170                 if (!$this->is_valid())
1171                 {
1172                         return false;
1173                 }
1175                 $iri = '';
1176                 if ($this->scheme !== null)
1177                 {
1178                         $iri .= $this->scheme . ':';
1179                 }
1180                 if (($iauthority = $this->get_iauthority()) !== null)
1181                 {
1182                         $iri .= '//' . $iauthority;
1183                 }
1184                 if ($this->ipath !== '')
1185                 {
1186                         $iri .= $this->ipath;
1187                 }
1188                 elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '')
1189                 {
1190                         $iri .= $this->normalization[$this->scheme]['ipath'];
1191                 }
1192                 if ($this->iquery !== null)
1193                 {
1194                         $iri .= '?' . $this->iquery;
1195                 }
1196                 if ($this->ifragment !== null)
1197                 {
1198                         $iri .= '#' . $this->ifragment;
1199                 }
1201                 return $iri;
1202         }
1204         /**
1205          * Get the complete URI
1206          *
1207          * @return string
1208          */
1209         public function get_uri()
1210         {
1211                 return $this->to_uri($this->get_iri());
1212         }
1214         /**
1215          * Get the complete iauthority
1216          *
1217          * @return string
1218          */
1219         protected function get_iauthority()
1220         {
1221                 if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null)
1222                 {
1223                         $iauthority = '';
1224                         if ($this->iuserinfo !== null)
1225                         {
1226                                 $iauthority .= $this->iuserinfo . '@';
1227                         }
1228                         if ($this->ihost !== null)
1229                         {
1230                                 $iauthority .= $this->ihost;
1231                         }
1232                         if ($this->port !== null)
1233                         {
1234                                 $iauthority .= ':' . $this->port;
1235                         }
1236                         return $iauthority;
1237                 }
1238                 else
1239                 {
1240                         return null;
1241                 }
1242         }
1244         /**
1245          * Get the complete authority
1246          *
1247          * @return string
1248          */
1249         protected function get_authority()
1250         {
1251                 $iauthority = $this->get_iauthority();
1252                 if (is_string($iauthority))
1253                         return $this->to_uri($iauthority);
1254                 else
1255                         return $iauthority;
1256         }