Requests_IRI   F
last analyzed

Complexity

Total Complexity 221

Size/Duplication

Total Lines 1021
Duplicated Lines 10.77 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 221
lcom 1
cbo 2
dl 110
loc 1021
rs 1.516
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __toString() 0 3 1
B __set() 0 15 8
C __get() 0 39 12
A __isset() 0 3 2
A __unset() 0 5 2
A __construct() 0 3 1
D absolutize() 0 64 20
B parse_iri() 0 24 10
C remove_dot_segments() 8 52 14
F replace_invalid_with_pct_encoding() 19 113 28
F remove_iunreserved_percent_encoded() 34 113 36
C scheme_normalization() 18 23 15
B is_valid() 0 18 11
B set_iri() 0 39 8
A set_scheme() 0 13 3
B set_authority() 0 51 10
A set_userinfo() 11 11 2
B set_host() 0 39 7
A set_port() 0 15 3
A set_path() 0 21 4
A set_query() 10 10 2
A set_fragment() 10 10 2
A to_uri() 0 20 4
B get_iri() 0 22 6
A get_uri() 0 3 1
B get_iauthority() 0 17 7
A get_authority() 0 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Requests_IRI often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Requests_IRI, and based on these observations, apply Extract Interface, too.

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|null
71
	 */
72
	protected $scheme = null;
73
74
	/**
75
	 * User Information
76
	 *
77
	 * @var string|null
78
	 */
79
	protected $iuserinfo = null;
80
81
	/**
82
	 * ihost
83
	 *
84
	 * @var string|null
85
	 */
86
	protected $ihost = null;
87
88
	/**
89
	 * Port
90
	 *
91
	 * @var string|null
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|null
106
	 */
107
	protected $iquery = null;
108
109
	/**
110
	 * ifragment|null
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
	 * @var array
123
	 */
124
	protected $normalization = array(
125
		'acap' => array(
126
			'port' => 674
127
		),
128
		'dict' => array(
129
			'port' => 2628
130
		),
131
		'file' => array(
132
			'ihost' => 'localhost'
133
		),
134
		'http' => array(
135
			'port' => 80,
136
		),
137
		'https' => array(
138
			'port' => 443,
139
		),
140
	);
141
142
	/**
143
	 * Return the entire IRI when you try and read the object as a string
144
	 *
145
	 * @return string
146
	 */
147
	public function __toString() {
148
		return $this->get_iri();
149
	}
150
151
	/**
152
	 * Overload __set() to provide access via properties
153
	 *
154
	 * @param string $name Property name
155
	 * @param mixed $value Property value
156
	 */
157
	public function __set($name, $value) {
158
		if (method_exists($this, 'set_' . $name)) {
159
			call_user_func(array($this, 'set_' . $name), $value);
160
		}
161
		elseif (
162
			   $name === 'iauthority'
163
			|| $name === 'iuserinfo'
164
			|| $name === 'ihost'
165
			|| $name === 'ipath'
166
			|| $name === 'iquery'
167
			|| $name === 'ifragment'
168
		) {
169
			call_user_func(array($this, 'set_' . substr($name, 1)), $value);
170
		}
171
	}
172
173
	/**
174
	 * Overload __get() to provide access via properties
175
	 *
176
	 * @param string $name Property name
177
	 * @return mixed
178
	 */
179
	public function __get($name) {
180
		// isset() returns false for null, we don't want to do that
181
		// Also why we use array_key_exists below instead of isset()
182
		$props = get_object_vars($this);
183
184
		if (
185
			$name === 'iri' ||
186
			$name === 'uri' ||
187
			$name === 'iauthority' ||
188
			$name === 'authority'
189
		) {
190
			$method = 'get_' . $name;
191
			$return = $this->$method();
192
		}
193
		elseif (array_key_exists($name, $props)) {
194
			$return = $this->$name;
195
		}
196
		// host -> ihost
197
		elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) {
198
			$name = $prop;
199
			$return = $this->$prop;
200
		}
201
		// ischeme -> scheme
202
		elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) {
203
			$name = $prop;
204
			$return = $this->$prop;
205
		}
206
		else {
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
			return $this->normalization[$this->scheme][$name];
213
		}
214
		else {
215
			return $return;
216
		}
217
	}
218
219
	/**
220
	 * Overload __isset() to provide access via properties
221
	 *
222
	 * @param string $name Property name
223
	 * @return bool
224
	 */
