Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"require": {
"php": "^8.1",
"ext-mbstring": "*",
"guzzlehttp/psr7": "^2.0",
"phpunit/phpunit": "^9.5 | ^10.0 | ^11.0 | ^12",
"symfony/css-selector": ">=4.4.24 <8.0"
},
Expand Down
122 changes: 99 additions & 23 deletions src/Util/Uri.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Codeception\Util;

use GuzzleHttp\Psr7\Uri as Psr7Uri;
use InvalidArgumentException;

use function dirname;
Expand All @@ -28,13 +27,13 @@ class Uri
*/
public static function mergeUrls(string $baseUri, string $uri): string
{
$base = new Psr7Uri($baseUri);
$base = self::parseUrl($baseUri);
$parts = parse_url($uri);

//If the relative URL does not parse, attempt to parse the entire URL.
//PHP Known bug ( https://bugs.php.net/bug.php?id=70942 )
if ($parts === false) {
$parts = parse_url($base . $uri);
$parts = parse_url($baseUri . $uri);
}

if ($parts === false) {
Expand All @@ -47,14 +46,14 @@ public static function mergeUrls(string $baseUri, string $uri): string
}

if (isset($parts['host'])) {
$base = $base->withHost($parts['host']);
$base = $base->withPath('');
$base = $base->withQuery('');
$base = $base->withFragment('');
$base['host'] = $parts['host'];
$base['path'] = '';
$base['query'] = '';
$base['fragment'] = '';
}
if (isset($parts['path'])) {
$path = $parts['path'];
$basePath = $base->getPath();
$basePath = $base['path'] ?? '';
if ((!str_starts_with($path, '/')) && !empty($path)) {
if ($basePath !== '') {
// if it ends with a slash, relative paths are below it
Expand All @@ -69,36 +68,38 @@ public static function mergeUrls(string $baseUri, string $uri): string
$path = '/' . ltrim($path, '/');
}
}
$base = $base->withPath($path);
$base = $base->withQuery('');
$base = $base->withFragment('');
$base['path'] = $path;
$base['query'] = '';
$base['fragment'] = '';
}
if (isset($parts['query'])) {
$base = $base->withQuery($parts['query']);
$base = $base->withFragment('');
$base['query'] = $parts['query'];
$base['fragment'] = '';
}
if (isset($parts['fragment'])) {
$base = $base->withFragment($parts['fragment']);
$base['fragment'] = $parts['fragment'];
}

return (string)$base;
return self::phpUrlPartsToString($base);
}

/**
* Retrieve /path?query#fragment part of URL
*/
public static function retrieveUri(string $url): string
{
$uri = new Psr7Uri($url);
return (string)(new Psr7Uri())
->withPath($uri->getPath())
->withQuery($uri->getQuery())
->withFragment($uri->getFragment());
$urlParts = self::parseUrl($url);

return self::phpUrlPartsToString([
'path' => $urlParts['path'],
'query' => $urlParts['query'] ?? '',
'fragment' => $urlParts['fragment'] ?? '',
]);
}

public static function retrieveHost(string $url): string
{
$urlParts = parse_url($url);
$urlParts = self::parseUrl($url);
if (!isset($urlParts['host']) || !isset($urlParts['scheme'])) {
throw new InvalidArgumentException("Wrong URL passes, host and scheme not set");
}
Expand All @@ -111,13 +112,88 @@ public static function retrieveHost(string $url): string

public static function appendPath(string $url, string $path): string
{
$uri = new Psr7Uri($url);
$cutUrl = (string)$uri->withQuery('')->withFragment('');
$cutUrl = parse_url($url);
unset(
$cutUrl['query'],
$cutUrl['fragment'],
);
$cutUrl = self::phpUrlPartsToString($cutUrl);

if ($path === '' || $path[0] === '#') {
return $cutUrl . $path;
}

return rtrim($cutUrl, '/') . '/' . ltrim($path, '/');
}

/**
* @return array{
* scheme?: string,
* host?: string,
* port?: int,
* user?: string,
* pass?: string,
* query?: string,
* path?: string,
* fragment?: string,
* }
*/
public static function parseUrl(string $uri): array
{
$parts = parse_url($uri);

if ($parts === false) {
throw new InvalidArgumentException("Invalid URI {$uri}");
}

return $parts;
}

/**
* @param array{
* scheme?: string,
* host?: string,
* port?: int,
* user?: string,
* pass?: string,
* query?: string,
* path?: string,
* fragment?: string,
* } $urlParts
*/
public static function phpUrlPartsToString(array $urlParts): string
{
$uri = '';
$scheme = $urlParts['scheme'] ?? '';
$host = $urlParts['host'] ?? '';
$path = $urlParts['path'] ?? '';

if ($scheme !== '') {
$uri .= $scheme . ':';
}

if ($host !== '' || $scheme === 'file') {
$uri .= '//' . $host;

if (($urlParts['port'] ?? '') !== '') {
$uri .= ':' . $urlParts['port'];
}
}

if ($host !== '' && $path !== '' && $path[0] !== '/') {
$path = '/' . $path;
}

$uri .= $path;

if (($urlParts['query'] ?? '') !== '') {
$uri .= '?' . $urlParts['query'];
}

if (($urlParts['fragment'] ?? '') !== '') {
$uri .= '#' . $urlParts['fragment'];
}

return $uri;
}
}
58 changes: 58 additions & 0 deletions tests/Util/UriTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Tests\Codeception\Util;

use Codeception\Util\Uri;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class UriTest extends TestCase
Expand Down Expand Up @@ -125,4 +126,61 @@ public function testMergeUrlsWhenBaseUriEndsWithSlashButUriPathHasNoLeadingSlash
Uri::mergeUrls('https://codeception.com/', 'test')
);
}

#[DataProvider('phpUrlPartsProvider')]
public function testPhpUrlPartsToString(array $parts, string $expected): void
{
$this->assertSame($expected, Uri::phpUrlPartsToString($parts));
}

public static function phpUrlPartsProvider(): iterable
{
yield [
[
'path' => '/test',
],
'/test'
];

yield [
[
'query' => 'test=a',
],
'?test=a'
];

yield [
[
'scheme' => 'https',
'host' => 'codeception.com',
'path' => '/test',
],
'https://codeception.com/test'
];

yield [
[
'scheme' => 'https',
'host' => 'codeception.com',
],
'https://codeception.com'
];

yield [
[
'scheme' => 'https',
'host' => 'codeception.com',
],
'https://codeception.com'
];

yield [
[
'scheme' => 'https',
'host' => 'codeception.com',
'port' => 8080,
],
'https://codeception.com:8080'
];
}
}