ts3phpframework
Loading...
Searching...
No Matches
Uri.php
Go to the documentation of this file.
1<?php
2
3namespace PlanetTeamSpeak\TeamSpeak3Framework\Helper;
4
5use PlanetTeamSpeak\TeamSpeak3Framework\Exception\HelperException;
6
13class Uri
14{
20 protected StringHelper|string|null $scheme = null;
21
27 protected ?StringHelper $user = null;
28
34 protected ?StringHelper $pass = null;
35
41 protected ?StringHelper $host = null;
42
48 protected ?StringHelper $port = null;
49
55 protected ?StringHelper $path = null;
56
62 protected ?StringHelper $query = null;
63
69 protected ?StringHelper $fragment = null;
70
76 protected array $regex = [];
77
84 public function __construct(string $uri)
85 {
86 if (!strlen($uri)) {
87 throw new HelperException("no URI supplied");
88 }
89
90 /* grammar rules for validation */
91 $this->regex["alphanum"] = "[^\W_]";
92 $this->regex["escaped"] = "(?:%[\da-fA-F]{2})";
93 $this->regex["mark"] = "[-_.!~*'()\[\]]";
94 $this->regex["reserved"] = "[;\/?:@&=+$,]";
95 $this->regex["unreserved"] = "(?:" . $this->regex["alphanum"] . "|" . $this->regex["mark"] . ")";
96 $this->regex["segment"] = "(?:(?:" . $this->regex["unreserved"] . "|" . $this->regex["escaped"] . "|[:@&=+$,;])*)";
97 $this->regex["path"] = "(?:\/" . $this->regex["segment"] . "?)+";
98 $this->regex["uric"] = "(?:" . $this->regex["reserved"] . "|" . $this->regex["unreserved"] . "|" . $this->regex["escaped"] . ")";
99
100 $this->parseUri($uri);
101
102 if (!$this->isValid()) {
103 throw new HelperException("invalid URI supplied");
104 }
105 }
106
114 protected function parseUri(string $uriString = ''): void
115 {
116 $components = parse_url($uriString, -1);
117
118 if ($components === false) {
119 throw new HelperException("URI scheme-specific decomposition failed");
120 }
121
122 $this->scheme = StringHelper::factory(isset($components["scheme"]) ? $components["scheme"] : "");
123
124 if (empty(trim($this->scheme)) or !ctype_alnum($this->scheme->toString())) {
125 throw new HelperException("invalid URI scheme '" . $this->scheme . "' supplied");
126 }
127
128 $this->host = StringHelper::factory(isset($components["host"]) ? $components["host"] : "");
129 $this->port = StringHelper::factory(isset($components["port"]) ? $components["port"] : "");
130
131 $this->user = StringHelper::factory(isset($components["user"]) ? $components["user"] : "");
132 $this->pass = StringHelper::factory(isset($components["pass"]) ? $components["pass"] : "");
133
134 $this->path = StringHelper::factory((isset($components["path"])) ? $components["path"] : "");
135 $this->query = StringHelper::factory((isset($components["query"])) ? $components["query"] : "");
136 $this->fragment = StringHelper::factory((isset($components["fragment"])) ? $components["fragment"] : "");
137
138 if (str_contains($this->fragment, "?")) {
139 throw new HelperException("invalid URI fragment '" . $this->fragment . "' supplied (fragment must be after query)");
140 }
141 }
142
149 public function isValid(): bool
150 {
151 return ($this->checkUser() && $this->checkPass() && $this->checkHost() && $this->checkPort() && $this->checkPath() && $this->checkQuery() && $this->checkFragment());
152 }
153
161 public static function check(StringHelper $uri): bool
162 {
163 try {
164 $uri = new self(strval($uri));
165 } catch (HelperException) {
166 return false;
167 }
168
169 return $uri->isValid();
170 }
171
177 public function hasScheme(): bool
178 {
179 return (bool)strlen($this->scheme);
180 }
181
188 public function getScheme(mixed $default = null): ?StringHelper
189 {
190 return ($this->hasScheme()) ? new StringHelper($this->scheme) : $default;
191 }
192
200 public function checkUser(string $username = null): bool
201 {
202 if ($username === null) {
203 $username = $this->user->toString();
204 }
205
206 if (strlen($username) == 0) {
207 return true;
208 }
209
210 $pattern = "/^(" . $this->regex["alphanum"] . "|" . $this->regex["mark"] . "|" . $this->regex["escaped"] . "|" . "[;:&=+$,])+$/";
211 $status = @preg_match($pattern, $username);
212
213 if ($status === false) {
214 throw new HelperException("URI username validation failed");
215 }
216
217 return ($status == 1);
218 }
219
225 public function hasUser(): bool
226 {
227 return (bool)strlen($this->user);
228 }
229
236 public function getUser(mixed $default = null): ?StringHelper
237 {
238 return ($this->hasUser()) ? new StringHelper(urldecode($this->user)) : $default;
239 }
240
248 public function checkPass(StringHelper|string $password = null): bool
249 {
250 if ($password === null) {
251 $password = $this->pass->toString();
252 }
253
254 if (strlen($password) == 0) {
255 return true;
256 }
257
258 $pattern = "/^(" . $this->regex["alphanum"] . "|" . $this->regex["mark"] . "|" . $this->regex["escaped"] . "|" . "[;:&=+$,])+$/";
259 $status = @preg_match($pattern, $password);
260
261 if ($status === false) {
262 throw new HelperException("URI password validation failed");
263 }
264
265 return ($status == 1);
266 }
267
273 public function hasPass(): bool
274 {
275 return (bool)strlen($this->pass);
276 }
277
284 public function getPass(mixed $default = null): ?StringHelper
285 {
286 return ($this->hasPass()) ? new StringHelper(urldecode($this->pass)) : $default;
287 }
288
295 public function checkHost(string $host = null): bool
296 {
297 if ($host === null) {
299 }
300
301 switch ($host) {
302 case filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6):
303 // Valid IPv4 or IPv6 address
304 break;
305 case !preg_match("/^(([0-9]{1,3})\.){3}([0-9]{1,3})$/", $host) and preg_match("/^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])$/i", $host):
306 // Valid hostname as specified in RFC 1123 (IPv4 like strings are excluded)
307 // `filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)` unfortunately also reports invalid IP
308 // addresses (e.g. `127.0.0.300`) as valid, so we can not use this filter here.
309 // Regex reference: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
310 break;
311 default:
312 // Otherwise invalid host provided
313 return false;
314 }
315
316 return true;
317 }
318
324 public function hasHost(): bool
325 {
326 return (bool)strlen($this->host);
327 }
328
335 public function getHost(mixed $default = null): ?StringHelper
336 {
337 return ($this->hasHost()) ? new StringHelper(rawurldecode($this->host)) : $default;
338 }
339
346 public function checkPort(int $port = null): bool
347 {
348 if ($port === null) {
349 if ($this->port instanceof StringHelper) {
350 $port = intval($this->port->toString());
351 } else {
352 $port = intval($this->port);
353 }
354 }
355
356 switch ($port) {
357 case str_starts_with($port, '-'):
358 case $port < 0:
359 case !is_int($port):
360 case !filter_var($port, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 65535]]):
361 return false;
362 }
363
364 return true;
365 }
366
372 public function hasPort(): bool
373 {
374 return (bool)strlen($this->port->toString());
375 }
376
383 public function getPort(mixed $default = null): int
384 {
385 return ($this->hasPort()) ? intval($this->port->toString()) : $default;
386 }
387
395 public function checkPath(string $path = null): bool
396 {
397 if ($path === null) {
398 $path = $this->path->toString();
399 }
400
401 if (strlen($path) == 0) {
402 return true;
403 }
404
405 $pattern = "/^" . $this->regex["path"] . "$/";
406 $status = @preg_match($pattern, $path);
407
408 if ($status === false) {
409 throw new HelperException("URI path validation failed");
410 }
411
412 return ($status == 1);
413 }
414
420 public function hasPath(): bool
421 {
422 return (bool)strlen($this->path);
423 }
424
431 public function getPath(string $default = "/"): StringHelper
432 {
433 return ($this->hasPath()) ? new StringHelper(rawurldecode($this->path)) : new StringHelper($default);
434 }
435
443 public function checkQuery(string $query = null): bool
444 {
445 if ($query === null) {
447 }
448
449 if (strlen($query) == 0) {
450 return true;
451 }
452
453 $pattern = "/^" . $this->regex["uric"] . "*$/";
454 $status = @preg_match($pattern, $query);
455
456 if ($status === false) {
457 throw new HelperException("URI query string validation failed");
458 }
459
460 return ($status == 1);
461 }
462
468 public function hasQuery(): bool
469 {
470 return (bool)strlen($this->query);
471 }
472
479 public function getQuery(array $default = []): array
480 {
481 if (!$this->hasQuery()) {
482 return $default;
483 }
484
485 parse_str(rawurldecode($this->query), $queryArray);
486
487 return $queryArray;
488 }
489
496 public function hasQueryVar($key): bool
497 {
498 if (!$this->hasQuery()) {
499 return false;
500 }
501
502 parse_str($this->query, $queryArray);
503
504 return array_key_exists($key, $queryArray);
505 }
506
514 public function getQueryVar(string $key, mixed $default = null): mixed
515 {
516 if (!$this->hasQuery()) {
517 return $default;
518 }
519
520 parse_str(rawurldecode($this->query), $queryArray);
521
522 if (array_key_exists($key, $queryArray)) {
523 $val = $queryArray[$key];
524
525 if (ctype_digit($val)) {
526 return intval($val);
527 } elseif (is_string($val)) {
528 return new StringHelper($val);
529 } else {
530 return $val;
531 }
532 }
533
534 return $default;
535 }
536
544 public function checkFragment(string $fragment = null): bool
545 {
546 if ($fragment === null) {
548 }
549
550 if (strlen($fragment) == 0) {
551 return true;
552 }
553
554 $pattern = "/^" . $this->regex["uric"] . "*$/";
555 $status = @preg_match($pattern, $fragment);
556
557 if ($status === false) {
558 throw new HelperException("URI fragment validation failed");
559 }
560
561 return ($status == 1);
562 }
563
569 public function hasFragment(): bool
570 {
571 return (bool)strlen($this->fragment);
572 }
573
580 public function getFragment(mixed $default = null): ?StringHelper
581 {
582 return ($this->hasFragment()) ? new StringHelper(rawurldecode($this->fragment)) : $default;
583 }
584
592 public static function getUserParam(string $key, mixed $default = null): mixed
593 {
594 return (array_key_exists($key, $_REQUEST) && !empty($_REQUEST[$key])) ? self::stripslashesRecursive($_REQUEST[$key]) : $default;
595 }
596
604 public static function getHostParam(string $key, mixed $default = null): mixed
605 {
606 return (array_key_exists($key, $_SERVER) && !empty($_SERVER[$key])) ? $_SERVER[$key] : $default;
607 }
608
616 public static function getSessParam(string $key, mixed $default = null): mixed
617 {
618 return (array_key_exists($key, $_SESSION) && !empty($_SESSION[$key])) ? $_SESSION[$key] : $default;
619 }
620
628 public static function getFQDNParts(string $hostname): array
629 {
630 if (!preg_match("/^([a-z0-9][a-z0-9-]{0,62}\.)*([a-z0-9][a-z0-9-]{0,62}\.)+([a-z]{2,6})$/i", $hostname, $matches)) {
631 return [];
632 }
633
634 $parts["tld"] = $matches[3];
635 $parts["2nd"] = $matches[2];
636 $parts["3rd"] = $matches[1];
637
638 return $parts;
639 }
640
646 public static function getHostUri(): StringHelper
647 {
648 $sheme = (self::getHostParam("HTTPS") == "on") ? "https" : "http";
649
650 $serverName = new StringHelper(self::getHostParam("HTTP_HOST"));
651 $serverPort = self::getHostParam("SERVER_PORT");
652 $serverPort = ($serverPort != 80 && $serverPort != 443) ? ":" . $serverPort : "";
653
654 if ($serverName->endsWith($serverPort)) {
655 $serverName = $serverName->replace($serverPort, "");
656 }
657
658 return new StringHelper($sheme . "://" . $serverName . $serverPort);
659 }
660
666 public static function getBaseUri(): StringHelper
667 {
668 $scriptPath = new StringHelper(dirname(self::getHostParam("SCRIPT_NAME")));
669
670 return self::getHostUri()->append(($scriptPath == DIRECTORY_SEPARATOR ? "" : $scriptPath) . "/");
671 }
672
679 protected static function stripslashesRecursive(mixed $var): string|array
680 {
681 if (!is_array($var)) {
682 return stripslashes(strval($var));
683 }
684
685 foreach ($var as $key => $val) {
686 $var[$key] = (is_array($val)) ? self::stripslashesRecursive($val) : stripslashes(strval($val));
687 }
688
689 return $var;
690 }
691}
Enhanced exception class for PlanetTeamSpeak\TeamSpeak3Framework\Helper* objects.
Helper class for URI handling.
Definition Uri.php:14
static getFQDNParts(string $hostname)
Definition Uri.php:628
static getSessParam(string $key, mixed $default=null)
Definition Uri.php:616
static getHostParam(string $key, mixed $default=null)
Definition Uri.php:604
static getUserParam(string $key, mixed $default=null)
Definition Uri.php:592
checkPass(StringHelper|string $password=null)
Definition Uri.php:248
getQueryVar(string $key, mixed $default=null)
Definition Uri.php:514
static check(StringHelper $uri)
Definition Uri.php:161
StringHelper string null $scheme
Definition Uri.php:20