225
	public function __isset($name) {
226
		return (method_exists($this, 'get_' . $name) || isset($this->$name));
227
	}
228
229
	/**
230
	 * Overload __unset() to provide access via properties
231
	 *
232
	 * @param string $name Property name
233
	 */
234
	public function __unset($name) {
235
		if (method_exists($this, 'set_' . $name)) {
236
			call_user_func(array($this, 'set_' . $name), '');
237
		}
238
	}
239
240
	/**
241
	 * Create a new IRI object, from a specified string
242
	 *
243
	 * @param string|null $iri
244
	 */
245
	public function __construct($iri = null) {
246
		$this->set_iri($iri);
247
	}
248
249
	/**
250
	 * Create a new IRI object by resolving a relative IRI
251
	 *
252
	 * Returns false if $base is not absolute, otherwise an IRI.
253
	 *
254
	 * @param Requests_IRI|string $base (Absolute) Base IRI
255
	 * @param Requests_IRI|string $relative Relative IRI
256
	 * @return Requests_IRI|false
257
	 */
258
	public static function absolutize($base, $relative) {
259
		if (!($relative instanceof Requests_IRI)) {
260
			$relative = new Requests_IRI($relative);
261
		}
262
		if (!$relative->is_valid()) {
263
			return false;
264
		}
265
		elseif ($relative->scheme !== null) {
266
			return clone $relative;
267
		}
268
269
		if (!($base instanceof Requests_IRI)) {
270
			$base = new Requests_IRI($base);
271
		}
272
		if ($base->scheme === null || !$base->is_valid()) {
273
			return false;
274
		}
275
276
		if ($relative->get_iri() !== '') {
277
			if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) {
278
				$target = clone $relative;
279
				$target->scheme = $base->scheme;
280
			}
281
			else {
282
				$target = new Requests_IRI;
283
				$target->scheme = $base->scheme;
284
				$target->iuserinfo = $base->iuserinfo;
285
				$target->ihost = $base->ihost;
286
				$target->port = $base->port;
287
				if ($relative->ipath !== '') {
288
					if ($relative->ipath[0] === '/') {
289
						$target->ipath = $relative->ipath;
290
					}
291
					elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') {
292
						$target->ipath = '/' . $relative->ipath;
293
					}
294
					elseif (($last_segment = strrpos($base->ipath, '/')) !== false) {
295
						$target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
296
					}
297
					else {
298
						$target->ipath = $relative->ipath;
299
					}
300
					$target->ipath = $target->remove_dot_segments($target->ipath);
301
					$target->iquery = $relative->iquery;
302
				}
303
				else {
304
					$target->ipath = $base->ipath;
305
					if ($relative->iquery !== null) {
306
						$target->iquery = $relative->iquery;
307
					}
308
					elseif ($base->iquery !== null) {
309
						$target->iquery = $base->iquery;
310
					}
311
				}
312
				$target->ifragment = $relative->ifragment;
313
			}
314
		}
315
		else {
316
			$target = clone $base;
317
			$target->ifragment = null;
318
		}
319
		$target->scheme_normalization();
320
		return $target;
321
	}
322
323
	/**
324
	 * Parse an IRI into scheme/authority/path/query/fragment segments
325
	 *
326
	 * @param string $iri
327
	 * @return array
328
	 */
329
	protected function parse_iri($iri) {
330
		$iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
331
		$has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
332
		if (!$has_match) {
333
			throw new Requests_Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
334
		}
335
336
		if ($match[1] === '') {
337
			$match['scheme'] = null;
338
		}
339
		if (!isset($match[3]) || $match[3] === '') {
340
			$match['authority'] = null;
341
		}
342
		if (!isset($match[5])) {
343
			$match['path'] = '';
344
		}
345
		if (!isset($match[6]) || $match[6] === '') {
346
			$match['query'] = null;
347
		}
348
		if (!isset($match[8]) || $match[8] === '') {
349
			$match['fragment'] = null;
350
		}
351
		return $match;
352
	}
353
354
	/**
355
	 * Remove dot segments from a path
356
	 *
357
	 * @param string $input
358
	 * @return string
359
	 */
