From 7ac527d72eb718406ea1a904e12fd2e37bc4b7e8 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 14 Feb 2023 17:25:02 +0900 Subject: [PATCH 01/31] feat: add SiteURI class --- system/HTTP/SiteURI.php | 312 ++++++++++++++++++++++++++++++ system/HTTP/URI.php | 2 + tests/system/HTTP/SiteURITest.php | 245 +++++++++++++++++++++++ 3 files changed, 559 insertions(+) create mode 100644 system/HTTP/SiteURI.php create mode 100644 tests/system/HTTP/SiteURITest.php diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php new file mode 100644 index 000000000000..bf3e06903188 --- /dev/null +++ b/system/HTTP/SiteURI.php @@ -0,0 +1,312 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP; + +use BadMethodCallException; +use CodeIgniter\HTTP\Exceptions\HTTPException; +use Config\App; + +/** + * URI for the application site + */ +class SiteURI extends URI +{ + /** + * The baseURL. + */ + private string $baseURL; + + /** + * The Index File. + */ + private string $indexPage; + + /** + * List of URI segments in baseURL and indexPage. + * + * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b", + * and the baseUR is "http://localhost:8888/ci431/public/", then: + * $baseSegments = [ + * 0 => 'ci431', + * 1 => 'public', + * 2 => 'index.php', + * ]; + */ + private array $baseSegments; + + /** + * List of URI segments after indexPage. + * + * The word "URI Segments" originally means only the URI path part relative + * to the baseURL. + * + * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b", + * and the baseUR is "http://localhost:8888/ci431/public/", then: + * $segments = [ + * 0 => 'test', + * ]; + * + * @var array + * + * @deprecated This property will be private. + */ + protected $segments; + + /** + * URI path relative to baseURL. + * + * If the baseURL contains sub folders, this value will be different from + * the current URI path. + */ + private string $routePath; + + public function __construct(App $configApp) + { + // It's possible the user forgot a trailing slash on their + // baseURL, so let's help them out. + $baseURL = rtrim($configApp->baseURL, '/ ') . '/'; + + $this->baseURL = $baseURL; + $this->indexPage = $configApp->indexPage; + + $this->setBaseSeegments(); + + // Check for an index page + $indexPage = ''; + if ($configApp->indexPage !== '') { + $indexPage = $configApp->indexPage . '/'; + } + + $tempUri = $this->baseURL . $indexPage; + $uri = new URI($tempUri); + + if ($configApp->forceGlobalSecureRequests) { + $uri->setScheme('https'); + } + + $parts = parse_url((string) $uri); + if ($parts === false) { + throw HTTPException::forUnableToParseURI($uri); + } + $this->applyParts($parts); + + $this->setPath('/'); + } + + /** + * Sets baseSegments. + */ + private function setBaseSeegments(): void + { + $basePath = (new URI($this->baseURL))->getPath(); + $this->baseSegments = $this->convertToSegments($basePath); + + if ($this->indexPage) { + $this->baseSegments[] = $this->indexPage; + } + } + + public function setURI(?string $uri = null) + { + throw new BadMethodCallException('Cannot use this method.'); + } + + /** + * Returns the URI path relative to baseURL. + * + * @return string The Route path. + */ + public function getRoutePath(): string + { + return $this->routePath; + } + + /** + * Returns the URI segments of the path as an array. + */ + public function getSegments(): array + { + return $this->segments; + } + + /** + * Returns the value of a specific segment of the URI path relative to baseURL. + * + * @param int $number Segment number + * @param string $default Default value + * + * @return string The value of the segment. If no segment is found, + * throws HTTPException + */ + public function getSegment(int $number, string $default = ''): string + { + if ($number < 1) { + throw HTTPException::forURISegmentOutOfRange($number); + } + + if ($number > count($this->segments) && ! $this->silent) { + throw HTTPException::forURISegmentOutOfRange($number); + } + + // The segment should treat the array as 1-based for the user + // but we still have to deal with a zero-based array. + $number--; + + return $this->segments[$number] ?? $default; + } + + /** + * Set the value of a specific segment of the URI path relative to baseURL. + * Allows to set only existing segments or add new one. + * + * @param int $number The segment number. Starting with 1. + * @param string $value The segment value. + * + * @return $this + */ + public function setSegment(int $number, $value) + { + if ($number < 1) { + throw HTTPException::forURISegmentOutOfRange($number); + } + + if ($number > count($this->segments) + 1) { + if ($this->silent) { + return $this; + } + + throw HTTPException::forURISegmentOutOfRange($number); + } + + // The segment should treat the array as 1-based for the user, + // but we still have to deal with a zero-based array. + $number--; + + $this->segments[$number] = $value; + + $this->refreshPath(); + + return $this; + } + + /** + * Returns the total number of segments. + */ + public function getTotalSegments(): int + { + return count($this->segments); + } + + /** + * Formats the URI as a string. + */ + public function __toString(): string + { + return static::createURIString( + $this->getScheme(), + $this->getAuthority(), + $this->getPath(), // Absolute URIs should use a "/" for an empty path + $this->getQuery(), + $this->getFragment() + ); + } + + /** + * Sets the route path (and segments). + * + * @return $this + */ + public function setPath(string $path) + { + $this->routePath = $this->filterPath($path); + + $this->segments = $this->convertToSegments($this->routePath); + + $this->refreshPath(); + + return $this; + } + + /** + * Converts path to segments + */ + private function convertToSegments(string $path): array + { + $tempPath = trim($path, '/'); + + return ($tempPath === '') ? [] : explode('/', $tempPath); + } + + /** + * Sets the path portion of the URI based on segments. + * + * @return $this + * + * @deprecated This method will be private. + */ + public function refreshPath() + { + $allSegments = array_merge($this->baseSegments, $this->segments); + $this->path = '/' . $this->filterPath(implode('/', $allSegments)); + + $this->routePath = $this->filterPath(implode('/', $this->segments)); + + if ($this->routePath === '') { + $this->routePath = '/'; + + if ($this->indexPage !== '') { + $this->path .= '/'; + } + } + + return $this; + } + + /** + * Saves our parts from a parse_url() call. + */ + protected function applyParts(array $parts) + { + if (! empty($parts['host'])) { + $this->host = $parts['host']; + } + if (! empty($parts['user'])) { + $this->user = $parts['user']; + } + if (isset($parts['path']) && $parts['path'] !== '') { + $this->path = $this->filterPath($parts['path']); + } + if (! empty($parts['query'])) { + $this->setQuery($parts['query']); + } + if (! empty($parts['fragment'])) { + $this->fragment = $parts['fragment']; + } + + // Scheme + if (isset($parts['scheme'])) { + $this->setScheme(rtrim($parts['scheme'], ':/')); + } else { + $this->setScheme('http'); + } + + // Port + if (isset($parts['port']) && $parts['port'] !== null) { + // Valid port numbers are enforced by earlier parse_url() or setPort() + $this->port = $parts['port']; + } + + if (isset($parts['pass'])) { + $this->password = $parts['pass']; + } + } +} diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 1e11953051bb..55c359d17d98 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -797,6 +797,8 @@ public function getBaseURL(): string * Sets the path portion of the URI based on segments. * * @return $this + * + * @deprecated This method will be private. */ public function refreshPath() { diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php new file mode 100644 index 000000000000..3a7c532519f6 --- /dev/null +++ b/tests/system/HTTP/SiteURITest.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\HTTP; + +use BadMethodCallException; +use CodeIgniter\HTTP\Exceptions\HTTPException; +use CodeIgniter\Test\CIUnitTestCase; +use Config\App; + +/** + * @backupGlobals enabled + * + * @internal + * + * @group Others + */ +final class SiteURITest extends CIUnitTestCase +{ + public function testConstructor() + { + $config = new App(); + + $uri = new SiteURI($config); + + $this->assertInstanceOf(SiteURI::class, $uri); + $this->assertSame('http://example.com/index.php/', (string) $uri); + $this->assertSame('/index.php/', $uri->getPath()); + } + + public function testConstructorSubfolder() + { + $config = new App(); + $config->baseURL = 'http://example.com/ci4/'; + + $uri = new SiteURI($config); + + $this->assertInstanceOf(SiteURI::class, $uri); + $this->assertSame('http://example.com/ci4/index.php/', (string) $uri); + $this->assertSame('/ci4/index.php/', $uri->getPath()); + } + + public function testConstructorForceGlobalSecureRequests() + { + $config = new App(); + $config->forceGlobalSecureRequests = true; + + $uri = new SiteURI($config); + + $this->assertSame('https://example.com/index.php/', (string) $uri); + } + + public function testConstructorIndexPageEmpty() + { + $config = new App(); + $config->indexPage = ''; + + $uri = new SiteURI($config); + + $this->assertSame('http://example.com/', (string) $uri); + } + + public function testSetPath() + { + $config = new App(); + + $uri = new SiteURI($config); + + $uri->setPath('test/method'); + + $this->assertSame('http://example.com/index.php/test/method', (string) $uri); + $this->assertSame('test/method', $uri->getRoutePath()); + $this->assertSame('/index.php/test/method', $uri->getPath()); + $this->assertSame(['test', 'method'], $uri->getSegments()); + $this->assertSame('test', $uri->getSegment(1)); + $this->assertSame(2, $uri->getTotalSegments()); + } + + public function testSetPathSubfolder() + { + $config = new App(); + $config->baseURL = 'http://example.com/ci4/'; + + $uri = new SiteURI($config); + + $uri->setPath('test/method'); + + $this->assertSame('http://example.com/ci4/index.php/test/method', (string) $uri); + $this->assertSame('test/method', $uri->getRoutePath()); + $this->assertSame('/ci4/index.php/test/method', $uri->getPath()); + $this->assertSame(['test', 'method'], $uri->getSegments()); + $this->assertSame('test', $uri->getSegment(1)); + $this->assertSame(2, $uri->getTotalSegments()); + } + + public function testSetPathEmpty() + { + $config = new App(); + + $uri = new SiteURI($config); + + $uri->setPath(''); + + $this->assertSame('http://example.com/index.php/', (string) $uri); + $this->assertSame('/', $uri->getRoutePath()); + $this->assertSame('/index.php/', $uri->getPath()); + $this->assertSame([], $uri->getSegments()); + $this->assertSame(0, $uri->getTotalSegments()); + } + + public function testSetSegment() + { + $config = new App(); + + $uri = new SiteURI($config); + $uri->setPath('test/method'); + + $uri->setSegment(1, 'one'); + + $this->assertSame('http://example.com/index.php/one/method', (string) $uri); + $this->assertSame('one/method', $uri->getRoutePath()); + $this->assertSame('/index.php/one/method', $uri->getPath()); + $this->assertSame(['one', 'method'], $uri->getSegments()); + $this->assertSame('one', $uri->getSegment(1)); + $this->assertSame(2, $uri->getTotalSegments()); + } + + public function testSetSegmentOutOfRange() + { + $this->expectException(HTTPException::class); + + $config = new App(); + $uri = new SiteURI($config); + $uri->setPath('test/method'); + + $uri->setSegment(4, 'four'); + } + + public function testSetSegmentSilentOutOfRange() + { + $config = new App(); + $uri = new SiteURI($config); + $uri->setPath('one/method'); + $uri->setSilent(); + + $uri->setSegment(4, 'four'); + $this->assertSame(['one', 'method'], $uri->getSegments()); + } + + public function testSetSegmentZero() + { + $this->expectException(HTTPException::class); + + $config = new App(); + $uri = new SiteURI($config); + $uri->setPath('test/method'); + + $uri->setSegment(0, 'four'); + } + + public function testSetSegmentSubfolder() + { + $config = new App(); + $config->baseURL = 'http://example.com/ci4/'; + + $uri = new SiteURI($config); + $uri->setPath('test/method'); + + $uri->setSegment(1, 'one'); + + $this->assertSame('http://example.com/ci4/index.php/one/method', (string) $uri); + $this->assertSame('one/method', $uri->getRoutePath()); + $this->assertSame('/ci4/index.php/one/method', $uri->getPath()); + $this->assertSame(['one', 'method'], $uri->getSegments()); + $this->assertSame('one', $uri->getSegment(1)); + $this->assertSame(2, $uri->getTotalSegments()); + } + + public function testGetRoutePath() + { + $config = new App(); + $uri = new SiteURI($config); + + $this->assertSame('/', $uri->getRoutePath()); + } + + public function testGetSegments() + { + $config = new App(); + $uri = new SiteURI($config); + + $this->assertSame([], $uri->getSegments()); + } + + public function testGetSegmentZero() + { + $this->expectException(HTTPException::class); + + $config = new App(); + $uri = new SiteURI($config); + $uri->setPath('test/method'); + + $uri->getSegment(0); + } + + public function testGetSegmentOutOfRange() + { + $this->expectException(HTTPException::class); + + $config = new App(); + $uri = new SiteURI($config); + $uri->setPath('test/method'); + + $this->assertSame('method', $uri->getSegment(2)); + $this->assertSame('', $uri->getSegment(3)); + + $uri->getSegment(4); + } + + public function testGetTotalSegments() + { + $config = new App(); + $uri = new SiteURI($config); + + $this->assertSame(0, $uri->getTotalSegments()); + } + + public function testSetURI() + { + $this->expectException(BadMethodCallException::class); + + $config = new App(); + $uri = new SiteURI($config); + + $uri->setURI('http://another.site.example.jp/'); + } +} From 7eec7bb9e146a648e7fa16f2dbd8d26b1c84d466 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 14 Feb 2023 17:57:52 +0900 Subject: [PATCH 02/31] chore: add system/HTTP/SiteURI.php --- .github/workflows/test-phpcpd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-phpcpd.yml b/.github/workflows/test-phpcpd.yml index 31dcdb232749..f08945514386 100644 --- a/.github/workflows/test-phpcpd.yml +++ b/.github/workflows/test-phpcpd.yml @@ -55,4 +55,5 @@ jobs: --exclude system/Database/OCI8/Builder.php --exclude system/Database/Postgre/Builder.php --exclude system/Debug/Exceptions.php + --exclude system/HTTP/SiteURI.php -- app/ public/ system/ From 5593f8a481edc7f7e21950e996b270fd10637387 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 14 Feb 2023 18:48:16 +0900 Subject: [PATCH 03/31] docs: add @deprecated --- system/HTTP/URI.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 55c359d17d98..9b525e627fc6 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -34,11 +34,15 @@ class URI * Current URI string * * @var string + * + * @deprecated Not used. */ protected $uriString; /** * The Current baseURL. + * + * @deprecated Use SiteURI instead. */ private ?string $baseURL = null; @@ -773,6 +777,8 @@ public function setPath(string $path) * Sets the current baseURL. * * @interal + * + * @deprecated Use SiteURI instead. */ public function setBaseURL(string $baseURL): void { @@ -783,6 +789,8 @@ public function setBaseURL(string $baseURL): void * Returns the current baseURL. * * @interal + * + * @deprecated Use SiteURI instead. */ public function getBaseURL(): string { From 8a66de596999aa42b649bb48bbec173af068a50b Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 14 Feb 2023 21:14:31 +0900 Subject: [PATCH 04/31] test: fix incorrect test One test one assertion. --- tests/system/HTTP/SiteURITest.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 3a7c532519f6..d92421e58830 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -219,10 +219,7 @@ public function testGetSegmentOutOfRange() $uri = new SiteURI($config); $uri->setPath('test/method'); - $this->assertSame('method', $uri->getSegment(2)); - $this->assertSame('', $uri->getSegment(3)); - - $uri->getSegment(4); + $uri->getSegment(3); } public function testGetTotalSegments() From 406128f65d535eaf387750d9ec37a27ad77ce0c5 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 15 Feb 2023 08:18:15 +0900 Subject: [PATCH 05/31] refactor: fix typo in method name --- system/HTTP/SiteURI.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index bf3e06903188..6de923e66307 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -78,7 +78,7 @@ public function __construct(App $configApp) $this->baseURL = $baseURL; $this->indexPage = $configApp->indexPage; - $this->setBaseSeegments(); + $this->setBaseSegments(); // Check for an index page $indexPage = ''; @@ -105,7 +105,7 @@ public function __construct(App $configApp) /** * Sets baseSegments. */ - private function setBaseSeegments(): void + private function setBaseSegments(): void { $basePath = (new URI($this->baseURL))->getPath(); $this->baseSegments = $this->convertToSegments($basePath); From 7d91c0e9bdb5791c0c718a765cae91058869a8c3 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 15 Feb 2023 08:41:37 +0900 Subject: [PATCH 06/31] docs: update PHPDocs --- system/HTTP/URI.php | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 9b525e627fc6..4f12b3464260 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -263,6 +263,8 @@ public function __construct(?string $uri = null) * If $silent == true, then will not throw exceptions and will * attempt to continue gracefully. * + * Note: Method not in PSR-7 + * * @return URI */ public function setSilent(bool $silent = true) @@ -276,6 +278,8 @@ public function setSilent(bool $silent = true) * If $raw == true, then will use parseStr() method * instead of native parse_str() function. * + * Note: Method not in PSR-7 + * * @return URI */ public function useRawQueryString(bool $raw = true) @@ -291,6 +295,8 @@ public function useRawQueryString(bool $raw = true) * @return URI * * @throws HTTPException + * + * @deprecated This method will be private. */ public function setURI(?string $uri = null) { @@ -408,6 +414,8 @@ public function getUserInfo() * Temporarily sets the URI to show a password in userInfo. Will * reset itself after the first call to authority(). * + * Note: Method not in PSR-7 + * * @return URI */ public function showPassword(bool $val = true) @@ -567,6 +575,8 @@ public function getSegment(int $number, string $default = ''): string * Set the value of a specific segment of the URI path. * Allows to set only existing segments or add new one. * + * Note: Method not in PSR-7 + * * @param int $number Segment number starting at 1 * @param int|string $value * @@ -598,6 +608,8 @@ public function setSegment(int $number, $value) /** * Returns the total number of segments. + * + * Note: Method not in PSR-7 */ public function getTotalSegments(): int { @@ -665,6 +677,8 @@ private function changeSchemeAndPath(string $scheme, string $path): array /** * Parses the given string and saves the appropriate authority pieces. * + * Note: Method not in PSR-7 + * * @return $this */ public function setAuthority(string $str) @@ -694,6 +708,8 @@ public function setAuthority(string $str) * @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml * * @return $this + * + * @TODO PSR-7: Should be `withScheme($scheme)`. */ public function setScheme(string $str) { @@ -710,6 +726,8 @@ public function setScheme(string $str) * @param string $pass The user's password * * @return $this + * + * @TODO PSR-7: Should be `withUserInfo($user, $password = null)`. */ public function setUserInfo(string $user, string $pass) { @@ -723,6 +741,8 @@ public function setUserInfo(string $user, string $pass) * Sets the host name to use. * * @return $this + * + * @TODO PSR-7: Should be `withHost($host)`. */ public function setHost(string $str) { @@ -737,6 +757,8 @@ public function setHost(string $str) * @param int $port * * @return $this + * + * @TODO PSR-7: Should be `withPort($port)`. */ public function setPort(?int $port = null) { @@ -761,6 +783,8 @@ public function setPort(?int $port = null) * Sets the path portion of the URI. * * @return $this + * + * @TODO PSR-7: Should be `withPath($port)`. */ public function setPath(string $path) { @@ -824,6 +848,8 @@ public function refreshPath() * to clean the various parts of the query keys and values. * * @return $this + * + * @TODO PSR-7: Should be `withQuery($query)`. */ public function setQuery(string $query) { @@ -854,6 +880,8 @@ public function setQuery(string $query) * portion of the URI. * * @return URI + * + * @TODO: PSR-7: Should be `withQueryParams(array $query)` */ public function setQueryArray(array $query) { @@ -865,7 +893,9 @@ public function setQueryArray(array $query) /** * Adds a single new element to the query vars. * - * @param int|string $value + * Note: Method not in PSR-7 + * + * @param int|string|null $value * * @return $this */ @@ -879,6 +909,8 @@ public function addQuery(string $key, $value = null) /** * Removes one or more query vars from the URI. * + * Note: Method not in PSR-7 + * * @param string ...$params * * @return $this @@ -896,6 +928,8 @@ public function stripQuery(...$params) * Filters the query variables so that only the keys passed in * are kept. The rest are removed from the object. * + * Note: Method not in PSR-7 + * * @param string ...$params * * @return $this @@ -923,6 +957,8 @@ public function keepQuery(...$params) * @see https://tools.ietf.org/html/rfc3986#section-3.5 * * @return $this + * + * @TODO PSR-7: Should be `withFragment($fragment)`. */ public function setFragment(string $string) { From b728948d91c3eb94a3e6511962f29969871524a0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 15 Feb 2023 13:06:15 +0900 Subject: [PATCH 07/31] docs: add @deprecated --- system/HTTP/SiteURI.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 6de923e66307..4cad657ca1f3 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -115,6 +115,9 @@ private function setBaseSegments(): void } } + /** + * @deprecated + */ public function setURI(?string $uri = null) { throw new BadMethodCallException('Cannot use this method.'); From 9af4994bc3bd15ef3436cc9e17b5aaa8f219b8f2 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 15 Feb 2023 13:33:22 +0900 Subject: [PATCH 08/31] feat: add validation for baseURL --- system/HTTP/SiteURI.php | 8 ++++++++ tests/system/HTTP/SiteURITest.php | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 4cad657ca1f3..11299468fc1c 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -12,6 +12,7 @@ namespace CodeIgniter\HTTP; use BadMethodCallException; +use CodeIgniter\Exceptions\ConfigException; use CodeIgniter\HTTP\Exceptions\HTTPException; use Config\App; @@ -75,6 +76,13 @@ public function __construct(App $configApp) // baseURL, so let's help them out. $baseURL = rtrim($configApp->baseURL, '/ ') . '/'; + // Validate baseURL + if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) { + throw new ConfigException( + 'Config\App::$baseURL is invalid.' + ); + } + $this->baseURL = $baseURL; $this->indexPage = $configApp->indexPage; diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index d92421e58830..833e52747ceb 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -12,6 +12,7 @@ namespace CodeIgniter\HTTP; use BadMethodCallException; +use CodeIgniter\Exceptions\ConfigException; use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\Test\CIUnitTestCase; use Config\App; @@ -68,6 +69,16 @@ public function testConstructorIndexPageEmpty() $this->assertSame('http://example.com/', (string) $uri); } + public function testConstructorInvalidBaseURL() + { + $this->expectException(ConfigException::class); + + $config = new App(); + $config->baseURL = 'invalid'; + + new SiteURI($config); + } + public function testSetPath() { $config = new App(); From 5fa53059ff93fa94cce56ea4272244c7f27472cd Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 15 Feb 2023 13:33:56 +0900 Subject: [PATCH 09/31] feat: add getBaseURL() implementation --- system/HTTP/SiteURI.php | 10 ++++++++++ tests/system/HTTP/SiteURITest.php | 8 ++++++++ 2 files changed, 18 insertions(+) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 11299468fc1c..4c5e66417158 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -131,6 +131,16 @@ public function setURI(?string $uri = null) throw new BadMethodCallException('Cannot use this method.'); } + /** + * Returns the baseURL. + * + * @interal + */ + public function getBaseURL(): string + { + return $this->baseURL; + } + /** * Returns the URI path relative to baseURL. * diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 833e52747ceb..5d0a3d134024 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -250,4 +250,12 @@ public function testSetURI() $uri->setURI('http://another.site.example.jp/'); } + + public function testGetBaseURL() + { + $config = new App(); + $uri = new SiteURI($config); + + $this->assertSame('http://example.com/', $uri->getBaseURL()); + } } From c8d5eefaf1514e88cd52abf10552681dd43b5bef Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 08:32:43 +0900 Subject: [PATCH 10/31] feat: add param $relativePath to constructor Change the condition to add / after index.php. --- system/HTTP/SiteURI.php | 62 ++++++++++++++++++++++--------- tests/system/HTTP/SiteURITest.php | 33 ++++++++++++++++ 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 4c5e66417158..44f819ea8f43 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -70,20 +70,13 @@ class SiteURI extends URI */ private string $routePath; - public function __construct(App $configApp) + /** + * @param string $relativePath URI path relative to baseURL. May include + * queries or fragments. + */ + public function __construct(App $configApp, string $relativePath = '') { - // It's possible the user forgot a trailing slash on their - // baseURL, so let's help them out. - $baseURL = rtrim($configApp->baseURL, '/ ') . '/'; - - // Validate baseURL - if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) { - throw new ConfigException( - 'Config\App::$baseURL is invalid.' - ); - } - - $this->baseURL = $baseURL; + $this->baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; $this->setBaseSegments(); @@ -91,10 +84,17 @@ public function __construct(App $configApp) // Check for an index page $indexPage = ''; if ($configApp->indexPage !== '') { - $indexPage = $configApp->indexPage . '/'; + $indexPage = $configApp->indexPage; + + // Check if we need a separator + if ($relativePath !== '' && $relativePath[0] !== '/' && $relativePath[0] !== '?') { + $indexPage .= '/'; + } } - $tempUri = $this->baseURL . $indexPage; + $relativePath = URI::removeDotSegments($relativePath); + + $tempUri = $this->baseURL . $indexPage . $relativePath; $uri = new URI($tempUri); if ($configApp->forceGlobalSecureRequests) { @@ -107,7 +107,25 @@ public function __construct(App $configApp) } $this->applyParts($parts); - $this->setPath('/'); + $parts = explode('?', $relativePath); + $routePath = $parts[0]; + $this->setRoutePath($routePath); + } + + private function normalizeBaseURL(App $configApp): string + { + // It's possible the user forgot a trailing slash on their + // baseURL, so let's help them out. + $baseURL = rtrim($configApp->baseURL, '/ ') . '/'; + + // Validate baseURL + if (filter_var($baseURL, FILTER_VALIDATE_URL) === false) { + throw new ConfigException( + 'Config\App::$baseURL is invalid.' + ); + } + + return $baseURL; } /** @@ -247,14 +265,22 @@ public function __toString(): string * @return $this */ public function setPath(string $path) + { + $this->setRoutePath($path); + + return $this; + } + + /** + * Sets the route path (and segments). + */ + private function setRoutePath(string $path): void { $this->routePath = $this->filterPath($path); $this->segments = $this->convertToSegments($this->routePath); $this->refreshPath(); - - return $this; } /** diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 5d0a3d134024..74e035868196 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -37,6 +37,27 @@ public function testConstructor() $this->assertSame('/index.php/', $uri->getPath()); } + public function testConstructorRelativePath() + { + $config = new App(); + + $uri = new SiteURI($config, 'one/two'); + + $this->assertSame('http://example.com/index.php/one/two', (string) $uri); + $this->assertSame('/index.php/one/two', $uri->getPath()); + } + + public function testConstructorRelativePathWithQuery() + { + $config = new App(); + + $uri = new SiteURI($config, 'one/two?foo=1&bar=2'); + + $this->assertSame('http://example.com/index.php/one/two?foo=1&bar=2', (string) $uri); + $this->assertSame('/index.php/one/two', $uri->getPath()); + $this->assertSame('foo=1&bar=2', $uri->getQuery()); + } + public function testConstructorSubfolder() { $config = new App(); @@ -49,6 +70,18 @@ public function testConstructorSubfolder() $this->assertSame('/ci4/index.php/', $uri->getPath()); } + public function testConstructorSubfolderRelativePathWithQuery() + { + $config = new App(); + $config->baseURL = 'http://example.com/ci4/'; + + $uri = new SiteURI($config, 'one/two?foo=1&bar=2'); + + $this->assertSame('http://example.com/ci4/index.php/one/two?foo=1&bar=2', (string) $uri); + $this->assertSame('/ci4/index.php/one/two', $uri->getPath()); + $this->assertSame('foo=1&bar=2', $uri->getQuery()); + } + public function testConstructorForceGlobalSecureRequests() { $config = new App(); From b30035be7390acf50c366a3c7c2ded51c7299f38 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 08:58:14 +0900 Subject: [PATCH 11/31] feat: add param $host to constructor --- system/HTTP/SiteURI.php | 16 +++++++++++++++- tests/system/HTTP/SiteURITest.php | 12 ++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 44f819ea8f43..61f9cbbe214d 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -73,8 +73,10 @@ class SiteURI extends URI /** * @param string $relativePath URI path relative to baseURL. May include * queries or fragments. + * @param string $host Hostname. If it is not in $allowedHostnames, + * just be ignored. */ - public function __construct(App $configApp, string $relativePath = '') + public function __construct(App $configApp, string $relativePath = '', string $host = '') { $this->baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; @@ -97,21 +99,33 @@ public function __construct(App $configApp, string $relativePath = '') $tempUri = $this->baseURL . $indexPage . $relativePath; $uri = new URI($tempUri); + // Update scheme if ($configApp->forceGlobalSecureRequests) { $uri->setScheme('https'); } + // Update host + if ($host !== '' && $this->checkHost($host, $configApp->allowedHostnames)) { + $uri->setHost($host); + } + $parts = parse_url((string) $uri); if ($parts === false) { throw HTTPException::forUnableToParseURI($uri); } $this->applyParts($parts); + // Set routePath $parts = explode('?', $relativePath); $routePath = $parts[0]; $this->setRoutePath($routePath); } + private function checkHost(string $host, array $allowedHostnames): bool + { + return in_array($host, $allowedHostnames, true); + } + private function normalizeBaseURL(App $configApp): string { // It's possible the user forgot a trailing slash on their diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 74e035868196..413ab66ee57c 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -58,6 +58,18 @@ public function testConstructorRelativePathWithQuery() $this->assertSame('foo=1&bar=2', $uri->getQuery()); } + public function testConstructorHost() + { + $config = new App(); + $config->allowedHostnames = ['sub.example.com']; + + $uri = new SiteURI($config, '', 'sub.example.com'); + + $this->assertInstanceOf(SiteURI::class, $uri); + $this->assertSame('http://sub.example.com/index.php/', (string) $uri); + $this->assertSame('/index.php/', $uri->getPath()); + } + public function testConstructorSubfolder() { $config = new App(); From 9d823382ac66d00598417b495bb17150c1b967af Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 09:06:22 +0900 Subject: [PATCH 12/31] feat: add param $scheme to constructor --- system/HTTP/SiteURI.php | 10 +++++++--- tests/system/HTTP/SiteURITest.php | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 61f9cbbe214d..ea2bab91078f 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -73,10 +73,12 @@ class SiteURI extends URI /** * @param string $relativePath URI path relative to baseURL. May include * queries or fragments. - * @param string $host Hostname. If it is not in $allowedHostnames, + * @param string $host Optional hostname. If it is not in $allowedHostnames, * just be ignored. + * @param string $scheme Optional scheme. 'http' or 'https'. + * @phpstan-param 'http'|'https'|'' $scheme */ - public function __construct(App $configApp, string $relativePath = '', string $host = '') + public function __construct(App $configApp, string $relativePath = '', string $host = '', string $scheme = '') { $this->baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; @@ -100,7 +102,9 @@ public function __construct(App $configApp, string $relativePath = '', string $h $uri = new URI($tempUri); // Update scheme - if ($configApp->forceGlobalSecureRequests) { + if ($scheme !== '') { + $uri->setScheme($scheme); + } elseif ($configApp->forceGlobalSecureRequests) { $uri->setScheme('https'); } diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 413ab66ee57c..bdc6679bacc1 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -70,6 +70,16 @@ public function testConstructorHost() $this->assertSame('/index.php/', $uri->getPath()); } + public function testConstructorScheme() + { + $config = new App(); + + $uri = new SiteURI($config, '', '', 'https'); + + $this->assertInstanceOf(SiteURI::class, $uri); + $this->assertSame('https://example.com/index.php/', (string) $uri); + } + public function testConstructorSubfolder() { $config = new App(); From b231d1c2838fb37f1ba4bc00cecdaf3877585f0c Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 09:11:23 +0900 Subject: [PATCH 13/31] style: break long line --- system/HTTP/SiteURI.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index ea2bab91078f..6b07bca62aa1 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -78,8 +78,12 @@ class SiteURI extends URI * @param string $scheme Optional scheme. 'http' or 'https'. * @phpstan-param 'http'|'https'|'' $scheme */ - public function __construct(App $configApp, string $relativePath = '', string $host = '', string $scheme = '') - { + public function __construct( + App $configApp, + string $relativePath = '', + string $host = '', + string $scheme = '' + ) { $this->baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; From 4932572a5dcd8d968c10c2412799db232f9d512d Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 10:01:03 +0900 Subject: [PATCH 14/31] fix: remove unneeded methods getSegment() accepts n+1. --- system/HTTP/SiteURI.php | 64 +++++-------------------------- tests/system/HTTP/SiteURITest.php | 2 +- 2 files changed, 10 insertions(+), 56 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 6b07bca62aa1..6a2d7f594af0 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -192,21 +192,17 @@ public function getRoutePath(): string } /** - * Returns the URI segments of the path as an array. - */ - public function getSegments(): array - { - return $this->segments; - } - - /** - * Returns the value of a specific segment of the URI path relative to baseURL. + * Returns the value of a specific segment of the URI path. + * Allows to get only existing segments or the next one. * - * @param int $number Segment number + * @param int $number Segment number starting at 1 * @param string $default Default value * - * @return string The value of the segment. If no segment is found, - * throws HTTPException + * @return string The value of the segment. If you specify the last +1 + * segment, the $default value. If you specify the last +2 + * or more throws HTTPException. + * + * @TODO remove this method after merging #7267 */ public function getSegment(int $number, string $default = ''): string { @@ -214,7 +210,7 @@ public function getSegment(int $number, string $default = ''): string throw HTTPException::forURISegmentOutOfRange($number); } - if ($number > count($this->segments) && ! $this->silent) { + if ($number > count($this->segments) + 1 && ! $this->silent) { throw HTTPException::forURISegmentOutOfRange($number); } @@ -225,48 +221,6 @@ public function getSegment(int $number, string $default = ''): string return $this->segments[$number] ?? $default; } - /** - * Set the value of a specific segment of the URI path relative to baseURL. - * Allows to set only existing segments or add new one. - * - * @param int $number The segment number. Starting with 1. - * @param string $value The segment value. - * - * @return $this - */ - public function setSegment(int $number, $value) - { - if ($number < 1) { - throw HTTPException::forURISegmentOutOfRange($number); - } - - if ($number > count($this->segments) + 1) { - if ($this->silent) { - return $this; - } - - throw HTTPException::forURISegmentOutOfRange($number); - } - - // The segment should treat the array as 1-based for the user, - // but we still have to deal with a zero-based array. - $number--; - - $this->segments[$number] = $value; - - $this->refreshPath(); - - return $this; - } - - /** - * Returns the total number of segments. - */ - public function getTotalSegments(): int - { - return count($this->segments); - } - /** * Formats the URI as a string. */ diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index bdc6679bacc1..e670e023b9f4 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -285,7 +285,7 @@ public function testGetSegmentOutOfRange() $uri = new SiteURI($config); $uri->setPath('test/method'); - $uri->getSegment(3); + $uri->getSegment(4); } public function testGetTotalSegments() From c3628d14c63a4507927b19fab5327353ddf11f2a Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 15:24:36 +0900 Subject: [PATCH 15/31] fix: change default values to null For consistency with previous implementations. --- system/HTTP/SiteURI.php | 20 ++++++++++---------- tests/system/HTTP/SiteURITest.php | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 6a2d7f594af0..110d189e9d12 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -71,18 +71,18 @@ class SiteURI extends URI private string $routePath; /** - * @param string $relativePath URI path relative to baseURL. May include - * queries or fragments. - * @param string $host Optional hostname. If it is not in $allowedHostnames, - * just be ignored. - * @param string $scheme Optional scheme. 'http' or 'https'. - * @phpstan-param 'http'|'https'|'' $scheme + * @param string $relativePath URI path relative to baseURL. May include + * queries or fragments. + * @param string|null $host Optional hostname. If it is not in + * $allowedHostnames, just be ignored. + * @param string|null $scheme Optional scheme. 'http' or 'https'. + * @phpstan-param 'http'|'https'|null $scheme */ public function __construct( App $configApp, string $relativePath = '', - string $host = '', - string $scheme = '' + ?string $host = null, + ?string $scheme = null ) { $this->baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; @@ -106,14 +106,14 @@ public function __construct( $uri = new URI($tempUri); // Update scheme - if ($scheme !== '') { + if ($scheme !== null) { $uri->setScheme($scheme); } elseif ($configApp->forceGlobalSecureRequests) { $uri->setScheme('https'); } // Update host - if ($host !== '' && $this->checkHost($host, $configApp->allowedHostnames)) { + if ($host !== null && $this->checkHost($host, $configApp->allowedHostnames)) { $uri->setHost($host); } diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index e670e023b9f4..11153fb9b7fd 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -74,7 +74,7 @@ public function testConstructorScheme() { $config = new App(); - $uri = new SiteURI($config, '', '', 'https'); + $uri = new SiteURI($config, '', null, 'https'); $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('https://example.com/index.php/', (string) $uri); From 57afaf9920465562e17e0a06b795bfde11dd364f Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 17 Feb 2023 18:23:36 +0900 Subject: [PATCH 16/31] fix: handling fragment --- system/HTTP/SiteURI.php | 1 + tests/system/HTTP/SiteURITest.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 110d189e9d12..958c503a5cdd 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -125,6 +125,7 @@ public function __construct( // Set routePath $parts = explode('?', $relativePath); + $parts = explode('#', $parts[0]); $routePath = $parts[0]; $this->setRoutePath($routePath); } diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 11153fb9b7fd..e8c2f4365b9c 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -58,6 +58,30 @@ public function testConstructorRelativePathWithQuery() $this->assertSame('foo=1&bar=2', $uri->getQuery()); } + public function testConstructorRelativePathWithFragment() + { + $config = new App(); + + $uri = new SiteURI($config, 'one/two#sec1'); + + $this->assertSame('http://example.com/index.php/one/two#sec1', (string) $uri); + $this->assertSame('/index.php/one/two', $uri->getPath()); + $this->assertSame('', $uri->getQuery()); + $this->assertSame('sec1', $uri->getFragment()); + } + + public function testConstructorRelativePathWithQueryAndFragment() + { + $config = new App(); + + $uri = new SiteURI($config, 'one/two?foo=1&bar=2#sec1'); + + $this->assertSame('http://example.com/index.php/one/two?foo=1&bar=2#sec1', (string) $uri); + $this->assertSame('/index.php/one/two', $uri->getPath()); + $this->assertSame('foo=1&bar=2', $uri->getQuery()); + $this->assertSame('sec1', $uri->getFragment()); + } + public function testConstructorHost() { $config = new App(); From 246eea249a526d562f30f45778eaff9e8575fc40 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 18 Feb 2023 13:13:34 +0900 Subject: [PATCH 17/31] fix: host in baseURL may be wrong --- system/HTTP/SiteURI.php | 32 +++++++++++++++++++++++-------- tests/system/HTTP/SiteURITest.php | 6 ++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 958c503a5cdd..84488a307bca 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -22,10 +22,18 @@ class SiteURI extends URI { /** - * The baseURL. + * The current baseURL. */ private string $baseURL; + /** + * The path part of baseURL. + * + * The baseURL "http://example.com/" → '/' + * The baseURL "http://localhost:8888/ci431/public/" → '/ci431/public/' + */ + private string $basePathWithoutIndexPage; + /** * The Index File. */ @@ -84,10 +92,10 @@ public function __construct( ?string $host = null, ?string $scheme = null ) { - $this->baseURL = $this->normalizeBaseURL($configApp); + $baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; - $this->setBaseSegments(); + $this->setBasePath($baseURL); // Check for an index page $indexPage = ''; @@ -102,7 +110,7 @@ public function __construct( $relativePath = URI::removeDotSegments($relativePath); - $tempUri = $this->baseURL . $indexPage . $relativePath; + $tempUri = $baseURL . $indexPage . $relativePath; $uri = new URI($tempUri); // Update scheme @@ -128,6 +136,13 @@ public function __construct( $parts = explode('#', $parts[0]); $routePath = $parts[0]; $this->setRoutePath($routePath); + + // Set baseURL + $this->baseURL = URI::createURIString( + $this->getScheme(), + $this->getAuthority(), + $this->basePathWithoutIndexPage, + ); } private function checkHost(string $host, array $allowedHostnames): bool @@ -152,12 +167,13 @@ private function normalizeBaseURL(App $configApp): string } /** - * Sets baseSegments. + * Sets basePathWithoutIndexPage and baseSegments. */ - private function setBaseSegments(): void + private function setBasePath(string $baseURL): void { - $basePath = (new URI($this->baseURL))->getPath(); - $this->baseSegments = $this->convertToSegments($basePath); + $this->basePathWithoutIndexPage = (new URI($baseURL))->getPath(); + + $this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage); if ($this->indexPage) { $this->baseSegments[] = $this->indexPage; diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index e8c2f4365b9c..9a8caa3a7d95 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -35,6 +35,7 @@ public function testConstructor() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://example.com/index.php/', (string) $uri); $this->assertSame('/index.php/', $uri->getPath()); + $this->assertSame('http://example.com/', $uri->getBaseURL()); } public function testConstructorRelativePath() @@ -92,6 +93,7 @@ public function testConstructorHost() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://sub.example.com/index.php/', (string) $uri); $this->assertSame('/index.php/', $uri->getPath()); + $this->assertSame('http://sub.example.com/', $uri->getBaseURL()); } public function testConstructorScheme() @@ -102,6 +104,7 @@ public function testConstructorScheme() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('https://example.com/index.php/', (string) $uri); + $this->assertSame('https://example.com/', $uri->getBaseURL()); } public function testConstructorSubfolder() @@ -114,6 +117,7 @@ public function testConstructorSubfolder() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://example.com/ci4/index.php/', (string) $uri); $this->assertSame('/ci4/index.php/', $uri->getPath()); + $this->assertSame('http://example.com/ci4/', $uri->getBaseURL()); } public function testConstructorSubfolderRelativePathWithQuery() @@ -136,6 +140,7 @@ public function testConstructorForceGlobalSecureRequests() $uri = new SiteURI($config); $this->assertSame('https://example.com/index.php/', (string) $uri); + $this->assertSame('https://example.com/', $uri->getBaseURL()); } public function testConstructorIndexPageEmpty() @@ -146,6 +151,7 @@ public function testConstructorIndexPageEmpty() $uri = new SiteURI($config); $this->assertSame('http://example.com/', (string) $uri); + $this->assertSame('http://example.com/', $uri->getBaseURL()); } public function testConstructorInvalidBaseURL() From 08bbc18006b22911c1b6d5c19de851d660ef3295 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 18 Feb 2023 13:14:54 +0900 Subject: [PATCH 18/31] fix: disable setBaseURL() method --- system/HTTP/SiteURI.php | 8 ++++++++ tests/system/HTTP/SiteURITest.php | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 84488a307bca..9f5cbb56ba5a 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -180,6 +180,14 @@ private function setBasePath(string $baseURL): void } } + /** + * @deprecated + */ + public function setBaseURL(string $baseURL): void + { + throw new BadMethodCallException('Cannot use this method.'); + } + /** * @deprecated */ diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 9a8caa3a7d95..578074ef5d6f 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -336,6 +336,16 @@ public function testSetURI() $uri->setURI('http://another.site.example.jp/'); } + public function testSetBaseURI() + { + $this->expectException(BadMethodCallException::class); + + $config = new App(); + $uri = new SiteURI($config); + + $uri->setBaseURL('http://another.site.example.jp/'); + } + public function testGetBaseURL() { $config = new App(); From ab6da6186727d74fdcaa261a178c21d3a170a379 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 20 Feb 2023 16:04:14 +0900 Subject: [PATCH 19/31] test: add assertions --- tests/system/HTTP/SiteURITest.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 578074ef5d6f..807ac03f6ced 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -34,6 +34,7 @@ public function testConstructor() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://example.com/index.php/', (string) $uri); + $this->assertSame('/', $uri->getRoutePath()); $this->assertSame('/index.php/', $uri->getPath()); $this->assertSame('http://example.com/', $uri->getBaseURL()); } @@ -45,6 +46,7 @@ public function testConstructorRelativePath() $uri = new SiteURI($config, 'one/two'); $this->assertSame('http://example.com/index.php/one/two', (string) $uri); + $this->assertSame('one/two', $uri->getRoutePath()); $this->assertSame('/index.php/one/two', $uri->getPath()); } @@ -55,6 +57,7 @@ public function testConstructorRelativePathWithQuery() $uri = new SiteURI($config, 'one/two?foo=1&bar=2'); $this->assertSame('http://example.com/index.php/one/two?foo=1&bar=2', (string) $uri); + $this->assertSame('one/two', $uri->getRoutePath()); $this->assertSame('/index.php/one/two', $uri->getPath()); $this->assertSame('foo=1&bar=2', $uri->getQuery()); } @@ -66,6 +69,7 @@ public function testConstructorRelativePathWithFragment() $uri = new SiteURI($config, 'one/two#sec1'); $this->assertSame('http://example.com/index.php/one/two#sec1', (string) $uri); + $this->assertSame('one/two', $uri->getRoutePath()); $this->assertSame('/index.php/one/two', $uri->getPath()); $this->assertSame('', $uri->getQuery()); $this->assertSame('sec1', $uri->getFragment()); @@ -78,6 +82,7 @@ public function testConstructorRelativePathWithQueryAndFragment() $uri = new SiteURI($config, 'one/two?foo=1&bar=2#sec1'); $this->assertSame('http://example.com/index.php/one/two?foo=1&bar=2#sec1', (string) $uri); + $this->assertSame('one/two', $uri->getRoutePath()); $this->assertSame('/index.php/one/two', $uri->getPath()); $this->assertSame('foo=1&bar=2', $uri->getQuery()); $this->assertSame('sec1', $uri->getFragment()); @@ -92,6 +97,7 @@ public function testConstructorHost() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://sub.example.com/index.php/', (string) $uri); + $this->assertSame('/', $uri->getRoutePath()); $this->assertSame('/index.php/', $uri->getPath()); $this->assertSame('http://sub.example.com/', $uri->getBaseURL()); } @@ -116,6 +122,7 @@ public function testConstructorSubfolder() $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://example.com/ci4/index.php/', (string) $uri); + $this->assertSame('/', $uri->getRoutePath()); $this->assertSame('/ci4/index.php/', $uri->getPath()); $this->assertSame('http://example.com/ci4/', $uri->getBaseURL()); } @@ -128,6 +135,7 @@ public function testConstructorSubfolderRelativePathWithQuery() $uri = new SiteURI($config, 'one/two?foo=1&bar=2'); $this->assertSame('http://example.com/ci4/index.php/one/two?foo=1&bar=2', (string) $uri); + $this->assertSame('one/two', $uri->getRoutePath()); $this->assertSame('/ci4/index.php/one/two', $uri->getPath()); $this->assertSame('foo=1&bar=2', $uri->getQuery()); } @@ -152,6 +160,8 @@ public function testConstructorIndexPageEmpty() $this->assertSame('http://example.com/', (string) $uri); $this->assertSame('http://example.com/', $uri->getBaseURL()); + $this->assertSame('/', $uri->getRoutePath()); + $this->assertSame('/', $uri->getPath()); } public function testConstructorInvalidBaseURL() From 82a567d09c3ba12f9bcddb8e1743aa100aec8c01 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 20 Feb 2023 16:44:14 +0900 Subject: [PATCH 20/31] refactor: SiteURI::__construct() --- system/HTTP/SiteURI.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 9f5cbb56ba5a..38e28763743e 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -92,24 +92,23 @@ public function __construct( ?string $host = null, ?string $scheme = null ) { - $baseURL = $this->normalizeBaseURL($configApp); $this->indexPage = $configApp->indexPage; + $baseURL = $this->normalizeBaseURL($configApp); $this->setBasePath($baseURL); + $relativePath = URI::removeDotSegments($relativePath); + // Remove starting slash + if ($relativePath !== '' && $relativePath[0] === '/') { + $relativePath = ltrim($relativePath, '/'); + } + // Check for an index page $indexPage = ''; if ($configApp->indexPage !== '') { - $indexPage = $configApp->indexPage; - - // Check if we need a separator - if ($relativePath !== '' && $relativePath[0] !== '/' && $relativePath[0] !== '?') { - $indexPage .= '/'; - } + $indexPage = $configApp->indexPage . '/'; } - $relativePath = URI::removeDotSegments($relativePath); - $tempUri = $baseURL . $indexPage . $relativePath; $uri = new URI($tempUri); @@ -254,7 +253,7 @@ public function __toString(): string return static::createURIString( $this->getScheme(), $this->getAuthority(), - $this->getPath(), // Absolute URIs should use a "/" for an empty path + $this->getPath(), $this->getQuery(), $this->getFragment() ); From ccfd79db821dcf721d817ea92ce2189b43e768fe Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 20 Feb 2023 16:46:29 +0900 Subject: [PATCH 21/31] test: add test cases for path starting with slash --- tests/system/HTTP/SiteURITest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 807ac03f6ced..e00c65b0c89e 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -50,6 +50,17 @@ public function testConstructorRelativePath() $this->assertSame('/index.php/one/two', $uri->getPath()); } + public function testConstructorRelativePathStartWithSlash() + { + $config = new App(); + + $uri = new SiteURI($config, '/one/two'); + + $this->assertSame('http://example.com/index.php/one/two', (string) $uri); + $this->assertSame('one/two', $uri->getRoutePath()); + $this->assertSame('/index.php/one/two', $uri->getPath()); + } + public function testConstructorRelativePathWithQuery() { $config = new App(); @@ -190,6 +201,22 @@ public function testSetPath() $this->assertSame(2, $uri->getTotalSegments()); } + public function testSetPathStartWithSlash() + { + $config = new App(); + + $uri = new SiteURI($config); + + $uri->setPath('/test/method'); + + $this->assertSame('http://example.com/index.php/test/method', (string) $uri); + $this->assertSame('test/method', $uri->getRoutePath()); + $this->assertSame('/index.php/test/method', $uri->getPath()); + $this->assertSame(['test', 'method'], $uri->getSegments()); + $this->assertSame('test', $uri->getSegment(1)); + $this->assertSame(2, $uri->getTotalSegments()); + } + public function testSetPathSubfolder() { $config = new App(); From e733417b491a06057fec4b535e438ba5828485d7 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 21 Feb 2023 13:38:53 +0900 Subject: [PATCH 22/31] fix: change behavior The $routePath never starts with `/`. If the path is `/`, the URI retain the trailing `/`. --- system/HTTP/SiteURI.php | 31 +++++++++------ tests/system/HTTP/SiteURITest.php | 65 +++++++++++++++++++++++++------ 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 38e28763743e..e444dbb7b66a 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -75,6 +75,8 @@ class SiteURI extends URI * * If the baseURL contains sub folders, this value will be different from * the current URI path. + * + * This value never starts with '/'. */ private string $routePath; @@ -98,18 +100,27 @@ public function __construct( $this->setBasePath($baseURL); $relativePath = URI::removeDotSegments($relativePath); - // Remove starting slash - if ($relativePath !== '' && $relativePath[0] === '/') { + // Remove starting slash unless it is `/`. + if ($relativePath !== '' && $relativePath[0] === '/' && $relativePath !== '/') { $relativePath = ltrim($relativePath, '/'); } + $tempPath = $relativePath; + // Check for an index page $indexPage = ''; if ($configApp->indexPage !== '') { - $indexPage = $configApp->indexPage . '/'; + $indexPage = $configApp->indexPage; + + // Check if we need a separator + if ($relativePath !== '' && $relativePath[0] !== '/' && $relativePath[0] !== '?') { + $indexPage .= '/'; + } + } elseif ($relativePath === '/') { + $tempPath = ''; } - $tempUri = $baseURL . $indexPage . $relativePath; + $tempUri = $baseURL . $indexPage . $tempPath; $uri = new URI($tempUri); // Update scheme @@ -305,16 +316,12 @@ public function refreshPath() $allSegments = array_merge($this->baseSegments, $this->segments); $this->path = '/' . $this->filterPath(implode('/', $allSegments)); - $this->routePath = $this->filterPath(implode('/', $this->segments)); - - if ($this->routePath === '') { - $this->routePath = '/'; - - if ($this->indexPage !== '') { - $this->path .= '/'; - } + if ($this->routePath === '/' && $this->path !== '/') { + $this->path .= '/'; } + $this->routePath = $this->filterPath(implode('/', $this->segments)); + return $this; } diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index e00c65b0c89e..0ca227616a2d 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -32,9 +32,22 @@ public function testConstructor() $uri = new SiteURI($config); + $this->assertInstanceOf(SiteURI::class, $uri); + $this->assertSame('http://example.com/index.php', (string) $uri); + $this->assertSame('', $uri->getRoutePath()); + $this->assertSame('/index.php', $uri->getPath()); + $this->assertSame('http://example.com/', $uri->getBaseURL()); + } + + public function testConstructorPathSlash() + { + $config = new App(); + + $uri = new SiteURI($config, '/'); + $this->assertInstanceOf(SiteURI::class, $uri); $this->assertSame('http://example.com/index.php/', (string) $uri); - $this->assertSame('/', $uri->getRoutePath()); + $this->assertSame('', $uri->getRoutePath()); $this->assertSame('/index.php/', $uri->getPath()); $this->assertSame('http://example.com/', $uri->getBaseURL()); } @@ -107,9 +120,9 @@ public function testConstructorHost() $uri = new SiteURI($config, '', 'sub.example.com'); $this->assertInstanceOf(SiteURI::class, $uri); - $this->assertSame('http://sub.example.com/index.php/', (string) $uri); - $this->assertSame('/', $uri->getRoutePath()); - $this->assertSame('/index.php/', $uri->getPath()); + $this->assertSame('http://sub.example.com/index.php', (string) $uri); + $this->assertSame('', $uri->getRoutePath()); + $this->assertSame('/index.php', $uri->getPath()); $this->assertSame('http://sub.example.com/', $uri->getBaseURL()); } @@ -120,7 +133,7 @@ public function testConstructorScheme() $uri = new SiteURI($config, '', null, 'https'); $this->assertInstanceOf(SiteURI::class, $uri); - $this->assertSame('https://example.com/index.php/', (string) $uri); + $this->assertSame('https://example.com/index.php', (string) $uri); $this->assertSame('https://example.com/', $uri->getBaseURL()); } @@ -132,9 +145,9 @@ public function testConstructorSubfolder() $uri = new SiteURI($config); $this->assertInstanceOf(SiteURI::class, $uri); - $this->assertSame('http://example.com/ci4/index.php/', (string) $uri); - $this->assertSame('/', $uri->getRoutePath()); - $this->assertSame('/ci4/index.php/', $uri->getPath()); + $this->assertSame('http://example.com/ci4/index.php', (string) $uri); + $this->assertSame('', $uri->getRoutePath()); + $this->assertSame('/ci4/index.php', $uri->getPath()); $this->assertSame('http://example.com/ci4/', $uri->getBaseURL()); } @@ -158,7 +171,7 @@ public function testConstructorForceGlobalSecureRequests() $uri = new SiteURI($config); - $this->assertSame('https://example.com/index.php/', (string) $uri); + $this->assertSame('https://example.com/index.php', (string) $uri); $this->assertSame('https://example.com/', $uri->getBaseURL()); } @@ -171,7 +184,20 @@ public function testConstructorIndexPageEmpty() $this->assertSame('http://example.com/', (string) $uri); $this->assertSame('http://example.com/', $uri->getBaseURL()); - $this->assertSame('/', $uri->getRoutePath()); + $this->assertSame('', $uri->getRoutePath()); + $this->assertSame('/', $uri->getPath()); + } + + public function testConstructorIndexPageEmptyWithPathSlash() + { + $config = new App(); + $config->indexPage = ''; + + $uri = new SiteURI($config, '/'); + + $this->assertSame('http://example.com/', (string) $uri); + $this->assertSame('http://example.com/', $uri->getBaseURL()); + $this->assertSame('', $uri->getRoutePath()); $this->assertSame('/', $uri->getPath()); } @@ -242,8 +268,23 @@ public function testSetPathEmpty() $uri->setPath(''); + $this->assertSame('http://example.com/index.php', (string) $uri); + $this->assertSame('', $uri->getRoutePath()); + $this->assertSame('/index.php', $uri->getPath()); + $this->assertSame([], $uri->getSegments()); + $this->assertSame(0, $uri->getTotalSegments()); + } + + public function testSetPathSlash() + { + $config = new App(); + + $uri = new SiteURI($config); + + $uri->setPath('/'); + $this->assertSame('http://example.com/index.php/', (string) $uri); - $this->assertSame('/', $uri->getRoutePath()); + $this->assertSame('', $uri->getRoutePath()); $this->assertSame('/index.php/', $uri->getPath()); $this->assertSame([], $uri->getSegments()); $this->assertSame(0, $uri->getTotalSegments()); @@ -322,7 +363,7 @@ public function testGetRoutePath() $config = new App(); $uri = new SiteURI($config); - $this->assertSame('/', $uri->getRoutePath()); + $this->assertSame('', $uri->getRoutePath()); } public function testGetSegments() From 33fc02864f998ab5471bc891bb4756861727fb79 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 21 Feb 2023 14:50:31 +0900 Subject: [PATCH 23/31] test: use dataProviders --- tests/system/HTTP/SiteURITest.php | 424 +++++++++++++++--------------- 1 file changed, 219 insertions(+), 205 deletions(-) diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 0ca227616a2d..f7595063bd5e 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -26,90 +26,202 @@ */ final class SiteURITest extends CIUnitTestCase { - public function testConstructor() - { - $config = new App(); - - $uri = new SiteURI($config); - - $this->assertInstanceOf(SiteURI::class, $uri); - $this->assertSame('http://example.com/index.php', (string) $uri); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/index.php', $uri->getPath()); - $this->assertSame('http://example.com/', $uri->getBaseURL()); - } - - public function testConstructorPathSlash() - { - $config = new App(); + /** + * @dataProvider provideConstructor + */ + public function testConstructor( + string $baseURL, + string $indexPage, + string $relativePath, + string $expectedURI, + string $expectedRoutePath, + string $expectedPath, + string $expectedQuery, + string $expectedFragment, + array $expectedSegments, + int $expectedTotalSegments + ) { + $config = new App(); + $config->indexPage = $indexPage; + $config->baseURL = $baseURL; - $uri = new SiteURI($config, '/'); + $uri = new SiteURI($config, $relativePath); $this->assertInstanceOf(SiteURI::class, $uri); - $this->assertSame('http://example.com/index.php/', (string) $uri); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/index.php/', $uri->getPath()); - $this->assertSame('http://example.com/', $uri->getBaseURL()); - } - - public function testConstructorRelativePath() - { - $config = new App(); - - $uri = new SiteURI($config, 'one/two'); - - $this->assertSame('http://example.com/index.php/one/two', (string) $uri); - $this->assertSame('one/two', $uri->getRoutePath()); - $this->assertSame('/index.php/one/two', $uri->getPath()); - } - - public function testConstructorRelativePathStartWithSlash() - { - $config = new App(); - $uri = new SiteURI($config, '/one/two'); - - $this->assertSame('http://example.com/index.php/one/two', (string) $uri); - $this->assertSame('one/two', $uri->getRoutePath()); - $this->assertSame('/index.php/one/two', $uri->getPath()); - } - - public function testConstructorRelativePathWithQuery() - { - $config = new App(); - - $uri = new SiteURI($config, 'one/two?foo=1&bar=2'); - - $this->assertSame('http://example.com/index.php/one/two?foo=1&bar=2', (string) $uri); - $this->assertSame('one/two', $uri->getRoutePath()); - $this->assertSame('/index.php/one/two', $uri->getPath()); - $this->assertSame('foo=1&bar=2', $uri->getQuery()); - } - - public function testConstructorRelativePathWithFragment() - { - $config = new App(); - - $uri = new SiteURI($config, 'one/two#sec1'); - - $this->assertSame('http://example.com/index.php/one/two#sec1', (string) $uri); - $this->assertSame('one/two', $uri->getRoutePath()); - $this->assertSame('/index.php/one/two', $uri->getPath()); - $this->assertSame('', $uri->getQuery()); - $this->assertSame('sec1', $uri->getFragment()); - } - - public function testConstructorRelativePathWithQueryAndFragment() - { - $config = new App(); - - $uri = new SiteURI($config, 'one/two?foo=1&bar=2#sec1'); - - $this->assertSame('http://example.com/index.php/one/two?foo=1&bar=2#sec1', (string) $uri); - $this->assertSame('one/two', $uri->getRoutePath()); - $this->assertSame('/index.php/one/two', $uri->getPath()); - $this->assertSame('foo=1&bar=2', $uri->getQuery()); - $this->assertSame('sec1', $uri->getFragment()); + $this->assertSame($expectedURI, (string) $uri); + $this->assertSame($expectedRoutePath, $uri->getRoutePath()); + $this->assertSame($expectedPath, $uri->getPath()); + $this->assertSame($expectedQuery, $uri->getQuery()); + $this->assertSame($expectedFragment, $uri->getFragment()); + $this->assertSame($baseURL, $uri->getBaseURL()); + + $this->assertSame($expectedSegments, $uri->getSegments()); + $this->assertSame($expectedTotalSegments, $uri->getTotalSegments()); + } + + public function provideConstructor() + { + return array_merge($this->provideURIs(), $this->provideRelativePathWithQueryOrFragment()); + } + + public function provideURIs() + { + return [ + // $baseURL, $indexPage, $relativePath, $expectedURI, $expectedRoutePath, + // $expectedPath, $expectedQuery, $expectedFragment + '' => [ + 'http://example.com/', + 'index.php', + '', + 'http://example.com/index.php', + '', + '/index.php', + '', + '', + [], + 0, + ], + '/' => [ + 'http://example.com/', + 'index.php', + '/', + 'http://example.com/index.php/', + '', + '/index.php/', + '', + '', + [], + 0, + ], + 'one/two' => [ + 'http://example.com/', + 'index.php', + 'one/two', + 'http://example.com/index.php/one/two', + 'one/two', + '/index.php/one/two', '', + '', + ['one', 'two'], + 2, + ], + '/one/two' => [ + 'http://example.com/', + 'index.php', + '/one/two', + 'http://example.com/index.php/one/two', + 'one/two', + '/index.php/one/two', + '', + '', + ['one', 'two'], + 2, + ], + 'Subfolder: ' => [ + 'http://example.com/ci4/', + 'index.php', + '', + 'http://example.com/ci4/index.php', + '', + '/ci4/index.php', + '', + '', + [], + 0, + ], + 'Subfolder: one/two' => [ + 'http://example.com/ci4/', + 'index.php', + 'one/two', + 'http://example.com/ci4/index.php/one/two', + 'one/two', + '/ci4/index.php/one/two', + '', + '', + ['one', 'two'], + 2, + ], + 'EmptyIndexPage: ' => [ + 'http://example.com/', + '', + '', + 'http://example.com/', + '', + '/', + '', + '', + [], + 0, + ], + 'EmptyIndexPage: /' => [ + 'http://example.com/', + '', + '/', + 'http://example.com/', + '', + '/', + '', + '', + [], + 0, + ], + ]; + } + + public function provideRelativePathWithQueryOrFragment() + { + return [ + // $baseURL, $indexPage, $relativePath, $expectedURI, $expectedRoutePath, + // $expectedPath, $expectedQuery, $expectedFragment + 'one/two?foo=1&bar=2' => [ + 'http://example.com/', + 'index.php', + 'one/two?foo=1&bar=2', + 'http://example.com/index.php/one/two?foo=1&bar=2', + 'one/two', + '/index.php/one/two', + 'foo=1&bar=2', + '', + ['one', 'two'], + 2, + ], + 'one/two#sec1' => [ + 'http://example.com/', + 'index.php', + 'one/two#sec1', + 'http://example.com/index.php/one/two#sec1', + 'one/two', + '/index.php/one/two', + '', + 'sec1', + ['one', 'two'], + 2, + ], + 'one/two?foo=1&bar=2#sec1' => [ + 'http://example.com/', + 'index.php', + 'one/two?foo=1&bar=2#sec1', + 'http://example.com/index.php/one/two?foo=1&bar=2#sec1', + 'one/two', + '/index.php/one/two', + 'foo=1&bar=2', + 'sec1', + ['one', 'two'], + 2, + ], + 'Subfolder: one/two?foo=1&bar=2' => [ + 'http://example.com/ci4/', + 'index.php', + 'one/two?foo=1&bar=2', + 'http://example.com/ci4/index.php/one/two?foo=1&bar=2', + 'one/two', + '/ci4/index.php/one/two', + 'foo=1&bar=2', + '', + ['one', 'two'], + 2, + ], + ]; } public function testConstructorHost() @@ -137,33 +249,6 @@ public function testConstructorScheme() $this->assertSame('https://example.com/', $uri->getBaseURL()); } - public function testConstructorSubfolder() - { - $config = new App(); - $config->baseURL = 'http://example.com/ci4/'; - - $uri = new SiteURI($config); - - $this->assertInstanceOf(SiteURI::class, $uri); - $this->assertSame('http://example.com/ci4/index.php', (string) $uri); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/ci4/index.php', $uri->getPath()); - $this->assertSame('http://example.com/ci4/', $uri->getBaseURL()); - } - - public function testConstructorSubfolderRelativePathWithQuery() - { - $config = new App(); - $config->baseURL = 'http://example.com/ci4/'; - - $uri = new SiteURI($config, 'one/two?foo=1&bar=2'); - - $this->assertSame('http://example.com/ci4/index.php/one/two?foo=1&bar=2', (string) $uri); - $this->assertSame('one/two', $uri->getRoutePath()); - $this->assertSame('/ci4/index.php/one/two', $uri->getPath()); - $this->assertSame('foo=1&bar=2', $uri->getQuery()); - } - public function testConstructorForceGlobalSecureRequests() { $config = new App(); @@ -175,32 +260,6 @@ public function testConstructorForceGlobalSecureRequests() $this->assertSame('https://example.com/', $uri->getBaseURL()); } - public function testConstructorIndexPageEmpty() - { - $config = new App(); - $config->indexPage = ''; - - $uri = new SiteURI($config); - - $this->assertSame('http://example.com/', (string) $uri); - $this->assertSame('http://example.com/', $uri->getBaseURL()); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/', $uri->getPath()); - } - - public function testConstructorIndexPageEmptyWithPathSlash() - { - $config = new App(); - $config->indexPage = ''; - - $uri = new SiteURI($config, '/'); - - $this->assertSame('http://example.com/', (string) $uri); - $this->assertSame('http://example.com/', $uri->getBaseURL()); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/', $uri->getPath()); - } - public function testConstructorInvalidBaseURL() { $this->expectException(ConfigException::class); @@ -211,83 +270,38 @@ public function testConstructorInvalidBaseURL() new SiteURI($config); } - public function testSetPath() - { - $config = new App(); - - $uri = new SiteURI($config); - - $uri->setPath('test/method'); - - $this->assertSame('http://example.com/index.php/test/method', (string) $uri); - $this->assertSame('test/method', $uri->getRoutePath()); - $this->assertSame('/index.php/test/method', $uri->getPath()); - $this->assertSame(['test', 'method'], $uri->getSegments()); - $this->assertSame('test', $uri->getSegment(1)); - $this->assertSame(2, $uri->getTotalSegments()); - } - - public function testSetPathStartWithSlash() - { - $config = new App(); - - $uri = new SiteURI($config); - - $uri->setPath('/test/method'); - - $this->assertSame('http://example.com/index.php/test/method', (string) $uri); - $this->assertSame('test/method', $uri->getRoutePath()); - $this->assertSame('/index.php/test/method', $uri->getPath()); - $this->assertSame(['test', 'method'], $uri->getSegments()); - $this->assertSame('test', $uri->getSegment(1)); - $this->assertSame(2, $uri->getTotalSegments()); - } - - public function testSetPathSubfolder() - { - $config = new App(); - $config->baseURL = 'http://example.com/ci4/'; - - $uri = new SiteURI($config); - - $uri->setPath('test/method'); - - $this->assertSame('http://example.com/ci4/index.php/test/method', (string) $uri); - $this->assertSame('test/method', $uri->getRoutePath()); - $this->assertSame('/ci4/index.php/test/method', $uri->getPath()); - $this->assertSame(['test', 'method'], $uri->getSegments()); - $this->assertSame('test', $uri->getSegment(1)); - $this->assertSame(2, $uri->getTotalSegments()); - } - - public function testSetPathEmpty() - { - $config = new App(); + /** + * @dataProvider provideURIs + */ + public function testSetPath( + string $baseURL, + string $indexPage, + string $relativePath, + string $expectedURI, + string $expectedRoutePath, + string $expectedPath, + string $expectedQuery, + string $expectedFragment, + array $expectedSegments, + int $expectedTotalSegments + ) { + $config = new App(); + $config->indexPage = $indexPage; + $config->baseURL = $baseURL; $uri = new SiteURI($config); - $uri->setPath(''); + $uri->setPath($relativePath); - $this->assertSame('http://example.com/index.php', (string) $uri); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/index.php', $uri->getPath()); - $this->assertSame([], $uri->getSegments()); - $this->assertSame(0, $uri->getTotalSegments()); - } + $this->assertSame($expectedURI, (string) $uri); + $this->assertSame($expectedRoutePath, $uri->getRoutePath()); + $this->assertSame($expectedPath, $uri->getPath()); + $this->assertSame($expectedQuery, $uri->getQuery()); + $this->assertSame($expectedFragment, $uri->getFragment()); + $this->assertSame($baseURL, $uri->getBaseURL()); - public function testSetPathSlash() - { - $config = new App(); - - $uri = new SiteURI($config); - - $uri->setPath('/'); - - $this->assertSame('http://example.com/index.php/', (string) $uri); - $this->assertSame('', $uri->getRoutePath()); - $this->assertSame('/index.php/', $uri->getPath()); - $this->assertSame([], $uri->getSegments()); - $this->assertSame(0, $uri->getTotalSegments()); + $this->assertSame($expectedSegments, $uri->getSegments()); + $this->assertSame($expectedTotalSegments, $uri->getTotalSegments()); } public function testSetSegment() From ba35e57307be0bff91ac0ffd57416d714deb62cd Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 22 Feb 2023 11:38:34 +0900 Subject: [PATCH 24/31] docs: update comments --- tests/system/HTTP/SiteURITest.php | 44 ++++++++++++++----------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index f7595063bd5e..4473259581a7 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -68,19 +68,17 @@ public function provideConstructor() public function provideURIs() { return [ - // $baseURL, $indexPage, $relativePath, $expectedURI, $expectedRoutePath, - // $expectedPath, $expectedQuery, $expectedFragment '' => [ - 'http://example.com/', - 'index.php', - '', - 'http://example.com/index.php', - '', - '/index.php', - '', - '', - [], - 0, + 'http://example.com/', // $baseURL + 'index.php', // $indexPage + '', // $relativePath + 'http://example.com/index.php', // $expectedURI + '', // $expectedRoutePath + '/index.php', // $expectedPath + '', // $expectedQuery + '', // $expectedFragment + [], // $expectedSegments + 0, // $expectedTotalSegments ], '/' => [ 'http://example.com/', @@ -171,19 +169,17 @@ public function provideURIs() public function provideRelativePathWithQueryOrFragment() { return [ - // $baseURL, $indexPage, $relativePath, $expectedURI, $expectedRoutePath, - // $expectedPath, $expectedQuery, $expectedFragment 'one/two?foo=1&bar=2' => [ - 'http://example.com/', - 'index.php', - 'one/two?foo=1&bar=2', - 'http://example.com/index.php/one/two?foo=1&bar=2', - 'one/two', - '/index.php/one/two', - 'foo=1&bar=2', - '', - ['one', 'two'], - 2, + 'http://example.com/', // $baseURL + 'index.php', // $indexPage + 'one/two?foo=1&bar=2', // $relativePath + 'http://example.com/index.php/one/two?foo=1&bar=2', // $expectedURI + 'one/two', // $expectedRoutePath + '/index.php/one/two', // $expectedPath + 'foo=1&bar=2', // $expectedQuery + '', // $expectedFragment + ['one', 'two'], // $expectedSegments + 2, // $expectedTotalSegments ], 'one/two#sec1' => [ 'http://example.com/', From fe754f3591fc0799395eacbbfcad2849c1ba5c8d Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 23 Feb 2023 10:51:18 +0900 Subject: [PATCH 25/31] fix: change behavior If the path ends with `/`, the URI retain the trailing `/`. --- system/HTTP/SiteURI.php | 122 +++++++++++++++++++----------- tests/system/HTTP/SiteURITest.php | 12 +++ 2 files changed, 91 insertions(+), 43 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index e444dbb7b66a..160a31b4adcb 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -24,7 +24,7 @@ class SiteURI extends URI /** * The current baseURL. */ - private string $baseURL; + private URI $baseURL; /** * The path part of baseURL. @@ -96,32 +96,54 @@ public function __construct( ) { $this->indexPage = $configApp->indexPage; - $baseURL = $this->normalizeBaseURL($configApp); - $this->setBasePath($baseURL); + $this->baseURL = $this->determineBaseURL($configApp, $host, $scheme); - $relativePath = URI::removeDotSegments($relativePath); - // Remove starting slash unless it is `/`. - if ($relativePath !== '' && $relativePath[0] === '/' && $relativePath !== '/') { - $relativePath = ltrim($relativePath, '/'); - } + $this->setBasePath(); - $tempPath = $relativePath; + // Fix routePath, query, fragment + [$routePath, $query, $fragment] = $this->parseRelativePath($relativePath); - // Check for an index page - $indexPage = ''; - if ($configApp->indexPage !== '') { - $indexPage = $configApp->indexPage; + // Fix indexPage and routePath + $indexPageRoutePath = $this->getIndexPageRoutePath($routePath); - // Check if we need a separator - if ($relativePath !== '' && $relativePath[0] !== '/' && $relativePath[0] !== '?') { - $indexPage .= '/'; - } - } elseif ($relativePath === '/') { - $tempPath = ''; + // Fix the current URI + $uri = $this->baseURL . $indexPageRoutePath; + + // applyParts + $parts = parse_url($uri); + if ($parts === false) { + throw HTTPException::forUnableToParseURI($uri); + } + $parts['query'] = $query; + $parts['fragment'] = $fragment; + $this->applyParts($parts); + + $this->setRoutePath($routePath); + } + + private function parseRelativePath(string $relativePath): array + { + $parts = parse_url('http://dummy/' . $relativePath); + if ($parts === false) { + throw HTTPException::forUnableToParseURI($relativePath); } - $tempUri = $baseURL . $indexPage . $tempPath; - $uri = new URI($tempUri); + $routePath = $relativePath === '/' ? '/' : ltrim($parts['path'], '/'); + + $query = $parts['query'] ?? ''; + $fragment = $parts['fragment'] ?? ''; + + return [$routePath, $query, $fragment]; + } + + private function determineBaseURL( + App $configApp, + ?string $host, + ?string $scheme + ): URI { + $baseURL = $this->normalizeBaseURL($configApp); + + $uri = new URI($baseURL); // Update scheme if ($scheme !== null) { @@ -135,24 +157,34 @@ public function __construct( $uri->setHost($host); } - $parts = parse_url((string) $uri); - if ($parts === false) { - throw HTTPException::forUnableToParseURI($uri); + return $uri; + } + + private function getIndexPageRoutePath(string $routePath): string + { + // Remove starting slash unless it is `/`. + if ($routePath !== '' && $routePath[0] === '/' && $routePath !== '/') { + $routePath = ltrim($routePath, '/'); } - $this->applyParts($parts); - // Set routePath - $parts = explode('?', $relativePath); - $parts = explode('#', $parts[0]); - $routePath = $parts[0]; - $this->setRoutePath($routePath); + // Check for an index page + $indexPage = ''; + if ($this->indexPage !== '') { + $indexPage = $this->indexPage; - // Set baseURL - $this->baseURL = URI::createURIString( - $this->getScheme(), - $this->getAuthority(), - $this->basePathWithoutIndexPage, - ); + // Check if we need a separator + if ($routePath !== '' && $routePath[0] !== '/' && $routePath[0] !== '?') { + $indexPage .= '/'; + } + } + + $indexPageRoutePath = $indexPage . $routePath; + + if ($indexPageRoutePath === '/') { + $indexPageRoutePath = ''; + } + + return $indexPageRoutePath; } private function checkHost(string $host, array $allowedHostnames): bool @@ -179,9 +211,9 @@ private function normalizeBaseURL(App $configApp): string /** * Sets basePathWithoutIndexPage and baseSegments. */ - private function setBasePath(string $baseURL): void + private function setBasePath(): void { - $this->basePathWithoutIndexPage = (new URI($baseURL))->getPath(); + $this->basePathWithoutIndexPage = $this->baseURL->getPath(); $this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage); @@ -213,7 +245,7 @@ public function setURI(?string $uri = null) */ public function getBaseURL(): string { - return $this->baseURL; + return (string) $this->baseURL; } /** @@ -285,13 +317,17 @@ public function setPath(string $path) /** * Sets the route path (and segments). */ - private function setRoutePath(string $path): void + private function setRoutePath(string $routePath): void { - $this->routePath = $this->filterPath($path); + $routePath = $this->filterPath($routePath); - $this->segments = $this->convertToSegments($this->routePath); + $indexPageRoutePath = $this->getIndexPageRoutePath($routePath); - $this->refreshPath(); + $this->path = $this->basePathWithoutIndexPage . $indexPageRoutePath; + + $this->routePath = ltrim($routePath, '/'); + + $this->segments = $this->convertToSegments($this->routePath); } /** diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index 4473259581a7..c9423b6d1da3 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -115,6 +115,18 @@ public function provideURIs() ['one', 'two'], 2, ], + '/one/two/' => [ + 'http://example.com/', + 'index.php', + '/one/two/', + 'http://example.com/index.php/one/two/', + 'one/two/', + '/index.php/one/two/', + '', + '', + ['one', 'two'], + 2, + ], 'Subfolder: ' => [ 'http://example.com/ci4/', 'index.php', From 3f4a9fd94e9a65ca86eaa2e1e441c86fbf4f7cae Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 23 Feb 2023 11:54:35 +0900 Subject: [PATCH 26/31] fix: remove $allowedHostnames check SiteURI will check. --- system/HTTP/SiteURI.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 160a31b4adcb..9d4ccef074d6 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -83,8 +83,7 @@ class SiteURI extends URI /** * @param string $relativePath URI path relative to baseURL. May include * queries or fragments. - * @param string|null $host Optional hostname. If it is not in - * $allowedHostnames, just be ignored. + * @param string|null $host Optional current hostname. * @param string|null $scheme Optional scheme. 'http' or 'https'. * @phpstan-param 'http'|'https'|null $scheme */ @@ -153,7 +152,7 @@ private function determineBaseURL( } // Update host - if ($host !== null && $this->checkHost($host, $configApp->allowedHostnames)) { + if ($host !== null) { $uri->setHost($host); } @@ -187,11 +186,6 @@ private function getIndexPageRoutePath(string $routePath): string return $indexPageRoutePath; } - private function checkHost(string $host, array $allowedHostnames): bool - { - return in_array($host, $allowedHostnames, true); - } - private function normalizeBaseURL(App $configApp): string { // It's possible the user forgot a trailing slash on their From ecf96eb21dc6a97fcadeb09931e9beebf1ae6f5e Mon Sep 17 00:00:00 2001 From: kenjis Date: Thu, 23 Feb 2023 13:42:59 +0900 Subject: [PATCH 27/31] test: add test cases --- tests/system/HTTP/SiteURITest.php | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php index c9423b6d1da3..f2b665b94a38 100644 --- a/tests/system/HTTP/SiteURITest.php +++ b/tests/system/HTTP/SiteURITest.php @@ -127,6 +127,42 @@ public function provideURIs() ['one', 'two'], 2, ], + '//one/two' => [ + 'http://example.com/', + 'index.php', + '//one/two', + 'http://example.com/index.php/one/two', + 'one/two', + '/index.php/one/two', + '', + '', + ['one', 'two'], + 2, + ], + 'one/two//' => [ + 'http://example.com/', + 'index.php', + 'one/two//', + 'http://example.com/index.php/one/two/', + 'one/two/', + '/index.php/one/two/', + '', + '', + ['one', 'two'], + 2, + ], + '///one///two///' => [ + 'http://example.com/', + 'index.php', + '///one///two///', + 'http://example.com/index.php/one/two/', + 'one/two/', + '/index.php/one/two/', + '', + '', + ['one', 'two'], + 2, + ], 'Subfolder: ' => [ 'http://example.com/ci4/', 'index.php', From 5193414accf0f8411f7d3a5a675c5a22170c76f8 Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 24 Feb 2023 09:09:57 +0900 Subject: [PATCH 28/31] refactor: remove unneeded override The parent method is fixed. --- system/HTTP/SiteURI.php | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index 9d4ccef074d6..fc1071647462 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -252,36 +252,6 @@ public function getRoutePath(): string return $this->routePath; } - /** - * Returns the value of a specific segment of the URI path. - * Allows to get only existing segments or the next one. - * - * @param int $number Segment number starting at 1 - * @param string $default Default value - * - * @return string The value of the segment. If you specify the last +1 - * segment, the $default value. If you specify the last +2 - * or more throws HTTPException. - * - * @TODO remove this method after merging #7267 - */ - public function getSegment(int $number, string $default = ''): string - { - if ($number < 1) { - throw HTTPException::forURISegmentOutOfRange($number); - } - - if ($number > count($this->segments) + 1 && ! $this->silent) { - throw HTTPException::forURISegmentOutOfRange($number); - } - - // The segment should treat the array as 1-based for the user - // but we still have to deal with a zero-based array. - $number--; - - return $this->segments[$number] ?? $default; - } - /** * Formats the URI as a string. */ From b24c01c51287b31f9da18ed2a67628e39e616939 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 10 Jul 2023 14:03:24 +0900 Subject: [PATCH 29/31] docs: fix typo in comments --- system/HTTP/SiteURI.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php index fc1071647462..e64080974940 100644 --- a/system/HTTP/SiteURI.php +++ b/system/HTTP/SiteURI.php @@ -43,7 +43,7 @@ class SiteURI extends URI * List of URI segments in baseURL and indexPage. * * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b", - * and the baseUR is "http://localhost:8888/ci431/public/", then: + * and the baseURL is "http://localhost:8888/ci431/public/", then: * $baseSegments = [ * 0 => 'ci431', * 1 => 'public', @@ -59,7 +59,7 @@ class SiteURI extends URI * to the baseURL. * * If the URI is "http://localhost:8888/ci431/public/index.php/test?a=b", - * and the baseUR is "http://localhost:8888/ci431/public/", then: + * and the baseURL is "http://localhost:8888/ci431/public/", then: * $segments = [ * 0 => 'test', * ]; From 3e349e18774ae6b45f5e9b91e60ba11d5feb72ae Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 10 Jul 2023 14:11:24 +0900 Subject: [PATCH 30/31] docs: make URI::setSilent() deprecated --- system/HTTP/URI.php | 2 +- user_guide_src/source/changelogs/v4.4.0.rst | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 4f12b3464260..0201f0680ce0 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -263,7 +263,7 @@ public function __construct(?string $uri = null) * If $silent == true, then will not throw exceptions and will * attempt to continue gracefully. * - * Note: Method not in PSR-7 + * @deprecated 4.4.0 Method not in PSR-7 * * @return URI */ diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index 87712aff5bb6..085489277c7e 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -216,6 +216,7 @@ Deprecations ``$tokenName``, ``$headerName``, ``$expires``, ``$regenerate``, and ``$redirect`` in ``Security`` are deprecated, and no longer used. Use ``$config`` instead. +- **URI:** ``URI::setSilent()`` is deprecated. Bugs Fixed ********** From e529291339008883b6a8dec16a076a35b7d4079a Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 10 Jul 2023 14:30:47 +0900 Subject: [PATCH 31/31] feat: add URI::withScheme() and deprecate URI::setScheme() --- system/HTTP/URI.php | 31 ++++++++++++++++- tests/system/HTTP/URITest.php | 38 +++++++++++++++++++++ user_guide_src/source/changelogs/v4.4.0.rst | 4 ++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/system/HTTP/URI.php b/system/HTTP/URI.php index 0201f0680ce0..d27598b6bfb1 100644 --- a/system/HTTP/URI.php +++ b/system/HTTP/URI.php @@ -14,6 +14,7 @@ use BadMethodCallException; use CodeIgniter\HTTP\Exceptions\HTTPException; use Config\App; +use InvalidArgumentException; /** * Abstraction for a uniform resource identifier (URI). @@ -709,7 +710,7 @@ public function setAuthority(string $str) * * @return $this * - * @TODO PSR-7: Should be `withScheme($scheme)`. + * @deprecated Use `withScheme()` instead. */ public function setScheme(string $str) { @@ -719,6 +720,34 @@ public function setScheme(string $str) return $this; } + /** + * Return an instance with the specified scheme. + * + * This method MUST retain the state of the current instance, and return + * an instance that contains the specified scheme. + * + * Implementations MUST support the schemes "http" and "https" case + * insensitively, and MAY accommodate other schemes if required. + * + * An empty scheme is equivalent to removing the scheme. + * + * @param string $scheme The scheme to use with the new instance. + * + * @return static A new instance with the specified scheme. + * + * @throws InvalidArgumentException for invalid or unsupported schemes. + */ + public function withScheme(string $scheme) + { + $uri = clone $this; + + $scheme = strtolower($scheme); + + $uri->scheme = preg_replace('#:(//)?$#', '', $scheme); + + return $uri; + } + /** * Sets the userInfo/Authority portion of the URI. * diff --git a/tests/system/HTTP/URITest.php b/tests/system/HTTP/URITest.php index fa5caafa1a49..ec3443fdf278 100644 --- a/tests/system/HTTP/URITest.php +++ b/tests/system/HTTP/URITest.php @@ -240,6 +240,44 @@ public function testSetSchemeSetsValue() $this->assertSame($expected, (string) $uri); } + public function testWithScheme() + { + $url = 'example.com'; + $uri = new URI('http://' . $url); + + $new = $uri->withScheme('x'); + + $this->assertSame('x://' . $url, (string) $new); + $this->assertSame('http://' . $url, (string) $uri); + } + + public function testWithSchemeSetsHttps() + { + $url = 'http://example.com/path'; + $uri = new URI($url); + + $new = $uri->withScheme('https'); + + $this->assertSame('https', $new->getScheme()); + $this->assertSame('http', $uri->getScheme()); + + $expected = 'https://example.com/path'; + $this->assertSame($expected, (string) $new); + $expected = 'http://example.com/path'; + $this->assertSame($expected, (string) $uri); + } + + public function testWithSchemeSetsEmpty() + { + $url = 'example.com'; + $uri = new URI('http://' . $url); + + $new = $uri->withScheme(''); + + $this->assertSame($url, (string) $new); + $this->assertSame('http://' . $url, (string) $uri); + } + public function testSetUserInfoSetsValue() { $url = 'http://example.com/path'; diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index 085489277c7e..475d08e9eaea 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -216,7 +216,9 @@ Deprecations ``$tokenName``, ``$headerName``, ``$expires``, ``$regenerate``, and ``$redirect`` in ``Security`` are deprecated, and no longer used. Use ``$config`` instead. -- **URI:** ``URI::setSilent()`` is deprecated. +- **URI:** + - ``URI::setSilent()`` is deprecated. + - ``URI::setScheme()`` is deprecated. Use ``withScheme()`` instead. Bugs Fixed **********