Skip to content

Commit 4e815b6

Browse files
committed
Add internal Uri::resolve() to resolve URIs relative to base URI
1 parent 1d7790e commit 4e815b6

File tree

4 files changed

+192
-4
lines changed

4 files changed

+192
-4
lines changed

src/Browser.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
namespace React\Http;
44

55
use Psr\Http\Message\ResponseInterface;
6-
use RingCentral\Psr7\Uri;
76
use React\EventLoop\Loop;
87
use React\EventLoop\LoopInterface;
98
use React\Http\Io\Sender;
109
use React\Http\Io\Transaction;
1110
use React\Http\Message\Request;
11+
use React\Http\Message\Uri;
1212
use React\Promise\PromiseInterface;
1313
use React\Socket\ConnectorInterface;
1414
use React\Stream\ReadableStreamInterface;
@@ -834,7 +834,7 @@ private function requestMayBeStreaming($method, $url, array $headers = array(),
834834
{
835835
if ($this->baseUrl !== null) {
836836
// ensure we're actually below the base URL
837-
$url = Uri::resolve($this->baseUrl, $url);
837+
$url = Uri::resolve($this->baseUrl, new Uri($url));
838838
}
839839

840840
foreach ($this->defaultHeaders as $key => $value) {

src/Io/Transaction.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
use React\EventLoop\LoopInterface;
99
use React\Http\Message\Response;
1010
use React\Http\Message\ResponseException;
11+
use React\Http\Message\Uri;
1112
use React\Promise\Deferred;
1213
use React\Promise\Promise;
1314
use React\Promise\PromiseInterface;
1415
use React\Stream\ReadableStreamInterface;
15-
use RingCentral\Psr7\Uri;
1616

1717
/**
1818
* @internal
@@ -264,7 +264,7 @@ public function onResponse(ResponseInterface $response, RequestInterface $reques
264264
private function onResponseRedirect(ResponseInterface $response, RequestInterface $request, Deferred $deferred, ClientRequestState $state)
265265
{
266266
// resolve location relative to last request URI
267-
$location = Uri::resolve($request->getUri(), $response->getHeaderLine('Location'));
267+
$location = Uri::resolve($request->getUri(), new Uri($response->getHeaderLine('Location')));
268268

269269
$request = $this->makeRedirectRequest($request, $location, $response->getStatusCode());
270270
$this->progress('redirect', array($request));

src/Message/Uri.php

+64
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,68 @@ function (array $match) {
280280
$part
281281
);
282282
}
283+
284+
/**
285+
* [Internal] Resolve URI relative to base URI and return new absolute URI
286+
*
287+
* @internal
288+
* @param UriInterface $base
289+
* @param UriInterface $rel
290+
* @return UriInterface
291+
* @throws void
292+
*/
293+
public static function resolve(UriInterface $base, UriInterface $rel)
294+
{
295+
if ($rel->getScheme() !== '') {
296+
return $rel->getPath() === '' ? $rel : $rel->withPath(self::removeDotSegments($rel->getPath()));
297+
}
298+
299+
$reset = false;
300+
$new = $base;
301+
if ($rel->getAuthority() !== '') {
302+
$reset = true;
303+
$userInfo = \explode(':', $rel->getUserInfo(), 2);
304+
$new = $base->withUserInfo($userInfo[0], isset($userInfo[1]) ? $userInfo[1]: null)->withHost($rel->getHost())->withPort($rel->getPort());
305+
}
306+
307+
if ($reset && $rel->getPath() === '') {
308+
$new = $new->withPath('');
309+
} elseif (($path = $rel->getPath()) !== '') {
310+
$start = '';
311+
if ($path === '' || $path[0] !== '/') {
312+
$start = $base->getPath();
313+
if (\substr($start, -1) !== '/') {
314+
$start .= '/../';
315+
}
316+
}
317+
$reset = true;
318+
$new = $new->withPath(self::removeDotSegments($start . $path));
319+
}
320+
if ($reset || $rel->getQuery() !== '') {
321+
$reset = true;
322+
$new = $new->withQuery($rel->getQuery());
323+
}
324+
if ($reset || $rel->getFragment() !== '') {
325+
$new = $new->withFragment($rel->getFragment());
326+
}
327+
328+
return $new;
329+
}
330+
331+
/**
332+
* @param string $path
333+
* @return string
334+
*/
335+
private static function removeDotSegments($path)
336+
{
337+
$segments = array();
338+
foreach (\explode('/', $path) as $segment) {
339+
if ($segment === '..') {
340+
\array_pop($segments);
341+
} elseif ($segment !== '.' && $segment !== '') {
342+
$segments[] = $segment;
343+
}
344+
}
345+
return '/' . \implode('/', $segments) . ($path !== '/' && \substr($path, -1) === '/' ? '/' : '');
346+
}
283347
}

tests/Message/UriTest.php

+124
Original file line numberDiff line numberDiff line change
@@ -578,4 +578,128 @@ public function testWithFragmentReturnsSameInstanceWhenFragmentIsUnchangedEncode
578578
$this->assertSame($uri, $new);
579579
$this->assertEquals('section%20new%20text!', $uri->getFragment());
580580
}
581+
582+
public static function provideResolveUris()
583+
{
584+
return array(
585+
array(
586+
'http://localhost/',
587+
'',
588+
'http://localhost/'
589+
),
590+
array(
591+
'http://localhost/',
592+
'http://example.com/',
593+
'http://example.com/'
594+
),
595+
array(
596+
'http://localhost/',
597+
'path',
598+
'http://localhost/path'
599+
),
600+
array(
601+
'http://localhost/',
602+
'path/',
603+
'http://localhost/path/'
604+
),
605+
array(
606+
'http://localhost/',
607+
'path//',
608+
'http://localhost/path/'
609+
),
610+
array(
611+
'http://localhost',
612+
'path',
613+
'http://localhost/path'
614+
),
615+
array(
616+
'http://localhost/a/b',
617+
'/path',
618+
'http://localhost/path'
619+
),
620+
array(
621+
'http://localhost/',
622+
'/a/b/c',
623+
'http://localhost/a/b/c'
624+
),
625+
array(
626+
'http://localhost/a/path',
627+
'b/c',
628+
'http://localhost/a/b/c'
629+
),
630+
array(
631+
'http://localhost/a/path',
632+
'/b/c',
633+
'http://localhost/b/c'
634+
),
635+
array(
636+
'http://localhost/a/path/',
637+
'b/c',
638+
'http://localhost/a/path/b/c'
639+
),
640+
array(
641+
'http://localhost/a/path/',
642+
'../b/c',
643+
'http://localhost/a/b/c'
644+
),
645+
array(
646+
'http://localhost',
647+
'../../../a/b',
648+
'http://localhost/a/b'
649+
),
650+
array(
651+
'http://localhost/path',
652+
'?query',
653+
'http://localhost/path?query'
654+
),
655+
array(
656+
'http://localhost/path',
657+
'#fragment',
658+
'http://localhost/path#fragment'
659+
),
660+
array(
661+
'http://localhost/path',
662+
'http://localhost',
663+
'http://localhost'
664+
),
665+
array(
666+
'http://localhost/path',
667+
'http://localhost/?query#fragment',
668+
'http://localhost/?query#fragment'
669+
),
670+
array(
671+
'http://localhost/path/?a#fragment',
672+
'?b',
673+
'http://localhost/path/?b'
674+
),
675+
array(
676+
'http://localhost/path',
677+
'//localhost',
678+
'http://localhost'
679+
),
680+
array(
681+
'http://localhost/path',
682+
'//localhost/a?query',
683+
'http://localhost/a?query'
684+
),
685+
array(
686+
'http://localhost/path',
687+
'//LOCALHOST',
688+
'http://localhost'
689+
)
690+
);
691+
}
692+
693+
/**
694+
* @dataProvider provideResolveUris
695+
* @param string $base
696+
* @param string $rel
697+
* @param string $expected
698+
*/
699+
public function testResolveReturnsResolvedUri($base, $rel, $expected)
700+
{
701+
$uri = Uri::resolve(new Uri($base), new Uri($rel));
702+
703+
$this->assertEquals($expected, (string) $uri);
704+
}
581705
}

0 commit comments

Comments
 (0)