PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.20
Booking for Appointments and Events Calendar – Amelia v1.2.20
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / sabre / uri / lib / functions.php
ameliabooking / vendor / sabre / uri / lib Last commit date
InvalidUriException.php 1 year ago Version.php 1 year ago functions.php 1 year ago
functions.php
426 lines
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Sabre\Uri;
6
7 /**
8 * This file contains all the uri handling functions.
9 *
10 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11 * @author Evert Pot (http://evertpot.com/)
12 * @license http://sabre.io/license/
13 */
14
15 /**
16 * Resolves relative urls, like a browser would.
17 *
18 * This function takes a basePath, which itself _may_ also be relative, and
19 * then applies the relative path on top of it.
20 *
21 * @throws InvalidUriException
22 */
23 function resolve(string $basePath, string $newPath): string
24 {
25 $delta = parse($newPath);
26
27 // If the new path defines a scheme, it's absolute and we can just return
28 // that.
29 if (null !== $delta['scheme']) {
30 return build($delta);
31 }
32
33 $base = parse($basePath);
34 $pick = function ($part) use ($base, $delta) {
35 if (null !== $delta[$part]) {
36 return $delta[$part];
37 } elseif (null !== $base[$part]) {
38 return $base[$part];
39 }
40
41 return null;
42 };
43
44 $newParts = [];
45
46 $newParts['scheme'] = $pick('scheme');
47 $newParts['host'] = $pick('host');
48 $newParts['port'] = $pick('port');
49
50 if (is_string($delta['path']) and strlen($delta['path']) > 0) {
51 // If the path starts with a slash
52 if ('/' === $delta['path'][0]) {
53 $path = $delta['path'];
54 } else {
55 // Removing last component from base path.
56 $path = (string) $base['path'];
57 $length = strrpos($path, '/');
58 if (false !== $length) {
59 $path = substr($path, 0, $length);
60 }
61 $path .= '/'.$delta['path'];
62 }
63 } else {
64 $path = $base['path'] ?? '/';
65 if ('' === $path) {
66 $path = '/';
67 }
68 }
69 // Removing .. and .
70 $pathParts = explode('/', $path);
71 $newPathParts = [];
72 foreach ($pathParts as $pathPart) {
73 switch ($pathPart) {
74 // case '' :
75 case '.':
76 break;
77 case '..':
78 array_pop($newPathParts);
79 break;
80 default:
81 $newPathParts[] = $pathPart;
82 break;
83 }
84 }
85
86 $path = implode('/', $newPathParts);
87
88 // If the source url ended with a /, we want to preserve that.
89 $newParts['path'] = 0 === strpos($path, '/') ? $path : '/'.$path;
90 // From PHP 8, no "?" query at all causes 'query' to be null.
91 // An empty query "http://example.com/foo?" causes 'query' to be the empty string
92 if (null !== $delta['query'] && '' !== $delta['query']) {
93 $newParts['query'] = $delta['query'];
94 } elseif (isset($base['query']) && null === $delta['host'] && null === $delta['path']) {
95 // Keep the old query if host and path didn't change
96 $newParts['query'] = $base['query'];
97 }
98 // From PHP 8, no "#" fragment at all causes 'fragment' to be null.
99 // An empty fragment "http://example.com/foo#" causes 'fragment' to be the empty string
100 if (null !== $delta['fragment'] && '' !== $delta['fragment']) {
101 $newParts['fragment'] = $delta['fragment'];
102 }
103
104 return build($newParts);
105 }
106
107 /**
108 * Takes a URI or partial URI as its argument, and normalizes it.
109 *
110 * After normalizing a URI, you can safely compare it to other URIs.
111 * This function will for instance convert a %7E into a tilde, according to
112 * rfc3986.
113 *
114 * It will also change a %3a into a %3A.
115 *
116 * @throws InvalidUriException
117 */
118 function normalize(string $uri): string
119 {
120 $parts = parse($uri);
121
122 if (null !== $parts['path']) {
123 $pathParts = explode('/', ltrim($parts['path'], '/'));
124 $newPathParts = [];
125 foreach ($pathParts as $pathPart) {
126 switch ($pathPart) {
127 case '.':
128 // skip
129 break;
130 case '..':
131 // One level up in the hierarchy
132 array_pop($newPathParts);
133 break;
134 default:
135 // Ensuring that everything is correctly percent-encoded.
136 $newPathParts[] = rawurlencode(rawurldecode($pathPart));
137 break;
138 }
139 }
140 $parts['path'] = '/'.implode('/', $newPathParts);
141 }
142
143 if (null !== $parts['scheme']) {
144 $parts['scheme'] = strtolower($parts['scheme']);
145 $defaultPorts = [
146 'http' => '80',
147 'https' => '443',
148 ];
149
150 if (null !== $parts['port'] && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) {
151 // Removing default ports.
152 unset($parts['port']);
153 }
154 // A few HTTP specific rules.
155 switch ($parts['scheme']) {
156 case 'http':
157 case 'https':
158 if (null === $parts['path']) {
159 // An empty path is equivalent to / in http.
160 $parts['path'] = '/';
161 }
162 break;
163 }
164 }
165
166 if (null !== $parts['host']) {
167 $parts['host'] = strtolower($parts['host']);
168 }
169
170 return build($parts);
171 }
172
173 /**
174 * Parses a URI and returns its individual components.
175 *
176 * This method largely behaves the same as PHP's parse_url, except that it will
177 * return an array with all the array keys, including the ones that are not
178 * set by parse_url, which makes it a bit easier to work with.
179 *
180 * Unlike PHP's parse_url, it will also convert any non-ascii characters to
181 * percent-encoded strings. PHP's parse_url corrupts these characters on OS X.
182 *
183 * In the return array, key "port" is an int value. Other keys have a string value.
184 * "Unused" keys have value null.
185 *
186 * @return array{scheme: string|null, host: string|null, path: string|null, port: positive-int|null, user: string|null, query: string|null, fragment: string|null}
187 *
188 * @throws InvalidUriException
189 */
190 function parse(string $uri): array
191 {
192 // Normally a URI must be ASCII. However, often it's not and
193 // parse_url might corrupt these strings.
194 //
195 // For that reason we take any non-ascii characters from the uri and
196 // uriencode them first.
197 $uri = preg_replace_callback(
198 '/[^[:ascii:]]/u',
199 function ($matches) {
200 return rawurlencode($matches[0]);
201 },
202 $uri
203 );
204
205 if (null === $uri) {
206 throw new InvalidUriException('Invalid, or could not parse URI');
207 }
208
209 $result = parse_url($uri);
210 if (false === $result) {
211 $result = _parse_fallback($uri);
212 } else {
213 // Add empty host and leading slash to Windows file paths
214 // file:///C:/path or file:///C:\path
215 // Note: the regex fragment [a-zA-Z]:[\/\\\\].* end up being
216 // [a-zA-Z]:[\/\\].*
217 // The 4 backslash in a row are the way to get 2 backslash into the actual string
218 // that is used as the regex. The 2 backslash are then the way to get 1 backslash
219 // character into the character set "a forward slash or a backslash"
220 if (isset($result['scheme']) && 'file' === $result['scheme'] && isset($result['path'])
221 && 1 === preg_match('/^(?<windows_path> [a-zA-Z]:([\/\\\\].*)?)$/x', $result['path'])) {
222 $result['path'] = '/'.$result['path'];
223 $result['host'] = '';
224 }
225 }
226
227 /*
228 * phpstan is not able to process all the things that happen while this function
229 * constructs the result array. It only understands the $result is
230 * non-empty-array<string, mixed>
231 *
232 * But the detail of the returned array is correctly specified in the PHPdoc
233 * above the function call.
234 *
235 * @phpstan-ignore-next-line
236 */
237 return
238 $result + [
239 'scheme' => null,
240 'host' => null,
241 'path' => null,
242 'port' => null,
243 'user' => null,
244 'query' => null,
245 'fragment' => null,
246 ];
247 }
248
249 /**
250 * This function takes the components returned from PHP's parse_url, and uses
251 * it to generate a new uri.
252 *
253 * @param array<string, int|string|null> $parts
254 */
255 function build(array $parts): string
256 {
257 $uri = '';
258
259 $authority = '';
260 if (isset($parts['host'])) {
261 $authority = $parts['host'];
262 if (isset($parts['user'])) {
263 $authority = $parts['user'].'@'.$authority;
264 }
265 if (isset($parts['port'])) {
266 $authority = $authority.':'.$parts['port'];
267 }
268 }
269
270 if (isset($parts['scheme'])) {
271 // If there's a scheme, there's also a host.
272 $uri = $parts['scheme'].':';
273 }
274 if ('' !== $authority || (isset($parts['scheme']) && 'file' === $parts['scheme'])) {
275 // No scheme, but there is a host.
276 $uri .= '//'.$authority;
277 }
278
279 if (isset($parts['path'])) {
280 $uri .= $parts['path'];
281 }
282 if (isset($parts['query'])) {
283 $uri .= '?'.$parts['query'];
284 }
285 if (isset($parts['fragment'])) {
286 $uri .= '#'.$parts['fragment'];
287 }
288
289 return $uri;
290 }
291
292 /**
293 * Returns the 'dirname' and 'basename' for a path.
294 *
295 * The reason there is a custom function for this purpose, is because
296 * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale
297 * is used) and we need a method that just operates on UTF-8 characters.
298 *
299 * In addition basename and dirname are platform aware, and will treat
300 * backslash (\) as a directory separator on Windows.
301 *
302 * This method returns the 2 components as an array.
303 *
304 * If there is no dirname, it will return an empty string. Any / appearing at
305 * the end of the string is stripped off.
306 *
307 * @return list<mixed>
308 */
309 function split(string $path): array
310 {
311 $matches = [];
312 if (1 === preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) {
313 return [$matches[1], $matches[2]];
314 }
315
316 return [null, null];
317 }
318
319 /**
320 * This function is another implementation of parse_url, except this one is
321 * fully written in PHP.
322 *
323 * The reason is that the PHP bug team is not willing to admit that there are
324 * bugs in the parse_url implementation.
325 *
326 * This function is only called if the main parse method fails. It's pretty
327 * crude and probably slow, so the original parse_url is usually preferred.
328 *
329 * @return array{scheme: string|null, host: string|null, path: string|null, port: positive-int|null, user: string|null, query: string|null, fragment: string|null}
330 *
331 * @throws InvalidUriException
332 */
333 function _parse_fallback(string $uri): array
334 {
335 // Normally a URI must be ASCII, however. However, often it's not and
336 // parse_url might corrupt these strings.
337 //
338 // For that reason we take any non-ascii characters from the uri and
339 // uriencode them first.
340 $uri = preg_replace_callback(
341 '/[^[:ascii:]]/u',
342 function ($matches) {
343 return rawurlencode($matches[0]);
344 },
345 $uri
346 );
347
348 if (null === $uri) {
349 throw new InvalidUriException('Invalid, or could not parse URI');
350 }
351
352 $result = [
353 'scheme' => null,
354 'host' => null,
355 'port' => null,
356 'user' => null,
357 'path' => null,
358 'fragment' => null,
359 'query' => null,
360 ];
361
362 if (1 === preg_match('% ^([A-Za-z][A-Za-z0-9+-\.]+): %x', $uri, $matches)) {
363 $result['scheme'] = $matches[1];
364 // Take what's left.
365 $uri = substr($uri, strlen($result['scheme']) + 1);
366 if (false === $uri) {
367 // There was nothing left.
368 $uri = '';
369 }
370 }
371
372 // Taking off a fragment part
373 if (false !== strpos($uri, '#')) {
374 list($uri, $result['fragment']) = explode('#', $uri, 2);
375 }
376 // Taking off the query part
377 if (false !== strpos($uri, '?')) {
378 list($uri, $result['query']) = explode('?', $uri, 2);
379 }
380
381 if ('///' === substr($uri, 0, 3)) {
382 // The triple slash uris are a bit unusual, but we have special handling
383 // for them.
384 $path = substr($uri, 2);
385 if (false === $path) {
386 throw new \RuntimeException('The string cannot be false');
387 }
388 $result['path'] = $path;
389 $result['host'] = '';
390 } elseif ('//' === substr($uri, 0, 2)) {
391 // Uris that have an authority part.
392 $regex = '%^
393 //
394 (?: (?<user> [^:@]+) (: (?<pass> [^@]+)) @)?
395 (?<host> ( [^:/]* | \[ [^\]]+ \] ))
396 (?: : (?<port> [0-9]+))?
397 (?<path> / .*)?
398 $%x';
399 if (1 !== preg_match($regex, $uri, $matches)) {
400 throw new InvalidUriException('Invalid, or could not parse URI');
401 }
402 if (isset($matches['host']) && '' !== $matches['host']) {
403 $result['host'] = $matches['host'];
404 }
405 if (isset($matches['port'])) {
406 $port = (int) $matches['port'];
407 if ($port > 0) {
408 $result['port'] = $port;
409 }
410 }
411 if (isset($matches['path'])) {
412 $result['path'] = $matches['path'];
413 }
414 if (isset($matches['user']) && '' !== $matches['user']) {
415 $result['user'] = $matches['user'];
416 }
417 if (isset($matches['pass']) && '' !== $matches['pass']) {
418 $result['pass'] = $matches['pass'];
419 }
420 } else {
421 $result['path'] = $uri;
422 }
423
424 return $result;
425 }
426