360
	protected function remove_dot_segments($input) {
361
		$output = '';
362
		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') {
363
			// A: If the input buffer begins with a prefix of "../" or "./",
364
			// then remove that prefix from the input buffer; otherwise,
365
			if (strpos($input, '../') === 0) {
366
				$input = substr($input, 3);
367
			}
368
			elseif (strpos($input, './') === 0) {
369
				$input = substr($input, 2);
370
			}
371
			// B: if the input buffer begins with a prefix of "/./" or "/.",
372
			// where "." is a complete path segment, then replace that prefix
373
			// with "/" in the input buffer; otherwise,
374
			elseif (strpos($input, '/./') === 0) {
375
				$input = substr($input, 2);
376
			}
377
			elseif ($input === '/.') {
378
				$input = '/';
379
			}
380
			// C: if the input buffer begins with a prefix of "/../" or "/..",
381
			// where ".." is a complete path segment, then replace that prefix
382
			// with "/" in the input buffer and remove the last segment and its
383
			// preceding "/" (if any) from the output buffer; otherwise,
384 View Code Duplication
			elseif (strpos($input, '/../') === 0) {
385
				$input = substr($input, 3);
386
				$output = substr_replace($output, '', strrpos($output, '/'));
387
			}
388
			elseif ($input === '/..') {
389
				$input = '/';
390
				$output = substr_replace($output, '', strrpos($output, '/'));
391
			}
392
			// D: if the input buffer consists only of "." or "..", then remove
393
			// that from the input buffer; otherwise,
394
			elseif ($input === '.' || $input === '..') {
395
				$input = '';
396
			}
397
			// E: move the first path segment in the input buffer to the end of
398
			// the output buffer, including the initial "/" character (if any)
399
			// and any subsequent characters up to, but not including, the next
400
			// "/" character or the end of the input buffer
401 View Code Duplication
			elseif (($pos = strpos($input, '/', 1)) !== false) {
402
				$output .= substr($input, 0, $pos);
403
				$input = substr_replace($input, '', 0, $pos);
404
			}
405
			else {
406
				$output .= $input;
407
				$input = '';
408
			}
409
		}
410
		return $output . $input;
411
	}
412
413
	/**
414
	 * Replace invalid character with percent encoding
415
	 *
416
	 * @param string $string Input string
417
	 * @param string $extra_chars Valid characters not in iunreserved or
418
	 *                            iprivate (this is ASCII-only)
419
	 * @param bool $iprivate Allow iprivate
420
	 * @return string
421
	 */
422
	protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false) {
423
		// Normalize as many pct-encoded sections as possible
424
		$string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string);
425
426
		// Replace invalid percent characters
427
		$string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);
428
429
		// Add unreserved and % to $extra_chars (the latter is safe because all
430
		// pct-encoded sections are now valid).
431
		$extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';
432
433
		// Now replace any bytes that aren't allowed with their pct-encoded versions
434
		$position = 0;
435
		$strlen = strlen($string);
436
		while (($position += strspn($string, $extra_chars, $position)) < $strlen) {
437
			$value = ord($string[$position]);
438
439
			// Start position
440
			$start = $position;
441
442
			// By default we are valid
443
			$valid = true;
444
445
			// No one byte sequences are valid due to the while.
446
			// Two byte sequence:
447
			if (($value & 0xE0) === 0xC0) {
448
				$character = ($value & 0x1F) << 6;
449
				$length = 2;
450
				$remaining = 1;
451
			}
452
			// Three byte sequence:
453 View Code Duplication
			elseif (($value & 0xF0) === 0xE0) {
454
				$character = ($value & 0x0F) << 12;
455
				$length = 3;
456
				$remaining = 2;
457
			}
458
			// Four byte sequence:
459 View Code Duplication
			elseif (($value & 0xF8) === 0xF0) {
460
				$character = ($value & 0x07) << 18;
461
				$length = 4;
462
				$remaining = 3;
463
			}
464
			// Invalid byte:
465
			else {
466
				$valid = false;
467
				$length = 1;
468
				$remaining = 0;
469
			}
470
471
			if ($remaining) {
472
				if ($position + $length <= $strlen) {
473
					for ($position++; $remaining; $position++) {
474
						$value = ord($string[$position]);
475
476
						// Check that the byte is valid, then add it to the character:
477 View Code Duplication
						if (($value & 0xC0) === 0x80) {
478
							$character |= ($value & 0x3F) << (--$remaining * 6);
479
						}
480
						// If it is invalid, count the sequence as invalid and reprocess the current byte:
481
						else {
482
							$valid = false;
483
							$position--;
484
							break;
485
						}
486
					}
487
				}
488
				else {
489
					$position = $strlen - 1;
490
					$valid = false;
491
				}
492
			}
493
494
			// Percent encode anything invalid or not in ucschar
495
			if (
496
				// Invalid sequences
497
				!$valid
498
				// Non-shortest form sequences are invalid
499
				|| $length > 1 && $character <= 0x7F
500
				|| $length > 2 && $character <= 0x7FF
501
				|| $length > 3 && $character <= 0xFFFF
502
				// Outside of range of ucschar codepoints
503
				// Noncharacters
504
				|| ($character & 0xFFFE) === 0xFFFE
505
				|| $character >= 0xFDD0 && $character <= 0xFDEF
506
				|| (
507
					// Everything else not in ucschar
508
					   $character > 0xD7FF && $character < 0xF900
509
					|| $character < 0xA0
510
					|| $character > 0xEFFFD
511
				)
512
				&& (
513
					// Everything not in iprivate, if it applies
514
					   !$iprivate
515
					|| $character < 0xE000
516
					|| $character > 0x10FFFD
517
				)
518
			) {
519
				// If we were a character, pretend we weren't, but rather an error.
520
				if ($valid) {
521
					$position--;
522
				}
523
524
				for ($j = $start; $j <= $position; $j++) {
525
					$string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
526
					$j += 2;
527
					$position += 2;
528
					$strlen += 2;
529
				}
530
			}
531
		}
