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