Completed
Push — 1.11.x ( 518476...344d9e )
by José
55:02 queued 28:13
created

Requests_IRI::remove_iunreserved_percent_encoded()   D

Complexity

Conditions 36
Paths 44

Size

Total Lines 113
Code Lines 60

Duplication

Lines 34
Ratio 30.09 %

Importance

Changes 0
Metric Value
cc 36
eloc 60
nc 44
nop 1
dl 34
loc 113
rs 4.273
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 View Code Duplication
			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 View Code Duplication
			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 View Code Duplication
			elseif (($value & 0xF0) === 0xE0) {
452
				$character = ($value & 0x0F) << 12;
453
				$length = 3;
454
				$remaining = 2;
455
			}
456
			// Four byte sequence:
457 View Code Duplication
			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 View Code Duplication
						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 View Code Duplication
				elseif (($value & 0xE0) === 0xC0) {
573
					$character = ($value & 0x1F) << 6;
574
					$length = 2;
575
					$remaining = 1;
576
				}
577
				// Three byte sequence:
578 View Code Duplication
				elseif (($value & 0xF0) === 0xE0) {
579
					$character = ($value & 0x0F) << 12;
580
					$length = 3;
581
					$remaining = 2;
582
				}
583
				// Four byte sequence:
584 View Code Duplication
				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 View Code Duplication
				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 View Code Duplication
					for ($j = $start; $j <= $i; $j++) {
635
						$string .= '%' . strtoupper($bytes[$j]);
636
					}
637
				}
638
				else {
639 View Code Duplication
					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 View Code Duplication
			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 View Code Duplication
		if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
659
			$this->iuserinfo = null;
660
		}
661 View Code Duplication
		if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
662
			$this->ihost = null;
663
		}
664 View Code Duplication
		if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
665
			$this->port = null;
666
		}
667 View Code Duplication
		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 View Code Duplication
		if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
674
			$this->iquery = null;
675
		}
676 View Code Duplication
		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 && $this->ipath[0] !== '/' ||
692
				(
693
					$this->scheme === null &&
694
					!$isauthority &&
695
					strpos($this->ipath, ':') !== false &&
696
					(strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/'))
697
				)
698
			)
699
		) {
700
			return false;
701
		}
702
703
		return true;
704
	}
705
706
	/**
707
	 * Set the entire IRI. Returns true on success, false on failure (if there
708
	 * are any invalid characters).
709
	 *
710
	 * @param string $iri
711
	 * @return bool
712
	 */
713
	protected function set_iri($iri) {
714
		static $cache;
715
		if (!$cache) {
716
			$cache = array();
717
		}
718
719
		if ($iri === null) {
720
			return true;
721
		}
722
		if (isset($cache[$iri])) {
723
			list($this->scheme,
724
				 $this->iuserinfo,
725
				 $this->ihost,
726
				 $this->port,
727
				 $this->ipath,
728
				 $this->iquery,
729
				 $this->ifragment,
730
				 $return) = $cache[$iri];
731
			return $return;
732
		}
733
734
		$parsed = $this->parse_iri((string) $iri);
735
736
		$return = $this->set_scheme($parsed['scheme'])
737
			&& $this->set_authority($parsed['authority'])
738
			&& $this->set_path($parsed['path'])
739
			&& $this->set_query($parsed['query'])
740
			&& $this->set_fragment($parsed['fragment']);
741
742
		$cache[$iri] = array($this->scheme,
743
							 $this->iuserinfo,
744
							 $this->ihost,
745
							 $this->port,
746
							 $this->ipath,
747
							 $this->iquery,
748
							 $this->ifragment,
749
							 $return);
750
		return $return;
751
	}
752
753
	/**
754
	 * Set the scheme. Returns true on success, false on failure (if there are
755
	 * any invalid characters).
756
	 *
757
	 * @param string $scheme
758
	 * @return bool
759
	 */
760
	protected function set_scheme($scheme) {
761
		if ($scheme === null) {
762
			$this->scheme = null;
763
		}
764
		elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) {
765
			$this->scheme = null;
766
			return false;
767
		}
768
		else {
769
			$this->scheme = strtolower($scheme);
770
		}
771
		return true;
772
	}