532
533
		return $string;
534
	}
535
536
	/**
537
	 * Callback function for preg_replace_callback.
538
	 *
539
	 * Removes sequences of percent encoded bytes that represent UTF-8
540
	 * encoded characters in iunreserved
541
	 *
542
	 * @param array $match PCRE match
543
	 * @return string Replacement
544
	 */
545
	protected function remove_iunreserved_percent_encoded($match) {
546
		// As we just have valid percent encoded sequences we can just explode
547
		// and ignore the first member of the returned array (an empty string).
548
		$bytes = explode('%', $match[0]);
549
550
		// Initialize the new string (this is what will be returned) and that
551
		// there are no bytes remaining in the current sequence (unsurprising
552
		// at the first byte!).
553
		$string = '';
554
		$remaining = 0;
555
556
		// Loop over each and every byte, and set $value to its value
557
		for ($i = 1, $len = count($bytes); $i < $len; $i++) {
558
			$value = hexdec($bytes[$i]);
559
560
			// If we're the first byte of sequence:
561
			if (!$remaining) {
562
				// Start position
563
				$start = $i;
564
565
				// By default we are valid
566
				$valid = true;
567
568
				// One byte sequence:
569
				if ($value <= 0x7F) {
570
					$character = $value;
571
					$length = 1;
572
				}
573
				// Two byte sequence:
574 View Code Duplication
				elseif (($value & 0xE0) === 0xC0) {
575
					$character = ($value & 0x1F) << 6;
576
					$length = 2;
577
					$remaining = 1;
578
				}
579
				// Three byte sequence:
580 View Code Duplication
				elseif (($value & 0xF0) === 0xE0) {
581
					$character = ($value & 0x0F) << 12;
582
					$length = 3;
583
					$remaining = 2;
584
				}
585
				// Four byte sequence:
586 View Code Duplication
				elseif (($value & 0xF8) === 0xF0) {
587
					$character = ($value & 0x07) << 18;
588
					$length = 4;
589
					$remaining = 3;
590
				}
591
				// Invalid byte:
592
				else {
593
					$valid = false;
594
					$remaining = 0;
595
				}
596
			}
597
			// Continuation byte:
598
			else {
599
				// Check that the byte is valid, then add it to the character:
600 View Code Duplication
				if (($value & 0xC0) === 0x80) {
601
					$remaining--;
602
					$character |= ($value & 0x3F) << ($remaining * 6);
603
				}
604
				// If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
605
				else {
606
					$valid = false;
607
					$remaining = 0;
608
					$i--;
609
				}
610
			}
611
612
			// If we've reached the end of the current byte sequence, append it to Unicode::$data
613
			if (!$remaining) {
614
				// Percent encode anything invalid or not in iunreserved
615
				if (
616
					// Invalid sequences
617
					!$valid
618
					// Non-shortest form sequences are invalid
619
					|| $length > 1 && $character <= 0x7F
620
					|| $length > 2 && $character <= 0x7FF
621
					|| $length > 3 && $character <= 0xFFFF
622
					// Outside of range of iunreserved codepoints
623
					|| $character < 0x2D
624
					|| $character > 0xEFFFD
625
					// Noncharacters
626
					|| ($character & 0xFFFE) === 0xFFFE
627
					|| $character >= 0xFDD0 && $character <= 0xFDEF
628
					// Everything else not in iunreserved (this is all BMP)
629
					|| $character === 0x2F
630
					|| $character > 0x39 && $character < 0x41
631
					|| $character > 0x5A && $character < 0x61
632
					|| $character > 0x7A && $character < 0x7E
633
					|| $character > 0x7E && $character < 0xA0
634
					|| $character > 0xD7FF && $character < 0xF900
635
				) {
636 View Code Duplication
					for ($j = $start; $j <= $i; $j++) {
637
						$string .= '%' . strtoupper($bytes[$j]);
638
					}
639
				}
640
				else {
641 View Code Duplication
					for ($j = $start; $j <= $i; $j++) {
642
						$string .= chr(hexdec($bytes[$j]));
643
					}
644
				}
645
			}
646
		}
647
648
		// If we have any bytes left over they are invalid (i.e., we are
649
		// mid-way through a multi-byte sequence)
650
		if ($remaining) {
651 View Code Duplication
			for ($j = $start; $j < $len; $j++) {
652
				$string .= '%' . strtoupper($bytes[$j]);
653
			}
654
		}
655
656
		return $string;
657
	}
