friendship ended with social-app. php is my new best friend
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
12namespace Symfony\Component\DomCrawler;
13
14/**
15 * The UriResolver class takes an URI (relative, absolute, fragment, etc.)
16 * and turns it into an absolute URI against another given base URI.
17 *
18 * @author Fabien Potencier <fabien@symfony.com>
19 * @author Grégoire Pineau <lyrixx@lyrixx.info>
20 */
21class UriResolver
22{
23 /**
24 * Resolves a URI according to a base URI.
25 *
26 * For example if $uri=/foo/bar and $baseUri=https://symfony.com it will
27 * return https://symfony.com/foo/bar
28 *
29 * If the $uri is not absolute you must pass an absolute $baseUri
30 */
31 public static function resolve(string $uri, ?string $baseUri): string
32 {
33 $uri = trim($uri);
34
35 // absolute URL?
36 if (null !== parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#', \PHP_URL_SCHEME)) {
37 return $uri;
38 }
39
40 if (null === $baseUri) {
41 throw new \InvalidArgumentException('The URI is relative, so you must define its base URI passing an absolute URL.');
42 }
43
44 // empty URI
45 if (!$uri) {
46 return $baseUri;
47 }
48
49 // an anchor
50 if ('#' === $uri[0]) {
51 return self::cleanupAnchor($baseUri).$uri;
52 }
53
54 $baseUriCleaned = self::cleanupUri($baseUri);
55
56 if ('?' === $uri[0]) {
57 return $baseUriCleaned.$uri;
58 }
59
60 // absolute URL with relative schema
61 if (str_starts_with($uri, '//')) {
62 return preg_replace('#^([^/]*)//.*$#', '$1', $baseUriCleaned).$uri;
63 }
64
65 $baseUriCleaned = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUriCleaned);
66
67 // absolute path
68 if ('/' === $uri[0]) {
69 return $baseUriCleaned.$uri;
70 }
71
72 // relative path
73 $path = parse_url(substr($baseUri, \strlen($baseUriCleaned)), \PHP_URL_PATH) ?? '';
74 $path = self::canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri);
75
76 return $baseUriCleaned.('' === $path || '/' !== $path[0] ? '/' : '').$path;
77 }
78
79 /**
80 * Returns the canonicalized URI path (see RFC 3986, section 5.2.4).
81 */
82 private static function canonicalizePath(string $path): string
83 {
84 if ('' === $path || '/' === $path) {
85 return $path;
86 }
87
88 if (str_ends_with($path, '.')) {
89 $path .= '/';
90 }
91
92 $output = [];
93
94 foreach (explode('/', $path) as $segment) {
95 if ('..' === $segment) {
96 array_pop($output);
97 } elseif ('.' !== $segment) {
98 $output[] = $segment;
99 }
100 }
101
102 return implode('/', $output);
103 }
104
105 /**
106 * Removes the query string and the anchor from the given uri.
107 */
108 private static function cleanupUri(string $uri): string
109 {
110 return self::cleanupQuery(self::cleanupAnchor($uri));
111 }
112
113 /**
114 * Removes the query string from the uri.
115 */
116 private static function cleanupQuery(string $uri): string
117 {
118 if (false !== $pos = strpos($uri, '?')) {
119 return substr($uri, 0, $pos);
120 }
121
122 return $uri;
123 }
124
125 /**
126 * Removes the anchor from the uri.
127 */
128 private static function cleanupAnchor(string $uri): string
129 {
130 if (false !== $pos = strpos($uri, '#')) {
131 return substr($uri, 0, $pos);
132 }
133
134 return $uri;
135 }
136}