Skip to content

Commit 2e6add7

Browse files
committed
fix: detect host-like parts
closes #236
1 parent 1aee017 commit 2e6add7

3 files changed

Lines changed: 92 additions & 2 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
"scripts": {
4040
"phpunit": "vendor/bin/phpunit --configuration phpunit.xml",
41-
"phpstan": "vendor/bin/phpstan analyse --level 6 src",
41+
"phpstan": "vendor/bin/phpstan analyse --memory-limit=512M --level 6 src",
4242
"phpcs": "vendor/bin/phpcs src --standard=phpcs.xml",
4343
"phpmd": "vendor/bin/phpmd src/ text phpmd.xml",
4444
"test": [

src/Uri.php

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ protected function parseUri(string $uri):false|array {
4242
return $authorityStyleParts;
4343
}
4444

45+
$hostLikeParts = $this->parseHostLikeParts($uri, $parts);
46+
if(!is_null($hostLikeParts)) {
47+
return $hostLikeParts;
48+
}
49+
4550
return $parts;
4651
}
4752

@@ -77,6 +82,34 @@ protected function parseAuthorityStyleParts(
7782
return $authorityParts;
7883
}
7984

85+
/**
86+
* @param array<string, int|string> $parts
87+
* @return null|array<string, int|string>
88+
*/
89+
protected function parseHostLikeParts(
90+
string $uri,
91+
array $parts
92+
):?array {
93+
if(!$this->canBeHostLikeUri($uri, $parts)) {
94+
return null;
95+
}
96+
97+
$hostLikeParts = parse_url("//" . $uri);
98+
if($hostLikeParts === false || !isset($hostLikeParts["host"])) {
99+
return null;
100+
}
101+
102+
if(!$this->isAuthorityPathValid($hostLikeParts)) {
103+
return null;
104+
}
105+
106+
if(!$this->isAuthorityHostLike($hostLikeParts)) {
107+
return null;
108+
}
109+
110+
return $hostLikeParts;
111+
}
112+
80113
/** @param array<string, int|string> $parts */
81114
protected function canBeAuthorityStyleUri(string $uri, array $parts):bool {
82115
if(str_contains($uri, "://")) {
@@ -91,6 +124,29 @@ protected function canBeAuthorityStyleUri(string $uri, array $parts):bool {
91124
return str_contains($path, "@");
92125
}
93126

127+
/** @param array<string, int|string> $parts */
128+
protected function canBeHostLikeUri(string $uri, array $parts):bool {
129+
if(str_contains($uri, "://")) {
130+
return false;
131+
}
132+
133+
if(isset($parts["host"]) || !isset($parts["path"])) {
134+
return false;
135+
}
136+
137+
if(isset($parts["scheme"])) {
138+
return false;
139+
}
140+
141+
$path = (string)$parts["path"];
142+
if($path === "" || str_starts_with($path, "/")) {
143+
return false;
144+
}
145+
146+
$firstSegment = explode("/", $path, 2)[0];
147+
return $this->isHostLikeString($firstSegment);
148+
}
149+
94150
/** @param array<string, int|string> $authorityParts */
95151
protected function hasRequiredAuthorityParts(array $authorityParts):bool {
96152
return isset($authorityParts["user"], $authorityParts["pass"], $authorityParts["host"]);
@@ -104,7 +160,16 @@ protected function isAuthorityPathValid(array $authorityParts):bool {
104160

105161
/** @param array<string, int|string> $authorityParts */
106162
protected function isAuthorityHostLike(array $authorityParts):bool {
107-
$host = (string)$authorityParts["host"];
163+
return $this->isHostLikeString((string)$authorityParts["host"]);
164+
}
165+
166+
protected function isHostLikeString(string $host):bool {
167+
if($host === "."
168+
|| $host === ".."
169+
|| str_starts_with($host, ".")) {
170+
return false;
171+
}
172+
108173
if(filter_var($host, FILTER_VALIDATE_IP) !== false) {
109174
return true;
110175
}

test/phpunit/UriTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,4 +638,29 @@ public function testGetQueryValue() {
638638
self::assertSame("orange", $uri->getQueryValue("colour"));
639639
self::assertNull($uri->getQueryValue("age"));
640640
}
641+
642+
public function testConstruct_noPathJustHost():void {
643+
$uri = new Uri("10.0.0.1");
644+
self::assertSame("10.0.0.1", $uri->getHost());
645+
}
646+
647+
public function testConstruct_pathAndHost():void {
648+
$uri = new Uri("10.0.0.1/tagbatch");
649+
self::assertSame("10.0.0.1", $uri->getHost());
650+
self::assertSame("/tagbatch", $uri->getPath());
651+
}
652+
653+
public function testConstruct_pathWithQueryAndHost():void {
654+
$uri = new Uri("10.0.0.1/tagbatch?id=123");
655+
self::assertSame("10.0.0.1", $uri->getHost());
656+
self::assertSame("/tagbatch", $uri->getPath());
657+
self::assertSame("id=123", $uri->getQuery());
658+
self::assertSame("123", $uri->getQueryValue("id"));
659+
}
660+
661+
public function testConstruct_pathWithQueryAndHostAndPort():void {
662+
$uri = new Uri("10.0.0.1:4321/tagbatch?id=123");
663+
self::assertSame("10.0.0.1", $uri->getHost());
664+
self::assertSame(4321, $uri->getPort());
665+
}
641666
}

0 commit comments

Comments
 (0)