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