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/ diff --git a/system/HTTP/SiteURI.php b/system/HTTP/SiteURI.php new file mode 100644 index 000000000000..e64080974940 --- /dev/null +++ b/system/HTTP/SiteURI.php @@ -0,0 +1,366 @@ + + * + * 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\Exceptions\ConfigException; +use CodeIgniter\HTTP\Exceptions\HTTPException; +use Config\App; + +/** + * URI for the application site + */ +class SiteURI extends URI +{ + /** + * The current baseURL. + */ + private URI $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. + */ + 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 baseURL 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 baseURL 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. + * + * This value never starts with '/'. + */ + private string $routePath; + + /** + * @param string $relativePath URI path relative to baseURL. May include + * queries or fragments. + * @param string|null $host Optional current hostname. + * @param string|null $scheme Optional scheme. 'http' or 'https'. + * @phpstan-param 'http'|'https'|null $scheme + */ + public function __construct( + App $configApp, + string $relativePath = '', + ?string $host = null, + ?string $scheme = null + ) { + $this->indexPage = $configApp->indexPage; + + $this->baseURL = $this->determineBaseURL($configApp, $host, $scheme); + + $this->setBasePath(); + + // Fix routePath, query, fragment + [$routePath, $query, $fragment] = $this->parseRelativePath($relativePath); + + // Fix indexPage and routePath + $indexPageRoutePath = $this->getIndexPageRoutePath($routePath); + + // 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); + } + + $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) { + $uri->setScheme($scheme); + } elseif ($configApp->forceGlobalSecureRequests) { + $uri->setScheme('https'); + } + + // Update host + if ($host !== null) { + $uri->setHost($host); + } + + return $uri; + } + + private function getIndexPageRoutePath(string $routePath): string + { + // Remove starting slash unless it is `/`. + if ($routePath !== '' && $routePath[0] === '/' && $routePath !== '/') { + $routePath = ltrim($routePath, '/'); + } + + // Check for an index page + $indexPage = ''; + if ($this->indexPage !== '') { + $indexPage = $this->indexPage; + + // Check if we need a separator + if ($routePath !== '' && $routePath[0] !== '/' && $routePath[0] !== '?') { + $indexPage .= '/'; + } + } + + $indexPageRoutePath = $indexPage . $routePath; + + if ($indexPageRoutePath === '/') { + $indexPageRoutePath = ''; + } + + return $indexPageRoutePath; + } + + 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; + } + + /** + * Sets basePathWithoutIndexPage and baseSegments. + */ + private function setBasePath(): void + { + $this->basePathWithoutIndexPage = $this->baseURL->getPath(); + + $this->baseSegments = $this->convertToSegments($this->basePathWithoutIndexPage); + + if ($this->indexPage) { + $this->baseSegments[] = $this->indexPage; + } + } + + /** + * @deprecated + */ + public function setBaseURL(string $baseURL): void + { + throw new BadMethodCallException('Cannot use this method.'); + } + + /** + * @deprecated + */ + public function setURI(?string $uri = null) + { + throw new BadMethodCallException('Cannot use this method.'); + } + + /** + * Returns the baseURL. + * + * @interal + */ + public function getBaseURL(): string + { + return (string) $this->baseURL; + } + + /** + * Returns the URI path relative to baseURL. + * + * @return string The Route path. + */ + public function getRoutePath(): string + { + return $this->routePath; + } + + /** + * Formats the URI as a string. + */ + public function __toString(): string + { + return static::createURIString( + $this->getScheme(), + $this->getAuthority(), + $this->getPath(), + $this->getQuery(), + $this->getFragment() + ); + } + + /** + * Sets the route path (and segments). + * + * @return $this + */ + public function setPath(string $path) + { + $this->setRoutePath($path); + + return $this; + } + + /** + * Sets the route path (and segments). + */ + private function setRoutePath(string $routePath): void + { + $routePath = $this->filterPath($routePath); + + $indexPageRoutePath = $this->getIndexPageRoutePath($routePath); + + $this->path = $this->basePathWithoutIndexPage . $indexPageRoutePath; + + $this->routePath = ltrim($routePath, '/'); + + $this->segments = $this->convertToSegments($this->routePath); + } + + /** + * 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)); + + if ($this->routePath === '/' && $this->path !== '/') { + $this->path .= '/'; + } + + $this->routePath = $this->filterPath(implode('/', $this->segments)); + + 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..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). @@ -34,11 +35,15 @@ class URI * Current URI string * * @var string + * + * @deprecated Not used. */ protected $uriString; /** * The Current baseURL. + * + * @deprecated Use SiteURI instead. */ private ?string $baseURL = null; @@ -259,6 +264,8 @@ public function __construct(?string $uri = null) * If $silent == true, then will not throw exceptions and will * attempt to continue gracefully. * + * @deprecated 4.4.0 Method not in PSR-7 + * * @return URI */ public function setSilent(bool $silent = true) @@ -272,6 +279,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) @@ -287,6 +296,8 @@ public function useRawQueryString(bool $raw = true) * @return URI * * @throws HTTPException + * + * @deprecated This method will be private. */ public function setURI(?string $uri = null) { @@ -404,6 +415,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) @@ -563,6 +576,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 * @@ -594,6 +609,8 @@ public function setSegment(int $number, $value) /** * Returns the total number of segments. + * + * Note: Method not in PSR-7 */ public function getTotalSegments(): int { @@ -661,6 +678,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) @@ -690,6 +709,8 @@ public function setAuthority(string $str) * @see https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml * * @return $this + * + * @deprecated Use `withScheme()` instead. */ public function setScheme(string $str) { @@ -699,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. * @@ -706,6 +755,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) { @@ -719,6 +770,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) { @@ -733,6 +786,8 @@ public function setHost(string $str) * @param int $port * * @return $this + * + * @TODO PSR-7: Should be `withPort($port)`. */ public function setPort(?int $port = null) { @@ -757,6 +812,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) { @@ -773,6 +830,8 @@ public function setPath(string $path) * Sets the current baseURL. * * @interal + * + * @deprecated Use SiteURI instead. */ public function setBaseURL(string $baseURL): void { @@ -783,6 +842,8 @@ public function setBaseURL(string $baseURL): void * Returns the current baseURL. * * @interal + * + * @deprecated Use SiteURI instead. */ public function getBaseURL(): string { @@ -797,6 +858,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() { @@ -814,6 +877,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) { @@ -844,6 +909,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) { @@ -855,7 +922,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 */ @@ -869,6 +938,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 @@ -886,6 +957,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 @@ -913,6 +986,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) { diff --git a/tests/system/HTTP/SiteURITest.php b/tests/system/HTTP/SiteURITest.php new file mode 100644 index 000000000000..f2b665b94a38 --- /dev/null +++ b/tests/system/HTTP/SiteURITest.php @@ -0,0 +1,492 @@ + + * + * 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\Exceptions\ConfigException; +use CodeIgniter\HTTP\Exceptions\HTTPException; +use CodeIgniter\Test\CIUnitTestCase; +use Config\App; + +/** + * @backupGlobals enabled + * + * @internal + * + * @group Others + */ +final class SiteURITest extends CIUnitTestCase +{ + /** + * @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, $relativePath); + + $this->assertInstanceOf(SiteURI::class, $uri); + + $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 [ + '' => [ + '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/', + '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, + ], + '/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, + ], + '///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 [ + 'one/two?foo=1&bar=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/', + '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() + { + $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('', $uri->getRoutePath()); + $this->assertSame('/index.php', $uri->getPath()); + $this->assertSame('http://sub.example.com/', $uri->getBaseURL()); + } + + public function testConstructorScheme() + { + $config = new App(); + + $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/', $uri->getBaseURL()); + } + + public function testConstructorForceGlobalSecureRequests() + { + $config = new App(); + $config->forceGlobalSecureRequests = true; + + $uri = new SiteURI($config); + + $this->assertSame('https://example.com/index.php', (string) $uri); + $this->assertSame('https://example.com/', $uri->getBaseURL()); + } + + public function testConstructorInvalidBaseURL() + { + $this->expectException(ConfigException::class); + + $config = new App(); + $config->baseURL = 'invalid'; + + new SiteURI($config); + } + + /** + * @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($relativePath); + + $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 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'); + + $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/'); + } + + 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(); + $uri = new SiteURI($config); + + $this->assertSame('http://example.com/', $uri->getBaseURL()); + } +} 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 87712aff5bb6..475d08e9eaea 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -216,6 +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::setScheme()`` is deprecated. Use ``withScheme()`` instead. Bugs Fixed **********