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