658
659
	protected function scheme_normalization() {
660 View Code Duplication
		if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
661
			$this->iuserinfo = null;
662
		}
663 View Code Duplication
		if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
664
			$this->ihost = null;
665
		}
666 View Code Duplication
		if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
667
			$this->port = null;
668
		}
669 View Code Duplication
		if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) {
670
			$this->ipath = '';
671
		}
672
		if (isset($this->ihost) && empty($this->ipath)) {
673
			$this->ipath = '/';
674
		}
675 View Code Duplication
		if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
676
			$this->iquery = null;
677
		}
678 View Code Duplication
		if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) {
679
			$this->ifragment = null;
680
		}
681
	}
682
683
	/**
684
	 * Check if the object represents a valid IRI. This needs to be done on each
685
	 * call as some things change depending on another part of the IRI.
686
	 *
687
	 * @return bool
688
	 */
689
	public function is_valid() {
690
		$isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
691
		if ($this->ipath !== '' &&
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !($this->ipath !=...($this->ipath, '/'))));.
Loading history...
692
			(
693
				$isauthority && $this->ipath[0] !== '/' ||
694
				(
695
					$this->scheme === null &&
696
					!$isauthority &&
697
					strpos($this->ipath, ':') !== false &&
698
					(strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/'))
699
				)
700
			)
701
		) {
702
			return false;
703
		}
704
705
		return true;
706
	}
707
708
	/**
709
	 * Set the entire IRI. Returns true on success, false on failure (if there
710
	 * are any invalid characters).
711
	 *
712
	 * @param string $iri
713
	 * @return bool
714
	 */
715
	protected function set_iri($iri) {
716
		static $cache;
717
		if (!$cache) {
718
			$cache = array();
719
		}
720
721
		if ($iri === null) {
722
			return true;
723
		}
724
		if (isset($cache[$iri])) {
725
			list($this->scheme,
726
				 $this->iuserinfo,
727
				 $this->ihost,
728
				 $this->port,
729
				 $this->ipath,
730
				 $this->iquery,
731
				 $this->ifragment,
732
				 $return) = $cache[$iri];
733
			return $return;
734
		}
735
736
		$parsed = $this->parse_iri((string) $iri);
737
738
		$return = $this->set_scheme($parsed['scheme'])
739
			&& $this->set_authority($parsed['authority'])
740
			&& $this->set_path($parsed['path'])
741
			&& $this->set_query($parsed['query'])
742
			&& $this->set_fragment($parsed['fragment']);
743
744
		$cache[$iri] = array($this->scheme,
745
							 $this->iuserinfo,
746
							 $this->ihost,
747
							 $this->port,
748
							 $this->ipath,
749
							 $this->iquery,
750
							 $this->ifragment,
751
							 $return);
752
		return $return;
753
	}
754
755
	/**
756
	 * Set the scheme. Returns true on success, false on failure (if there are
757
	 * any invalid characters).
758
	 *
759
	 * @param string $scheme
760
	 * @return bool
761
	 */