773
774
	/**
775
	 * Set the authority. Returns true on success, false on failure (if there are
776
	 * any invalid characters).
777
	 *
778
	 * @param string $authority
779
	 * @return bool
780
	 */
781
	protected function set_authority($authority) {
782
		static $cache;
783
		if (!$cache) {
784
			$cache = array();
785
		}
786
787
		if ($authority === null) {
788
			$this->iuserinfo = null;
789
			$this->ihost = null;
790
			$this->port = null;
791
			return true;
792
		}
793
		if (isset($cache[$authority])) {
794
			list($this->iuserinfo,
795
				 $this->ihost,
796
				 $this->port,
797
				 $return) = $cache[$authority];
798
799
			return $return;
800
		}
801
802
		$remaining = $authority;
803
		if (($iuserinfo_end = strrpos($remaining, '@')) !== false) {
804
			$iuserinfo = substr($remaining, 0, $iuserinfo_end);
805
			$remaining = substr($remaining, $iuserinfo_end + 1);
806
		}
807
		else {
808
			$iuserinfo = null;
809
		}
810
		if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) {
811
			$port = substr($remaining, $port_start + 1);
812
			if ($port === false || $port === '') {
813
				$port = null;
814
			}
815
			$remaining = substr($remaining, 0, $port_start);
816
		}
817
		else {
818
			$port = null;
819
		}
820
821
		$return = $this->set_userinfo($iuserinfo) &&
822
				  $this->set_host($remaining) &&
823
				  $this->set_port($port);
824
825
		$cache[$authority] = array($this->iuserinfo,
826
								   $this->ihost,
827
								   $this->port,
828
								   $return);
829
830
		return $return;
831
	}
832
833
	/**
834
	 * Set the iuserinfo.
835
	 *
836
	 * @param string $iuserinfo
837
	 * @return bool
838
	 */
839 View Code Duplication
	protected function set_userinfo($iuserinfo) {
840
		if ($iuserinfo === null) {
841
			$this->iuserinfo = null;
842
		}
843
		else {
844
			$this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
845
			$this->scheme_normalization();
846
		}
847
848
		return true;
849
	}
850
851
	/**
852
	 * Set the ihost. Returns true on success, false on failure (if there are
853
	 * any invalid characters).
854
	 *
855
	 * @param string $ihost
856
	 * @return bool
857
	 */
858
	protected function set_host($ihost) {
859
		if ($ihost === null) {
860
			$this->ihost = null;
861
			return true;
862
		}
863
		if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
864
			if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
865
				$this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']';
866
			}
867
			else {
868
				$this->ihost = null;
869
				return false;
870
			}
871
		}
872
		else {
873
			$ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');
874
875
			// Lowercase, but ignore pct-encoded sections (as they should
876
			// remain uppercase). This must be done after the previous step
877
			// as that can add unescaped characters.
878
			$position = 0;
879
			$strlen = strlen($ihost);
880
			while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) {
881
				if ($ihost[$position] === '%') {
882
					$position += 3;
883
				}
884
				else {
885
					$ihost[$position] = strtolower($ihost[$position]);
886
					$position++;
887
				}
888
			}
889
890
			$this->ihost = $ihost;
891
		}
892
893
		$this->scheme_normalization();
894
895
		return true;
896
	}
897
898
	/**
899
	 * Set the port. Returns true on success, false on failure (if there are
900
	 * any invalid characters).
901
	 *
902
	 * @param string $port
903
	 * @return bool
904
	 */
905
	protected function set_port($port) {
906
		if ($port === null) {
907
			$this->port = null;
908
			return true;
909
		}
910
911
		if (strspn($port, '0123456789') === strlen($port)) {
912
			$this->port = (int) $port;
0 ignored issues
show
Documentation Bug introduced by
The property $port was declared of type string, but (int) $port is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
913
			$this->scheme_normalization();
914
			return true;
915
		}
916
917
		$this->port = null;
918
		return false;
919
	}
920
921
	/**
922
	 * Set the ipath.
923
	 *
924
	 * @param string $ipath
925
	 * @return bool
926
	 */
