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