Skip to content

Commit 949858e

Browse files
committed
replace custom UriInterface implementation with a \Uri\Rfc3986\Uri wrapper
1 parent 1aead0f commit 949858e

File tree

14 files changed

+602
-327
lines changed

14 files changed

+602
-327
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
strategy:
1515
matrix:
16-
php-version: [ '8.3', '8.4', '8.5' ]
16+
php-version: [ '8.5' ]
1717

1818
name: Run tests on PHP v${{ matrix.php-version }}
1919

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"psr/http-message": "^1.1 || ^2.0",
88
"psr/http-factory": "^1.1",
99
"ext-curl": "*",
10-
"php": ">=8.3.0"
10+
"php": ">=8.5",
11+
"ext-uri": "*"
1112
},
1213
"provide": {
1314
"psr/http-client-implementation": "1.0",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace Aternos\CurlPsr\Exception;
4+
5+
use Exception;
6+
use Psr\Http\Message\UriInterface;
7+
use Throwable;
8+
9+
class UriResolutionException extends Exception implements UriResolutionExceptionInterface
10+
{
11+
/**
12+
* @param UriInterface $baseUri
13+
* @param UriInterface $targetUri
14+
* @param string $message
15+
* @param int $code
16+
* @param Throwable|null $previous
17+
*/
18+
public function __construct(
19+
protected UriInterface $baseUri,
20+
protected UriInterface $targetUri,
21+
string $message = "",
22+
int $code = 0,
23+
?Throwable $previous = null
24+
)
25+
{
26+
parent::__construct($message, $code, $previous);
27+
}
28+
29+
/**
30+
* @inheritDoc
31+
*/
32+
public function getBaseUri(): UriInterface
33+
{
34+
return $this->baseUri;
35+
}
36+
37+
/**
38+
* @inheritDoc
39+
*/
40+
public function getTargetUri(): UriInterface
41+
{
42+
return $this->targetUri;
43+
}
44+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Aternos\CurlPsr\Exception;
4+
5+
use Psr\Http\Message\UriInterface;
6+
use Throwable;
7+
8+
interface UriResolutionExceptionInterface extends Throwable
9+
{
10+
/**
11+
* @return UriInterface
12+
*/
13+
public function getBaseUri(): UriInterface;
14+
15+
/**
16+
* @return UriInterface
17+
*/
18+
public function getTargetUri(): UriInterface;
19+
}

src/Psr18/Client.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Aternos\CurlPsr\Exception\RequestException;
1010
use Aternos\CurlPsr\Exception\RequestRedirectedException;
1111
use Aternos\CurlPsr\Exception\TooManyRedirectsException;
12+
use Aternos\CurlPsr\Exception\UriResolutionExceptionInterface;
1213
use Aternos\CurlPsr\Psr17\Psr17Factory;
1314
use Aternos\CurlPsr\Psr18\UriResolver\UriResolver;
1415
use Aternos\CurlPsr\Psr18\UriResolver\UriResolverInterface;
@@ -302,7 +303,11 @@ protected function handleRedirect(RequestInterface $request, ResponseInterface $
302303
}
303304

304305
$originalUri = $request->getUri();
305-
$location = $this->uriResolver->resolve($originalUri, $relativeUri);
306+
try {
307+
$location = $this->uriResolver->resolve($originalUri, $relativeUri);
308+
} catch (UriResolutionExceptionInterface $e) {
309+
throw new RequestException($request, "Could not resolve redirect location", previous: $e);
310+
}
306311
$request = $request->withUri($location);
307312

308313
if (in_array($response->getStatusCode(), $options->redirectToGetStatusCodes)) {

src/Psr18/UriResolver/UriResolver.php

Lines changed: 18 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
namespace Aternos\CurlPsr\Psr18\UriResolver;
44

5+
use Aternos\CurlPsr\Exception\UriResolutionException;
6+
use Aternos\CurlPsr\Psr7\Uri;
7+
use InvalidArgumentException;
58
use Psr\Http\Message\UriFactoryInterface;
69
use Psr\Http\Message\UriInterface;
10+
use Throwable;
711

812
class UriResolver implements UriResolverInterface
913
{
@@ -21,83 +25,25 @@ public function __construct(
2125
*/
2226
public function resolve(UriInterface $baseUri, UriInterface $relativeUri): UriInterface
2327
{
24-
if ($relativeUri->getScheme() !== "") {
25-
return $relativeUri;
26-
}
27-
28-
if ($relativeUri->getAuthority() !== "") {
29-
return $relativeUri
30-
->withPath($this->removeDotSegments($relativeUri->getPath()))
31-
->withScheme($baseUri->getScheme());
32-
}
33-
34-
$result = $this->uriFactory->createUri();
35-
if ($relativeUri->getPath() === "") {
36-
$result = $result->withPath($baseUri->getPath());
37-
if ($relativeUri->getQuery() !== "") {
38-
$result = $result->withQuery($relativeUri->getQuery());
39-
} else {
40-
$result = $result->withQuery($baseUri->getQuery());
41-
}
28+
if ($baseUri instanceof Uri) {
29+
$builtInBaseUri = $baseUri->getUri();
4230
} else {
43-
$path = $this->mergePaths($baseUri->getPath(), $relativeUri->getPath());
44-
$result = $result->withPath($this->removeDotSegments($path))
45-
->withQuery($relativeUri->getQuery());
46-
}
47-
$result = $result->withUserInfo($baseUri->getUserInfo())
48-
->withHost($baseUri->getHost())
49-
->withPort($baseUri->getPort());
50-
51-
return $result->withScheme($baseUri->getScheme())
52-
->withFragment($relativeUri->getFragment());
53-
}
54-
55-
/**
56-
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.3
57-
* @param string $base
58-
* @param string $relative
59-
* @return string
60-
*/
61-
protected function mergePaths(string $base, string $relative): string
62-
{
63-
if (str_starts_with($relative, "/")) {
64-
return $relative;
65-
}
66-
67-
$index = strrpos($base, "/");
68-
if ($index === false) {
69-
return "/" . $relative;
70-
}
71-
72-
return substr($base, 0, $index + 1) . $relative;
73-
}
74-
75-
/**
76-
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
77-
* @param string $path
78-
* @return string
79-
*/
80-
protected function removeDotSegments(string $path): string
81-
{
82-
$resultParts = [];
83-
$parts = explode("/", $path);
84-
85-
foreach ($parts as $part) {
86-
if ($part === "..") {
87-
array_pop($resultParts);
88-
} elseif ($part !== ".") {
89-
$resultParts[] = $part;
31+
$builtInBaseUri = \Uri\Rfc3986\Uri::parse((string)$baseUri);
32+
if ($builtInBaseUri === null) {
33+
throw new UriResolutionException($baseUri, $relativeUri, "Could not create built-in URI from base URI");
9034
}
9135
}
9236

93-
$result = implode("/", $resultParts);
94-
95-
if (str_starts_with($path, "/") && !str_starts_with($result, "/")) {
96-
$result = "/" . $result;
97-
} else if ($result !== "" && in_array(end($parts), [".", ".."])) {
98-
$result .= "/";
37+
try {
38+
$resolved = $builtInBaseUri->resolve((string)$relativeUri);
39+
} catch (Throwable $e) {
40+
throw new UriResolutionException($baseUri, $relativeUri, "Could not resolve URI relative to base URI", previous: $e);
9941
}
10042

101-
return $result;
43+
try {
44+
return $this->uriFactory->createUri($resolved->toString());
45+
} catch (InvalidArgumentException $e) {
46+
throw new UriResolutionException($baseUri, $relativeUri, "Could not create URI from resolved URI string", previous: $e);
47+
}
10248
}
10349
}

src/Psr18/UriResolver/UriResolverInterface.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace Aternos\CurlPsr\Psr18\UriResolver;
44

5+
use Aternos\CurlPsr\Exception\UriResolutionExceptionInterface;
56
use Psr\Http\Message\UriInterface;
7+
use Throwable;
68

79
interface UriResolverInterface
810
{
@@ -12,6 +14,7 @@ interface UriResolverInterface
1214
*
1315
* @param UriInterface $baseUri
1416
* @param UriInterface $relativeUri
17+
* @throws UriResolutionExceptionInterface
1518
* @return UriInterface
1619
*/
1720
public function resolve(UriInterface $baseUri, UriInterface $relativeUri): UriInterface;

0 commit comments

Comments
 (0)