10000 Extracted code to expand an URI to `UriExpanderTrait` · symfony/symfony@0c4bd87 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0c4bd87

Browse files
committed
Extracted code to expand an URI to UriExpanderTrait
1 parent ddc0169 commit 0c4bd87

File tree

4 files changed

+255
-102
lines changed

4 files changed

+255
-102
lines changed

src/Symfony/Component/DomCrawler/AbstractUriElement.php

Lines changed: 3 additions & 102 deletions
B41A
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
abstract class AbstractUriElement
2020
{
21+
use UriExpanderTrait;
22+
2123
/**
2224
* @var \DOMElement
2325
*/
@@ -80,46 +82,7 @@ public function getMethod()
8082
*/
8183
public function getUri()
8284
{
83-
$uri = trim($this->getRawUri());
84-
85-
// absolute URL?
86-
if (null !== parse_url($uri, PHP_URL_SCHEME)) {
87-
return $uri;
88-
}
89-
90-
// empty URI
91-
if (!$uri) {
92-
return $this->currentUri;
93-
}
94-
95-
// an anchor
96-
if ('#' === $uri[0]) {
97-
return $this->cleanupAnchor($this->currentUri).$uri;
98-
}
99-
100-
$baseUri = $this->cleanupUri($this->currentUri);
101-
102-
if ('?' === $uri[0]) {
103-
return $baseUri.$uri;
104-
}
105-
106-
// absolute URL with relative schema
107-
if (0 === strpos($uri, '//')) {
108-
return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri;
109-
}
110-
111-
$baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri);
112-
113-
// absolute path
114-
if ('/' === $uri[0]) {
115-
return $baseUri.$uri;
116-
}
117-
118-
// relative path
119-
$path = parse_url(substr($this->currentUri, \strlen($baseUri)), PHP_URL_PATH);
120-
$path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri);
121-
122-
return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path;
85+
return $this->expandUri($this->getRawUri(), $this->currentUri);
12386
}
12487

12588
/**
@@ -129,36 +92,6 @@ public function getUri()
12992
*/
13093
abstract protected function getRawUri();
13194

132-
/**
133-
* Returns the canonicalized URI path (see RFC 3986, section 5.2.4).
134-
*
135-
* @param string $path URI path
136-
*
137-
* @return string
138-
*/
139-
protected function canonicalizePath(string $path)
140-
{
141-
if ('' === $path || '/' === $path) {
142-
return $path;
143-
}
144-
145-
if ('.' === substr($path, -1)) {
146-
$path .= '/';
147-
}
148-
149-
$output = [];
150-
151-
foreach (explode('/', $path) as $segment) {
152-
if ('..' === $segment) {
153-
array_pop($output);
154-
} elseif ('.' !== $segment) {
155-
$output[] = $segment;
156-
}
157-
}
158-
159-
return implode('/', $output);
160-
}
161-
16295
/**
16396
* Sets current \DOMElement instance.
16497
*
@@ -167,36 +100,4 @@ protected function canonicalizePath(string $path)
167100
* @throws \LogicException If given node is not an anchor
168101
*/
169102
abstract protected function setNode(\DOMElement $node);
170-
171-
/**
172-
* Removes the query string and the anchor from the given uri.
173-
*/
174-
private function cleanupUri(string $uri): string
175-
{
176-
return $this->cleanupQuery($this->cleanupAnchor($uri));
177-
}
178-
179-
/**
180-
* Remove the query string from the uri.
181-
*/
182-
private function cleanupQuery(string $uri): string
183-
{
184-
if (false !== $pos = strpos($uri, '?')) {
185-
return substr($uri, 0, $pos);
186-
}
187-
188-
return $uri;
189-
}
190-
191-
/**
192-
* Remove the anchor from the uri.
193-
*/
194-
private function cleanupAnchor(string $uri): string
195-
{
196-
if (false !== $pos = strpos($uri, '#')) {
197-
return substr($uri, 0, $pos);
198-
}
199-
200-
return $uri;
201-
}
202103
}