927
	protected function set_path($ipath) {
928
		static $cache;
929
		if (!$cache) {
930
			$cache = array();
931
		}
932
933
		$ipath = (string) $ipath;
934
935
		if (isset($cache[$ipath])) {
936
			$this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
937
		}
938
		else {
939
			$valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
940
			$removed = $this->remove_dot_segments($valid);
941
942
			$cache[$ipath] = array($valid, $removed);
943
			$this->ipath = ($this->scheme !== null) ? $removed : $valid;
944
		}
945
		$this->scheme_normalization();
946
		return true;
947
	}
948
949
	/**
950
	 * Set the iquery.
951
	 *
952
	 * @param string $iquery
953
	 * @return bool
954
	 */
955 View Code Duplication
	protected function set_query($iquery) {
956
		if ($iquery === null) {
957
			$this->iquery = null;
958
		}
959
		else {
960
			$this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
961
			$this->scheme_normalization();
962
		}
963
		return true;
964
	}
965
966
	/**
967
	 * Set the ifragment.
968
	 *
969
	 * @param string $ifragment
970
	 * @return bool
971
	 */
972 View Code Duplication
	protected function set_fragment($ifragment) {
973
		if ($ifragment === null) {
974
			$this->ifragment = null;
975
		}
976
		else {
977
			$this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
978
			$this->scheme_normalization();
979
		}
980
		return true;
981
	}
982
983
	/**
984
	 * Convert an IRI to a URI (or parts thereof)
985
	 *
986
	 * @param string|bool IRI to convert (or false from {@see get_iri})
987
	 * @return string|false URI if IRI is valid, false otherwise.
988
	 */
989
	protected function to_uri($string) {
990
		if (!is_string($string)) {
991
			return false;
992
		}
993
994
		static $non_ascii;
995
		if (!$non_ascii) {
996
			$non_ascii = implode('', range("\x80", "\xFF"));
997
		}
998
999
		$position = 0;
1000
		$strlen = strlen($string);
1001
		while (($position += strcspn($string, $non_ascii, $position)) < $strlen) {
1002
			$string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
1003
			$position += 3;
1004
			$strlen += 2;
1005
		}
1006
1007
		return $string;
1008
	}
1009
1010
	/**
1011
	 * Get the complete IRI
1012
	 *
1013
	 * @return string
1014
	 */
1015
	protected function get_iri() {
1016
		if (!$this->is_valid()) {
1017
			return false;
1018
		}
1019
1020
		$iri = '';
1021
		if ($this->scheme !== null) {
1022
			$iri .= $this->scheme . ':';
1023
		}
1024
		if (($iauthority = $this->get_iauthority()) !== null) {
1025
			$iri .= '//' . $iauthority;
1026
		}
1027
		$iri .= $this->ipath;
1028
		if ($this->iquery !== null) {
1029
			$iri .= '?' . $this->iquery;
1030
		}
1031
		if ($this->ifragment !== null) {
1032
			$iri .= '#' . $this->ifragment;
1033
		}
1034
1035
		return $iri;
1036
	}
1037
1038
	/**
1039
	 * Get the complete URI
1040
	 *
1041
	 * @return string
1042
	 */
1043
	protected function get_uri() {
1044
		return $this->to_uri($this->get_iri());
1045
	}
1046
1047
	/**
1048
	 * Get the complete iauthority
1049
	 *
1050
	 * @return string
1051
	 */
1052
	protected function get_iauthority() {
1053
		if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) {
1054
			return null;
1055
		}
1056
1057
		$iauthority = '';
1058
		if ($this->iuserinfo !== null) {
1059
			$iauthority .= $this->iuserinfo . '@';
1060
		}
1061
		if ($this->ihost !== null) {
1062
			$iauthority .= $this->ihost;
1063
		}
1064
		if ($this->port !== null) {
1065
			$iauthority .= ':' . $this->port;
1066
		}
1067
		return $iauthority;
1068
	}
1069
1070
	/**
1071
	 * Get the complete authority
1072
	 *
1073
	 * @return string
1074
	 */
1075
	protected function get_authority() {
1076
		$iauthority = $this->get_iauthority();
1077
		if (is_string($iauthority)) {
1078
			return $this->to_uri($iauthority);
1079
		}
1080
		else {
1081
			return $iauthority;
1082
		}
1083
	}
1084
}
1085