762
	protected function set_scheme($scheme) {
763
		if ($scheme === null) {
764
			$this->scheme = null;
765
		}
766
		elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) {
767
			$this->scheme = null;
768
			return false;
769
		}
770
		else {
771
			$this->scheme = strtolower($scheme);
772
		}
773
		return true;
774
	}
775
776
	/**
777
	 * Set the authority. Returns true on success, false on failure (if there are
778
	 * any invalid characters).
779
	 *
780
	 * @param string $authority
781
	 * @return bool
782
	 */
783
	protected function set_authority($authority) {
784
		static $cache;
785
		if (!$cache) {
786
			$cache = array();
787
		}
788
789
		if ($authority === null) {
790
			$this->iuserinfo = null;
791
			$this->ihost = null;
792
			$this->port = null;
793
			return true;
794
		}
795
		if (isset($cache[$authority])) {
796
			list($this->iuserinfo,
797
				 $this->ihost,
798
				 $this->port,
799
				 $return) = $cache[$authority];
800
801
			return $return;
802
		}
803
804
		$remaining = $authority;
805
		if (($iuserinfo_end = strrpos($remaining, '@')) !== false) {
806
			$iuserinfo = substr($remaining, 0, $iuserinfo_end);
807
			$remaining = substr($remaining, $iuserinfo_end + 1);
808
		}
809
		else {
810
			$iuserinfo = null;
811
		}
812
		if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) {
813
			$port = substr($remaining, $port_start + 1);
814
			if ($port === false || $port === '') {
815
				$port = null;
816
			}
817
			$remaining = substr($remaining, 0, $port_start);
818
		}
819
		else {
820
			$port = null;
821
		}
822
823
		$return = $this->set_userinfo($iuserinfo) &&
824
				  $this->set_host($remaining) &&
825
				  $this->set_port($port);
826
827
		$cache[$authority] = array($this->iuserinfo,
828
								   $this->ihost,
829
								   $this->port,
830
								   $return);
831
832
		return $return;
833
	}
834
835
	/**
836
	 * Set the iuserinfo.
837
	 *
838
	 * @param string $iuserinfo
839
	 * @return bool
840
	 */
841 View Code Duplication
	protected function set_userinfo($iuserinfo) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
842
		if ($iuserinfo === null) {
843
			$this->iuserinfo = null;
844
		}
845
		else {
846
			$this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
847
			$this->scheme_normalization();
848
		}
849
850
		return true;
851
	}
852
853
	/**
854
	 * Set the ihost. Returns true on success, false on failure (if there are
855
	 * any invalid characters).
856
	 *
857
	 * @param string $ihost
858
	 * @return bool
859
	 */
860
	protected function set_host($ihost) {
861
		if ($ihost === null) {
862
			$this->ihost = null;
863
			return true;
864
		}
865
		if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
866
			if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
867
				$this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -1)) . ']';
868
			}
869
			else {
870
				$this->ihost = null;
871
				return false;
872
			}
873
		}
874
		else {
875
			$ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');
876
877
			// Lowercase, but ignore pct-encoded sections (as they should
878
			// remain uppercase). This must be done after the previous step
879
			// as that can add unescaped characters.
880
			$position = 0;
881
			$strlen = strlen($ihost);
882
			while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) {
883
				if ($ihost[$position] === '%') {
884
					$position += 3;
885
				}
886
				else {
887
					$ihost[$position] = strtolower($ihost[$position]);
888
					$position++;
889
				}
890
			}
891
892
			$this->ihost = $ihost;
893
		}
894
895
		$this->scheme_normalization();
896
897
		return true;
898
	}
899
900
	/**
901
	 * Set the port. Returns true on success, false on failure (if there are
902
	 * any invalid characters).
903
	 *
904
	 * @param string $port
905
	 * @return bool
906
	 */
907
	protected function set_port($port) {
908
		if ($port === null) {
909
			$this->port = null;
910
			return true;
911
		}
912
913
		if (strspn($port, '0123456789') === strlen($port)) {
914
			$this->port = (int) $port;
0 ignored issues
show
Documentation Bug introduced by
It seems like (int) $port of type integer is incompatible with the declared type string|null of property $port.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
915
			$this->scheme_normalization();
916
			return true;
917
		}
918
919
		$this->port = null;
920
		return false;
921
	}
922
923
	/**
924
	 * Set the ipath.
925
	 *
926
	 * @param string $ipath
927
	 * @return bool
928
	 */