src/Symfony/Component/DomCrawler/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.0.1
5+
-----
6+
7+
* Extracted code to expand an URI to `UriExpanderTrait`
8+
49
5.0.0
510
-----
611

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DomCrawler\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DomCrawler\UriExpanderTrait;
16+
17+
class UriExpanderTraitTest extends TestCase
18+
{
19+
private $iriExpander;
20+
21+
protected function setUp(): void
22+
{
23+
$this->iriExpander = new class() {
24+
use UriExpanderTrait {
25+
expandUri as public doExpandUri;
26+
}
27+
};
28+
}
29+
30+
protected function tearDown(): void
31+
{
32+
$this->iriExpander = null;
33+
}
34+
35+
/**
36+
* @dataProvider provideExpandUriTests
37+
*/
38+
public function testExpandUri(string $uri, string $currentUri, string $expected)
39+
{
40+
$this->assertEquals($expected, $this->iriExpander->doExpandUri($uri, $currentUri));
41+
}
42+
43+
public function provideExpandUriTests()
44+
{
45+
return [
46+
['/foo', 'http://localhost/bar/foo/', 'http://localhost/foo'],
47+
['/foo', 'http://localhost/bar/foo', 'http://localhost/foo'],
48+
['
49+
/foo', 'http://localhost/bar/foo/', 'http://localhost/foo'],
50+
['/foo
51+
', 'http://localhost/bar/foo', 'http://localhost/foo'],
52+
53+
['foo', 'http://localhost/bar/foo/', 'http://localhost/bar/foo/foo'],
54+
['foo', 'http://localhost/bar/foo', 'http://localhost/bar/foo'],
55+
56+
['', 'http://localhost/bar/', 'http://localhost/bar/'],
57+
['#', 'http://localhost/bar/', 'http://localhost/bar/#'],
58+
['#bar', 'http://localhost/bar?a=b', 'http://localhost/bar?a=b#bar'],
59+
['#bar', 'http://localhost/bar/#foo', 'http://localhost/bar/#bar'],
60+
['?a=b', 'http://localhost/bar#foo', 'http://localhost/bar?a=b'],
61+
['?a=b', 'http://localhost/bar/', 'http://localhost/bar/?a=b'],
62+
63+
['http://login.foo.com/foo', 'http://localhost/bar/', 'http://login.foo.com/foo'],
64+
['https://login.foo.com/foo', 'https://localhost/bar/', 'https://login.foo.com/foo'],
65+
['mailto:foo@bar.com', 'http://localhost/foo', 'mailto:foo@bar.com'],
66+
67+
// tests schema relative URL (issue #7169)
68+
['//login.foo.com/foo', 'http://localhost/bar/', 'http://login.foo.com/foo'],
69+
['//login.foo.com/foo', 'https://localhost/bar/', 'https://login.foo.com/foo'],
70+
71+
['?foo=2', 'http://localhost?foo=1', 'http://localhost?foo=2'],
72+
['?foo=2', 'http://localhost/?foo=1', 'http://localhost/?foo=2'],
73+
['?foo=2', 'http://localhost/bar?foo=1', 'http://localhost/bar?foo=2'],
74+
['?foo=2', 'http://localhost/bar/?foo=1', 'http://localhost/bar/?foo=2'],
75+
['?bar=2', 'http://localhost?foo=1', 'http://localhost?bar=2'],
76+
77+
['foo', 'http://login.foo.com/bar/baz?/query/string', 'http://login.foo.com/bar/foo'],
78+
79+
['.', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/'],
80+
['./', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/'],
81+
['./foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo/bar/foo'],
82+
['..', 'http://localhost/foo/bar/baz', 'http://localhost/foo/'],
83+
['../', 'http://localhost/foo/bar/baz', 'http://localhost/foo/'],
84+
['../foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo/foo'],
85+
['../..', 'http://localhost/foo/bar/baz', 'http://localhost/'],
86+
['../../', 'http://localhost/foo/bar/baz', 'http://localhost/'],
87+
['../../foo', 'http://localhost/foo/bar/baz', 'http://localhost/foo'],
88+
['../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'],
89+
['../bar/../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'],
90+
['../bar/./../../foo', 'http://localhost/bar/foo/', 'http://localhost/foo'],
91+
['../../', 'http://localhost/', 'http://localhost/'],
92+
['../../', 'http://localhost', 'http://localhost/'],
93+
94+
['/foo', 'http://localhost?bar=1', 'http://localhost/foo'],
95+
['/foo', 'http://localhost#bar', 'http://localhost/foo'],
96+
['/foo', 'file:///', 'file:///foo'],
97+
['/foo', 'file:///bar/baz', 'file:///foo'],
98+
['foo', 'file:///', 'file:///foo'],
99+
['foo', 'file:///bar/baz', 'file:///bar/foo'],
100+
];
101+
}
102+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DomCrawler;
13+
14+
/**
15+
* Any HTML element that can link to an URI.
16+
*
17+
* @author Fabien Potencier <fabien@symfony.com>
18+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
19+
*/
20+
trait UriExpanderTrait
21+
{
22+
/**
23+
* Expand an URI according to a current Uri.
24+
*
25+
* For example if $uri=/foo/bar and $currentUri=https://symfony.com it will
26+
* return https://symfony.com/foo/bar
27+
*
28+
* If the $uri is not absolute you must pass an absolute $currentUri
29+
*/
30+
private function expandUri(string $uri, ?string $currentUri): string
31+
{
32+
$uri = trim($uri);
33+
34+
// absolute URL?
35+
if (null !== parse_url($uri, PHP_URL_SCHEME)) {
36+
return $uri;
37+
}
38+
39+
if (null === $currentUri) {
40+
throw new \InvalidArgumentException('The URI is relative, so you must define its base URI passing an absolute URL.');
41+
}
42+
43+
// empty URI
44+
if (!$uri) {
45+
return $currentUri;
46+
}
47+
48+
// an anchor
49+
if ('#' === $uri[0]) {
50+
return $this->cleanupAnchor($currentUri).$uri;
51+
}
52+
53+
$baseUri = $this->cleanupUri($currentUri);
54+
55+
if ('?' === $uri[0]) {
56+
return $baseUri.$uri;
57+
}
58+
59+
// absolute URL with relative schema
60+
if (0 === strpos($uri, '//')) {
61+
return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri;
62+
}
63+
64+
$baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri);
65+
66+
// absolute path
67+
if ('/' === $uri[0]) {
68+
return $baseUri.$uri;
69+
}
70+
71+
// relative path
72+
$path = parse_url(substr($currentUri, \strlen($baseUri)), PHP_URL_PATH);
73+
$path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri);
74+
75+
return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path;
76+
}
77+
78+
/**
79+
* Returns the canonicalized URI path (see RFC 3986, section 5.2.4).
80+
*
81+
* @param string $path URI path
82+
*
83+
* @return string
84+
*/
85+
protected function canonicalizePath(string $path)
86+
{
87+
if ('' === $path || '/' === $path) {
88+
return $path;
89+
}
90+
91+
if ('.' === substr($path, -1)) {
92+
$path .= '/';
93+
}
94+
95+
$output = [];
96+
97+
foreach (explode('/', $path) as $segment) {
98+
if ('..' === $segment) {
99+
array_pop($output);
100+
} elseif ('.' !== $segment) {
101+
$output[] = $segment;
102+
}
103+
}
104+
105+
return implode('/', $output);
106+
}
107+
108+
/**
109+
* Removes the query string and the anchor from the given uri.
110+
*
111+
* @internal
112+
*/
113+
private function cleanupUri(string $uri): string
114+
{
115+
return $this->cleanupQuery($this->cleanupAnchor($uri));
116+
}
117+
118+
/**
119+
* Remove the query string from the uri.
120+
*
121+
* @internal
122+
*/
123+
private function cleanupQuery(string $uri): string
124+
{
125+
if (false !== $pos = strpos($uri, '?')) {
126+
return substr($uri, 0, $pos);
127+
}
128+
129+
return $uri;
130+
}
131+
132+
/**
133+
* Remove the anchor from the uri.
134+
*
135+
* @internal
136+
*/
137+
private function cleanupAnchor(string $uri): string
138+
{
139+
if (false !== $pos = strpos($uri, '#')) {
140+
return substr($uri, 0, $pos);
141+
}
142+
143+
return $uri;
144+
}
145+
}

0 commit comments

Comments
 (0)
0