mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-07-31 13:50:23 +02:00
145
lib/url.php
Normal file
145
lib/url.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class UrlException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Intentionally restrictive url parser
|
||||
*/
|
||||
final class Url
|
||||
{
|
||||
private string $scheme;
|
||||
private string $host;
|
||||
private int $port;
|
||||
private string $path;
|
||||
private ?string $queryString;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public static function fromString(string $url): self
|
||||
{
|
||||
if (!self::validate($url)) {
|
||||
throw new UrlException(sprintf('Illegal url: "%s"', $url));
|
||||
}
|
||||
|
||||
$parts = parse_url($url);
|
||||
if ($parts === false) {
|
||||
throw new UrlException(sprintf('Invalid url %s', $url));
|
||||
}
|
||||
|
||||
return (new self())
|
||||
->withScheme($parts['scheme'] ?? '')
|
||||
->withHost($parts['host'])
|
||||
->withPort($parts['port'] ?? 80)
|
||||
->withPath($parts['path'] ?? '/')
|
||||
->withQueryString($parts['query'] ?? null);
|
||||
}
|
||||
|
||||
public static function validate(string $url): bool
|
||||
{
|
||||
if (strlen($url) > 1500) {
|
||||
return false;
|
||||
}
|
||||
$pattern = '#^https?://' // scheme
|
||||
. '([a-z0-9-]+\.?)+' // one or more domain names
|
||||
. '(\.[a-z]{1,24})?' // optional global tld
|
||||
. '(:\d+)?' // optional port
|
||||
. '($|/|\?)#i'; // end of string or slash or question mark
|
||||
|
||||
return preg_match($pattern, $url) === 1;
|
||||
}
|
||||
|
||||
public function getScheme(): string
|
||||
{
|
||||
return $this->scheme;
|
||||
}
|
||||
|
||||
public function getHost(): string
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function getPort(): int
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getQueryString(): string
|
||||
{
|
||||
return $this->queryString;
|
||||
}
|
||||
|
||||
public function withScheme(string $scheme): self
|
||||
{
|
||||
if (!in_array($scheme, ['http', 'https'])) {
|
||||
throw new UrlException(sprintf('Invalid scheme %s', $scheme));
|
||||
}
|
||||
$clone = clone $this;
|
||||
$clone->scheme = $scheme;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function withHost(string $host): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->host = $host;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function withPort(int $port)
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->port = $port;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function withPath(string $path): self
|
||||
{
|
||||
if (!str_starts_with($path, '/')) {
|
||||
throw new UrlException(sprintf('Path must start with forward slash: %s', $path));
|
||||
}
|
||||
$clone = clone $this;
|
||||
$clone->path = $path;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function withQueryString(?string $queryString): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->queryString = $queryString;
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->port === 80) {
|
||||
$port = '';
|
||||
} else {
|
||||
$port = ':' . $this->port;
|
||||
}
|
||||
if ($this->queryString) {
|
||||
$queryString = '?' . $this->queryString;
|
||||
} else {
|
||||
$queryString = '';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s://%s%s%s%s',
|
||||
$this->scheme,
|
||||
$this->host,
|
||||
$port,
|
||||
$this->path,
|
||||
$queryString
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user