Skip to content

Commit

Permalink
Merge pull request #219 from Sebbo94BY/#213-factory-fails-without-use…
Browse files Browse the repository at this point in the history
…rname-and-password

#213 Improve and fix URI parsing
  • Loading branch information
Sebbo94BY authored Jun 1, 2024
2 parents 77553bf + 2ab9e5f commit 09c0636
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 41 deletions.
54 changes: 29 additions & 25 deletions src/Helper/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,8 @@ class Uri
*/
public function __construct(string $uri)
{
$uri = explode(":", $uri, 2);

$this->scheme = strtolower($uri[0]);
$uriString = $uri[1] ?? "";

if (!ctype_alnum($this->scheme)) {
throw new HelperException("invalid URI scheme '" . $this->scheme . "' supplied");
if (!strlen($uri)) {
throw new HelperException("no URI supplied");
}

/* grammar rules for validation */
Expand All @@ -124,9 +119,7 @@ public function __construct(string $uri)
$this->regex["path"] = "(?:\/" . $this->regex["segment"] . "?)+";
$this->regex["uric"] = "(?:" . $this->regex["reserved"] . "|" . $this->regex["unreserved"] . "|" . $this->regex["escaped"] . ")";

if (strlen($uriString) > 0) {
$this->parseUri($uriString);
}
$this->parseUri($uri);

if (!$this->isValid()) {
throw new HelperException("invalid URI supplied");
Expand All @@ -142,24 +135,31 @@ public function __construct(string $uri)
*/
protected function parseUri(string $uriString = ''): void
{
$status = @preg_match("~^//(?P<user>.*):(?P<password>.*)@(?P<host>.*):(?P<port>[0-9]{2,5})(?:/(?P<path>.*?))?(?:\?(?P<query>.*?))?(?:#(?P<fragment>.*))?$~", $uriString, $matches);
$components = parse_url($uriString, -1);

if ($status === false) {
if ($components === false) {
throw new HelperException("URI scheme-specific decomposition failed");
}

if (!$status) {
return;
$this->scheme = StringHelper::factory(isset($components["scheme"]) ? $components["scheme"] : "");

if (empty(trim($this->scheme)) or !ctype_alnum($this->scheme->toString())) {
throw new HelperException("invalid URI scheme '" . $this->scheme . "' supplied");
}

$this->user = StringHelper::factory($matches[1] ?? "");
$this->pass = StringHelper::factory($matches[2] ?? "");
$this->host = StringHelper::factory($matches[3] ?? "");
$this->port = StringHelper::factory($matches[4] ?? "");
$this->host = StringHelper::factory(isset($components["host"]) ? $components["host"] : "");
$this->port = StringHelper::factory(isset($components["port"]) ? $components["port"] : "");

$this->user = StringHelper::factory(isset($components["user"]) ? $components["user"] : "");
$this->pass = StringHelper::factory(isset($components["pass"]) ? $components["pass"] : "");

$this->path = StringHelper::factory((isset($components["path"])) ? $components["path"] : "");
$this->query = StringHelper::factory((isset($components["query"])) ? $components["query"] : "");
$this->fragment = StringHelper::factory((isset($components["fragment"])) ? $components["fragment"] : "");

$this->path = StringHelper::factory((isset($matches[5])) ? $matches[5] : "");
$this->query = StringHelper::factory((isset($matches[6])) ? $matches[6] : "");
$this->fragment = StringHelper::factory((isset($matches[7])) ? $matches[7] : "");
if (str_contains($this->fragment, "?")) {
throw new HelperException("invalid URI fragment '" . $this->fragment . "' supplied (fragment must be after query)");
}
}

/**
Expand Down Expand Up @@ -222,7 +222,7 @@ public function getScheme(mixed $default = null): ?StringHelper
public function checkUser(string $username = null): bool
{
if ($username === null) {
$username = $this->user;
$username = $this->user->toString();
}

if (strlen($username) == 0) {
Expand Down Expand Up @@ -270,7 +270,7 @@ public function getUser(mixed $default = null): ?StringHelper
public function checkPass(StringHelper|string $password = null): bool
{
if ($password === null) {
$password = $this->pass;
$password = $this->pass->toString();
}

if (strlen($password) == 0) {
Expand Down Expand Up @@ -368,7 +368,11 @@ public function getHost(mixed $default = null): ?StringHelper
public function checkPort(int $port = null): bool
{
if ($port === null) {
$port = intval($this->port->toString());
if ($this->port instanceof StringHelper) {
$port = intval($this->port->toString());
} else {
$port = intval($this->port);
}
}

switch ($port) {
Expand Down Expand Up @@ -413,7 +417,7 @@ public function getPort(mixed $default = null): int
public function checkPath(string $path = null): bool
{
if ($path === null) {
$path = $this->path;
$path = $this->path->toString();
}

if (strlen($path) == 0) {
Expand Down
85 changes: 69 additions & 16 deletions tests/Helper/UriTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,39 @@ class UriTest extends TestCase
'use_offline_as_virtual',
'clients_before_channels'
],
'test_uri' => [
'test_uri' => 'serverquery://username:password@127.0.0.1:10011/?server_port=9987&blocking=0#no_query_clients',
'valid_uris' => [
'serverquery://127.0.0.1:10011',
'serverquery://127.0.0.1:10011/',
'serverquery://127.0.0.1:10011?server_port=9987',
'serverquery://127.0.0.1:10011/?server_port=9987',
'serverquery://127.0.0.1:10011/?server_port=9987&blocking=0',
'serverquery://127.0.0.1:10011/?server_port=9987&blocking=0#no_query_clients',
'serverquery://127.0.0.1:10011/#no_query_clients',
'serverquery://127.0.0.1:10011#no_query_clients',
'serverquery://username:password@127.0.0.1:10011',
'serverquery://username:password@127.0.0.1:10011/',
'serverquery://username:password@127.0.0.1:10011?server_port=9987',
'serverquery://username:password@127.0.0.1:10011/?server_port=9987',
'serverquery://username:password@127.0.0.1:10011/?server_port=9987&blocking=0',
'serverquery://username:password@127.0.0.1:10011/?server_port=9987&blocking=0#no_query_clients'
]
'serverquery://username:password@127.0.0.1:10011/?server_port=9987&blocking=0#no_query_clients',
'serverquery://username:password@127.0.0.1:10011/#no_query_clients',
'serverquery://username:password@127.0.0.1:10011#no_query_clients',
],
'invalid_uris' => [
'serverquery://127.0.0.1', // port missing
'serverquery://127.0.0.1/', // port missing
'serverquery://127.0.0.1:10011/#no_query_clients?server_port=9987', # fragment must be after query
'serverquery://username:password@127.0.0.1', // port missing
'serverquery://username:password@127.0.0.1/', // port missing
'serverquery://username:password@127.0.0.1:10011/#no_query_clients?server_port=9987', # fragment must be after query
],
];

public function testConstructEmptyURI()
{
$this->expectException(HelperException::class);
$this->expectExceptionMessage('invalid URI scheme');

// Uri should throw exception on non-alphanumeric in <scheme> of URI
$this->expectExceptionMessage('no URI supplied');
new Uri('');
}

Expand All @@ -70,7 +89,7 @@ public function testConstructInvalidScheme()
new Uri(str_replace(
'serverquery',
'server&&&&query', // non-alphanumeric
$this->mock['test_uri'][0]
$this->mock['test_uri']
));
}

Expand All @@ -84,7 +103,7 @@ public function testParseURI()
*/
public function testCheckUser()
{
$uri = new Uri($this->mock['test_uri'][0]);
$uri = new Uri($this->mock['test_uri']);

$ASCIIValid = [
48, 57, 65, 90, 97, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 91, 93, 59, 58, 38, 61, 43, 36, 44
Expand Down Expand Up @@ -114,7 +133,7 @@ public function testCheckUser()
*/
public function testCheckPass()
{
$uri = new Uri($this->mock['test_uri'][0]);
$uri = new Uri($this->mock['test_uri']);

$ASCIIValid = [
48, 57, 65, 90, 97, 122, 45, 95, 46, 33, 126, 42, 39, 40, 41, 91, 93, 59, 58, 38, 61, 43, 36, 44
Expand All @@ -141,7 +160,7 @@ public function testCheckPass()

public function testCheckHost()
{
$uri = new Uri($this->mock['test_uri'][0]);
$uri = new Uri($this->mock['test_uri']);

$validHosts = [
// Private IPv4 addresses
Expand Down Expand Up @@ -188,7 +207,7 @@ public function testCheckHost()

public function testCheckPort()
{
$uri = new Uri($this->mock['test_uri'][0]);
$uri = new Uri($this->mock['test_uri']);

$validPorts = [
80, 443, 8080, 9200, 9987, 10011, 10022,
Expand All @@ -210,7 +229,7 @@ public function testCheckPort()
*/
public function testCheckPath()
{
$uri = new Uri($this->mock['test_uri'][1]);
$uri = new Uri($this->mock['test_uri']);

// NOTE: Similar, but different valid characters than previous tests.
// '0-9A-Za-z-_.!~*'()[]:@&=+$,;/' and url escaped hex '%XX'
Expand Down Expand Up @@ -246,7 +265,7 @@ public function testCheckPath()
*/
public function testCheckQuery()
{
$uri = new Uri($this->mock['test_uri'][1]);
$uri = new Uri($this->mock['test_uri']);

// NOTE: Similar, but different valid characters than previous tests.
// '0-9A-Za-z-_.!~*'()[]:@&=+$,;/#' and url escaped hex '%XX'
Expand All @@ -273,7 +292,7 @@ public function testCheckQuery()
*/
public function testCheckFragment()
{
$uri = new Uri($this->mock['test_uri'][1]);
$uri = new Uri($this->mock['test_uri']);

// NOTE: Similar, but different valid characters than previous tests.
// '0-9A-Za-z-_.!~*'()[]:@&=+$,;/#' and url escaped hex '%XX'
Expand All @@ -295,12 +314,47 @@ public function testCheckFragment()
$this->assertFalse($uri->checkFragment("\xC2\xA2")); // "\u{00A2}" '¢'
}

/**
* @throws HelperException
*/
public function testUriIsValid(): void
{
foreach ($this->mock['valid_uris'] as $valid_uri) {
$uri = new Uri($valid_uri);
$this->assertTrue($uri->isValid());

$this->assertEquals('serverquery', $uri->getScheme());

$this->assertEquals('127.0.0.1', $uri->getHost());
$this->assertInstanceOf(
StringHelper::class,
$uri->getHost()
);

$this->assertEquals(10011, $uri->getPort());
$this->assertIsInt($uri->getPort());
}
}

/**
* @throws HelperException
*/
public function testUriIsNotValid(): void
{
foreach ($this->mock['invalid_uris'] as $invalid_uri) {
$this->expectException(HelperException::class);
$this->expectExceptionMessage('invalid URI supplied');
$uri = new Uri($invalid_uri);
$this->assertFalse($uri->isValid());
}
}

/**
* @throws HelperException
*/
public function testIsValid(): Uri
{
$uri = new Uri($this->mock['test_uri'][3]);
$uri = new Uri($this->mock['test_uri']);

$this->assertTrue($uri->isValid());

Expand Down Expand Up @@ -395,7 +449,6 @@ public function testGetPath(Uri $uri)
*/
public function testGetQuery(Uri $uri)
{
// NOTE: getPath() is never used in framework, add tests for consistency.
$this->assertEquals(
['server_port' => '9987', 'blocking' => '0'],
$uri->getQuery()
Expand Down

0 comments on commit 09c0636

Please sign in to comment.