929
	protected function set_path($ipath) {
930
		static $cache;
931
		if (!$cache) {
932
			$cache = array();
933
		}
934
935
		$ipath = (string) $ipath;
936
937
		if (isset($cache[$ipath])) {
938
			$this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
939
		}
940
		else {
941
			$valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
942
			$removed = $this->remove_dot_segments($valid);
943
944
			$cache[$ipath] = array($valid, $removed);
945
			$this->ipath = ($this->scheme !== null) ? $removed : $valid;
946
		}
947
		$this->scheme_normalization();
948
		return true;
949
	}
950
951
	/**
952
	 * Set the iquery.
953
	 *
954
	 * @param string $iquery
955
	 * @return bool
956
	 */
957 View Code Duplication
	protected function set_query($iquery) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
958
		if ($iquery === null) {
959
			$this->iquery = null;
960
		}
961
		else {
962
			$this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
963
			$this->scheme_normalization();
964
		}
965
		return true;
966
	}
967
968
	/**
969
	 * Set the ifragment.
970
	 *
971
	 * @param string $ifragment
972
	 * @return bool
973
	 */
974 View Code Duplication
	protected function set_fragment($ifragment) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
975
		if ($ifragment === null) {
976
			$this->ifragment = null;
977
		}
978
		else {
979
			$this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
980
			$this->scheme_normalization();
981
		}
982
		return true;
983
	}
984
985
	/**
986
	 * Convert an IRI to a URI (or parts thereof)
987
	 *
988
	 * @param string|bool IRI to convert (or false from {@see get_iri})
989
	 * @return string|false URI if IRI is valid, false otherwise.
990
	 */
991
	protected function to_uri($string) {
992
		if (!is_string($string)) {
993
			return false;
994
		}
995
996
		static $non_ascii;
997
		if (!$non_ascii) {
998
			$non_ascii = implode('', range("\x80", "\xFF"));
999
		}
1000
1001
		$position = 0;
1002
		$strlen = strlen($string);
1003
		while (($position += strcspn($string, $non_ascii, $position)) < $strlen) {
1004
			$string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
1005
			$position += 3;
1006
			$strlen += 2;
1007
		}
1008
1009
		return $string;
1010
	}
1011
1012
	/**
1013
	 * Get the complete IRI
1014
	 *
1015
	 * @return string|false
1016
	 */
1017
	protected function get_iri() {
1018
		if (!$this->is_valid()) {
1019
			return false;
1020
		}
1021
1022
		$iri = '';
1023
		if ($this->scheme !== null) {
1024
			$iri .= $this->scheme . ':';
1025
		}
1026
		if (($iauthority = $this->get_iauthority()) !== null) {
1027
			$iri .= '//' . $iauthority;
1028
		}
1029
		$iri .= $this->ipath;
1030
		if ($this->iquery !== null) {
1031
			$iri .= '?' . $this->iquery;
1032
		}
1033
		if ($this->ifragment !== null) {
1034
			$iri .= '#' . $this->ifragment;
1035
		}
1036
1037
		return $iri;
1038
	}
1039
1040
	/**
1041
	 * Get the complete URI
1042
	 *
1043
	 * @return string
1044
	 */
1045
	protected function get_uri() {
1046
		return $this->to_uri($this->get_iri());
1047
	}
1048
1049
	/**
1050
	 * Get the complete iauthority
1051
	 *
1052
	 * @return string|null
1053
	 */
1054
	protected function get_iauthority() {
1055
		if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) {
1056
			return null;
1057
		}
1058
1059
		$iauthority = '';
1060
		if ($this->iuserinfo !== null) {
1061
			$iauthority .= $this->iuserinfo . '@';
1062
		}
1063
		if ($this->ihost !== null) {
1064
			$iauthority .= $this->ihost;
1065
		}
1066
		if ($this->port !== null) {
1067
			$iauthority .= ':' . $this->port;
1068
		}
1069
		return $iauthority;
1070
	}
1071
1072
	/**
1073
	 * Get the complete authority
1074
	 *
1075
	 * @return string
1076
	 */
1077
	protected function get_authority() {
1078
		$iauthority = $this->get_iauthority();
1079
		if (is_string($iauthority)) {
1080
			return $this->to_uri($iauthority);
1081
		}
1082
		else {
1083
			return $iauthority;
1084
		}
1085
	}
1086
}
1087