Skip to content

Commit 70f61f8

Browse files
committed
Update to the latest version
1 parent 93928e9 commit 70f61f8

File tree

2 files changed

+144
-24
lines changed

2 files changed

+144
-24
lines changed

src/Url.php

+93-24
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ class Url {
1515
private $relativePath; // no trailing "/"
1616
private $assumedHost;
1717
private $assumedPath;
18+
private $hostUnmodified;
1819

1920
public function __construct($url) {
2021
$this->url = $url;
21-
$parts = parse_url($url);
22+
$parts = parse_url($url ?: '');
2223
$this->scheme = isset($parts["scheme"]) ? strtolower($parts["scheme"]) : NULL;
2324
$this->port = isset($parts["port"]) ? $parts["port"] : NULL;
2425
$this->host = isset($parts["host"]) ? strtolower($parts["host"]) : NULL;
@@ -32,12 +33,14 @@ public function __construct($url) {
3233

3334
if (!isset($parts["host"]) && isset($parts["path"])) { // This is probably a host written like this: nitropack.io
3435
if (preg_match("/^[^\s\/]+?\.[^\s\/]+$/", $parts["path"])) {
35-
$this->host = $this->path;
36+
$this->host = strtolower($this->path);
37+
$this->hostUnmodified = $this->path;
3638
$this->path = "/";
3739
$this->assumedHost = true;
3840
$this->assumedPath = true;
3941
} else if (preg_match("/^([^\s\/]+?\.[^\s\/]+)(\/.*?)$/", $parts["path"], $matches)) {
40-
$this->host = $matches[1];
42+
$this->host = strtolower($matches[1]);
43+
$this->hostUnmodified = $matches[1];
4144
$this->path = $matches[2];
4245
$this->assumedHost = true;
4346
}
@@ -51,6 +54,25 @@ public function __toString() {
5154
}
5255

5356
private function buildParts() {
57+
$this->updateRootUrl();
58+
$this->updateRelativePath();
59+
}
60+
61+
private function suggestScheme() {
62+
if (!$this->scheme) {
63+
if ($this->base) {
64+
$scheme = $this->base->getScheme() ? $this->base->getScheme() : "http";
65+
} else {
66+
$scheme = "http";
67+
}
68+
} else {
69+
$scheme = $this->scheme;
70+
}
71+
72+
return $scheme;
73+
}
74+
75+
private function updateRootUrl() {
5476
if ($this->host) {
5577
$scheme = $this->suggestScheme();
5678
$this->rootUrl = $scheme . "://" . $this->host;
@@ -62,7 +84,9 @@ private function buildParts() {
6284
} else if ($this->base) {
6385
$this->rootUrl = $this->base->getRootUrl();
6486
}
87+
}
6588

89+
private function updateRelativePath() {
6690
if (substr($this->path, -1) != '/' && $this->path != '/') {
6791
$this->relativePath = dirname($this->path);
6892
} else {
@@ -78,20 +102,6 @@ private function buildParts() {
78102
}
79103
}
80104

81-
private function suggestScheme() {
82-
if (!$this->scheme) {
83-
if ($this->base) {
84-
$scheme = $this->base->getScheme() ? $this->base->getScheme() : "http";
85-
} else {
86-
$scheme = "http";
87-
}
88-
} else {
89-
$scheme = $this->scheme;
90-
}
91-
92-
return $scheme;
93-
}
94-
95105
public function getUrl() { return $this->url; }
96106
public function getScheme() { return $this->suggestScheme(); }
97107
public function getPort() { return $this->port; }
@@ -104,13 +114,29 @@ public function getBaseUrl() { return $this->base ? $this->base->getNormalized()
104114
public function getRootUrl() { return $this->rootUrl; }
105115
public function getRelativePath() { return $this->relativePath; }
106116

107-
public function setScheme($scheme) { $this->scheme = $scheme; }
108-
public function setPort($port) { $this->port = $port; }
109-
public function setHost($host) { $this->host = $host; }
110-
public function setPath($path) { $this->path = $path; }
111117
public function setQuery($query) { $this->query = $query; }
112118
public function setHash($hash) { $this->hash = $hash; }
113119

120+
public function setPath($path) {
121+
$this->path = $path;
122+
$this->updateRelativePath();
123+
}
124+
125+
public function setPort($port) {
126+
$this->port = $port;
127+
$this->updateRootUrl();
128+
}
129+
130+
public function setScheme($scheme) {
131+
$this->scheme = $scheme;
132+
$this->updateRootUrl();
133+
}
134+
135+
public function setHost($host) {
136+
$this->host = $host;
137+
$this->updateRootUrl();
138+
}
139+
114140
public function setBaseUrl($url) {
115141
if ($url instanceof Url) {
116142
$this->base = $url;
@@ -119,7 +145,7 @@ public function setBaseUrl($url) {
119145
}
120146

121147
if ($this->assumedHost) {
122-
$this->path = $this->assumedPath ? $this->host : $this->host.$this->path;
148+
$this->path = $this->assumedPath ? $this->hostUnmodified : $this->host.$this->path;
123149
$this->host = NULL;
124150
$this->assumedHost = false;
125151
$this->assumedPath = false;
@@ -130,8 +156,6 @@ public function setBaseUrl($url) {
130156

131157
public function getNormalized($resolvePathNavigation = true, $includeHash = true) {
132158
$path = $this->path;
133-
$query = $this->query;
134-
$hash = $this->hash;
135159

136160
$url = "";
137161
if (strlen($path) > 0 && $path[0] == "/") { // absolute path - use rootUrl
@@ -145,6 +169,15 @@ public function getNormalized($resolvePathNavigation = true, $includeHash = true
145169
$path = $this->resolvePathNavigation($path, $resolvePathNavigation);
146170
}
147171

172+
if (strpos($path,'%') !== false) {
173+
// Based on RFC3986 (https://www.ietf.org/rfc/rfc3986.txt):
174+
// For consistency, URI producers and normalizers should use uppercase hexadecimal digits for all
175+
// percent-encodings.
176+
$path = preg_replace_callback('/%[a-fA-F\d]{2}/', function ($matches) {
177+
return strtoupper($matches[0]);
178+
}, $path);
179+
}
180+
148181
$path_parts = explode('/', $path);
149182
$final_parts = array();
150183

@@ -177,6 +210,42 @@ public function getNormalized($resolvePathNavigation = true, $includeHash = true
177210
return $url;
178211
}
179212

213+
/**
214+
* Checks if the URL object produces a valid URL
215+
* @return boolean
216+
*/
217+
public function isValid() {
218+
try {
219+
$originalHost = $this->getHost();
220+
// Add more compatibility chars in the array below
221+
// FILTER_VALIDATE_URL validates against http://www.faqs.org/rfcs/rfc2396.html,
222+
// which, for example, treats underscore("_") as invalid for hosts.
223+
$charsToReplace = ['_'];
224+
$replacementChar = '-';
225+
226+
if (empty($originalHost)) {
227+
// probably a relative path
228+
return false;
229+
}
230+
231+
// do we expect to have multibyte string for URL?
232+
// filter_var will also fail with multibyte string as URL
233+
if (!empty(array_intersect($charsToReplace, str_split($originalHost)))) {
234+
$newHost = str_replace($charsToReplace, $replacementChar, $originalHost);
235+
$this->setHost($newHost);
236+
}
237+
238+
if (filter_var($this->getNormalized(), FILTER_VALIDATE_URL) === false) {
239+
return false;
240+
}
241+
242+
return true;
243+
} finally {
244+
// Restore the original host
245+
$this->setHost($originalHost);
246+
}
247+
}
248+
180249
private function normalizeQueryStr($queryStr) {
181250
$queryStr = rawurldecode($queryStr);
182251
$newQueryStr = "";

tests/UrlTest.php

+51
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ public function testHostToLower()
2929
{
3030
$url = new Url('https://NITROPACK.IO/');
3131
$this->assertEquals('nitropack.io', $url->getHost());
32+
33+
$url2 = new Url('NITROPACK.IO');
34+
$this->assertEquals('nitropack.io', $url2->getHost());
35+
36+
$url3 = new Url('NITROPACK.IO/test');
37+
$this->assertEquals('nitropack.io', $url3->getHost());
3238
}
3339

3440
public function testDetectAsHost()
@@ -51,5 +57,50 @@ public function testNormalization()
5157
$this->assertEquals('http://nitropack.io/', $url->getBaseUrl());
5258
$this->assertEquals('http://nitropack.io/test-page', $url->getNormalized());
5359
$this->assertEquals('test-page', $url->getPath());
60+
61+
$url2 = new Url('Test.woff');
62+
$url2->setBaseUrl("https://example.com/");
63+
$this->assertEquals('https://example.com/Test.woff', $url2->getNormalized());
64+
}
65+
66+
public function testPercentEncodingToUpper()
67+
{
68+
// only deals with the path part of the URL
69+
// format: test URL => expected result
70+
$testUrlSet = [
71+
'https://www.example.com/%rf' => 'https://www.example.com/%rf',
72+
'https://www.example.com/%D8%AA%D8%B5%D9%86%D9%8A%D9%81/%d8%a8%d9%88%d9%8a%d8%a7%d8%aa-%d9%88%d8%a3%d9%83%d8%b3%d8%b3%d9%88%d8%a7%d8%b1%d8%a7%d8%aa/' =>
73+
'https://www.example.com/%D8%AA%D8%B5%D9%86%D9%8A%D9%81/%D8%A8%D9%88%D9%8A%D8%A7%D8%AA-%D9%88%D8%A3%D9%83%D8%B3%D8%B3%D9%88%D8%A7%D8%B1%D8%A7%D8%AA/',
74+
'https://www.example.co.uk/%d7%90%d7%99%d7%aa%d7%95%d7%a8-%d7%a0%d7%96%d7%99%d7%9c%d7%95%d7%aa-%d7%9e%d7%aa%d7%97%d7%aa-%d7%9c%d7%a8%d7%a6%d7%a4%d7%94/' =>
75+
'https://www.example.co.uk/%D7%90%D7%99%D7%AA%D7%95%D7%A8-%D7%A0%D7%96%D7%99%D7%9C%D7%95%D7%AA-%D7%9E%D7%AA%D7%97%D7%AA-%D7%9C%D7%A8%D7%A6%D7%A4%D7%94/',
76+
'https://www.example.com' => 'https://www.example.com/',
77+
'https://www.example.com/%A8Untitled' => 'https://www.example.com/%A8Untitled',
78+
'https://example.com/%25' => 'https://example.com/%25',
79+
'https://example.com/?param01=%d0%ba%d0%be%d0%ba%d0%be&param02=%d0%b4%d0%b6%d1%8a%d0%bc%d0%b1%d0%be' => 'https://example.com/?param01=%D0%BA%D0%BE%D0%BA%D0%BE&param02=%D0%B4%D0%B6%D1%8A%D0%BC%D0%B1%D0%BE',
80+
'https://example.com?%d0%ba%d0%be%d0%ba%d0%be=%d0%b4%d0%b6%d1%8a%d0%bc%d0%b1%d0%be' => 'https://example.com/?%D0%BA%D0%BE%D0%BA%D0%BE=%D0%B4%D0%B6%D1%8A%D0%BC%D0%B1%D0%BE',
81+
'https://example.com?%d0%ba%d0%be%d0%ba%d0%be=' => 'https://example.com/?%D0%BA%D0%BE%D0%BA%D0%BE=',
82+
'https://example.com?%d0%ba%d0%be%d0%ba%d0%be' => 'https://example.com/?%D0%BA%D0%BE%D0%BA%D0%BE',
83+
];
84+
85+
foreach ($testUrlSet as $inputUrl => $expectedResult) {
86+
$testUrl = new Url($inputUrl);
87+
$this->assertEquals($expectedResult, $testUrl->getNormalized());
88+
}
89+
}
90+
91+
public function testUrlValidation()
92+
{
93+
$testUrlSet = [
94+
"http://sub_domain.example.com/path/path.html" => true,
95+
"http://sub>domain.example.com/" => false,
96+
"https://7486822895993461897%6fe6c0fbdf0d210eecb7e5d644a411d037c435af.example.com/" => false,
97+
"https://sub*domain.example.com/" => false,
98+
"/index.html" => false,
99+
];
100+
101+
foreach ($testUrlSet as $inputUrl => $expectedResult) {
102+
$testUrl = new Url($inputUrl);
103+
$this->assertEquals($expectedResult, $testUrl->isValid());
104+
}
54105
}
55106
}

0 commit comments

Comments
 (0)