xref: /plugin/davcal/vendor/sabre/uri/lib/functions.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
1*a1a3b679SAndreas Boehler<?php
2*a1a3b679SAndreas Boehler
3*a1a3b679SAndreas Boehlernamespace Sabre\Uri;
4*a1a3b679SAndreas Boehler
5*a1a3b679SAndreas Boehler/**
6*a1a3b679SAndreas Boehler * Resolves relative urls, like a browser would.
7*a1a3b679SAndreas Boehler *
8*a1a3b679SAndreas Boehler * This function takes a basePath, which itself _may_ also be relative, and
9*a1a3b679SAndreas Boehler * then applies the relative path on top of it.
10*a1a3b679SAndreas Boehler *
11*a1a3b679SAndreas Boehler * @param string $basePath
12*a1a3b679SAndreas Boehler * @param string $newPath
13*a1a3b679SAndreas Boehler * @return string
14*a1a3b679SAndreas Boehler */
15*a1a3b679SAndreas Boehlerfunction resolve($basePath, $newPath) {
16*a1a3b679SAndreas Boehler
17*a1a3b679SAndreas Boehler    $base = parse($basePath);
18*a1a3b679SAndreas Boehler    $delta = parse($newPath);
19*a1a3b679SAndreas Boehler
20*a1a3b679SAndreas Boehler    $pick = function($part) use ($base, $delta) {
21*a1a3b679SAndreas Boehler
22*a1a3b679SAndreas Boehler        if ($delta[$part]) {
23*a1a3b679SAndreas Boehler            return $delta[$part];
24*a1a3b679SAndreas Boehler        } elseif ($base[$part]) {
25*a1a3b679SAndreas Boehler            return $base[$part];
26*a1a3b679SAndreas Boehler        }
27*a1a3b679SAndreas Boehler        return null;
28*a1a3b679SAndreas Boehler
29*a1a3b679SAndreas Boehler    };
30*a1a3b679SAndreas Boehler
31*a1a3b679SAndreas Boehler    // If the new path defines a scheme, it's absolute and we can just return
32*a1a3b679SAndreas Boehler    // that.
33*a1a3b679SAndreas Boehler    if ($delta['scheme']) {
34*a1a3b679SAndreas Boehler        return build($delta);
35*a1a3b679SAndreas Boehler    }
36*a1a3b679SAndreas Boehler
37*a1a3b679SAndreas Boehler    $newParts = [];
38*a1a3b679SAndreas Boehler
39*a1a3b679SAndreas Boehler    $newParts['scheme'] = $pick('scheme');
40*a1a3b679SAndreas Boehler    $newParts['host']   = $pick('host');
41*a1a3b679SAndreas Boehler    $newParts['port']   = $pick('port');
42*a1a3b679SAndreas Boehler
43*a1a3b679SAndreas Boehler    $path = '';
44*a1a3b679SAndreas Boehler    if ($delta['path']) {
45*a1a3b679SAndreas Boehler        // If the path starts with a slash
46*a1a3b679SAndreas Boehler        if ($delta['path'][0] === '/') {
47*a1a3b679SAndreas Boehler            $path = $delta['path'];
48*a1a3b679SAndreas Boehler        } else {
49*a1a3b679SAndreas Boehler            // Removing last component from base path.
50*a1a3b679SAndreas Boehler            $path = $base['path'];
51*a1a3b679SAndreas Boehler            if (strpos($path, '/') !== false) {
52*a1a3b679SAndreas Boehler                $path = substr($path, 0, strrpos($path, '/'));
53*a1a3b679SAndreas Boehler            }
54*a1a3b679SAndreas Boehler            $path .= '/' . $delta['path'];
55*a1a3b679SAndreas Boehler        }
56*a1a3b679SAndreas Boehler    } else {
57*a1a3b679SAndreas Boehler        $path = $base['path'] ?: '/';
58*a1a3b679SAndreas Boehler    }
59*a1a3b679SAndreas Boehler    // Removing .. and .
60*a1a3b679SAndreas Boehler    $pathParts = explode('/', $path);
61*a1a3b679SAndreas Boehler    $newPathParts = [];
62*a1a3b679SAndreas Boehler    foreach($pathParts as $pathPart) {
63*a1a3b679SAndreas Boehler
64*a1a3b679SAndreas Boehler        switch($pathPart) {
65*a1a3b679SAndreas Boehler            //case '' :
66*a1a3b679SAndreas Boehler            case '.' :
67*a1a3b679SAndreas Boehler                break;
68*a1a3b679SAndreas Boehler            case '..' :
69*a1a3b679SAndreas Boehler                array_pop($newPathParts);
70*a1a3b679SAndreas Boehler                break;
71*a1a3b679SAndreas Boehler            default :
72*a1a3b679SAndreas Boehler                $newPathParts[] = $pathPart;
73*a1a3b679SAndreas Boehler                break;
74*a1a3b679SAndreas Boehler        }
75*a1a3b679SAndreas Boehler    }
76*a1a3b679SAndreas Boehler
77*a1a3b679SAndreas Boehler    $path = implode('/', $newPathParts);
78*a1a3b679SAndreas Boehler
79*a1a3b679SAndreas Boehler    // If the source url ended with a /, we want to preserve that.
80*a1a3b679SAndreas Boehler    $newParts['path'] = $path;
81*a1a3b679SAndreas Boehler    if ($delta['query']) {
82*a1a3b679SAndreas Boehler        $newParts['query'] = $delta['query'];
83*a1a3b679SAndreas Boehler    } elseif (!empty($base['query']) && empty($delta['host']) && empty($delta['path'])) {
84*a1a3b679SAndreas Boehler        // Keep the old query if host and path didn't change
85*a1a3b679SAndreas Boehler        $newParts['query'] = $base['query'];
86*a1a3b679SAndreas Boehler    }
87*a1a3b679SAndreas Boehler    if ($delta['fragment']) {
88*a1a3b679SAndreas Boehler        $newParts['fragment'] = $delta['fragment'];
89*a1a3b679SAndreas Boehler    }
90*a1a3b679SAndreas Boehler    return build($newParts);
91*a1a3b679SAndreas Boehler
92*a1a3b679SAndreas Boehler}
93*a1a3b679SAndreas Boehler
94*a1a3b679SAndreas Boehler/**
95*a1a3b679SAndreas Boehler * Takes a URI or partial URI as its argument, and normalizes it.
96*a1a3b679SAndreas Boehler *
97*a1a3b679SAndreas Boehler * After normalizing a URI, you can safely compare it to other URIs.
98*a1a3b679SAndreas Boehler * This function will for instance convert a %7E into a tilde, according to
99*a1a3b679SAndreas Boehler * rfc3986.
100*a1a3b679SAndreas Boehler *
101*a1a3b679SAndreas Boehler * It will also change a %3a into a %3A.
102*a1a3b679SAndreas Boehler *
103*a1a3b679SAndreas Boehler * @param string $uri
104*a1a3b679SAndreas Boehler * @return string
105*a1a3b679SAndreas Boehler */
106*a1a3b679SAndreas Boehlerfunction normalize($uri) {
107*a1a3b679SAndreas Boehler
108*a1a3b679SAndreas Boehler    $parts = parse($uri);
109*a1a3b679SAndreas Boehler
110*a1a3b679SAndreas Boehler    if (!empty($parts['path'])) {
111*a1a3b679SAndreas Boehler        $pathParts = explode('/', ltrim($parts['path'], '/'));
112*a1a3b679SAndreas Boehler        $newPathParts = [];
113*a1a3b679SAndreas Boehler        foreach($pathParts as $pathPart) {
114*a1a3b679SAndreas Boehler            switch($pathPart) {
115*a1a3b679SAndreas Boehler                case '.':
116*a1a3b679SAndreas Boehler                    // skip
117*a1a3b679SAndreas Boehler                    break;
118*a1a3b679SAndreas Boehler                case '..' :
119*a1a3b679SAndreas Boehler                    // One level up in the hierarchy
120*a1a3b679SAndreas Boehler                    array_pop($newPathParts);
121*a1a3b679SAndreas Boehler                    break;
122*a1a3b679SAndreas Boehler                default :
123*a1a3b679SAndreas Boehler                    // Ensuring that everything is correctly percent-encoded.
124*a1a3b679SAndreas Boehler                    $newPathParts[] = rawurlencode(rawurldecode($pathPart));
125*a1a3b679SAndreas Boehler                    break;
126*a1a3b679SAndreas Boehler            }
127*a1a3b679SAndreas Boehler        }
128*a1a3b679SAndreas Boehler        $parts['path'] = '/' . implode('/', $newPathParts);
129*a1a3b679SAndreas Boehler    }
130*a1a3b679SAndreas Boehler
131*a1a3b679SAndreas Boehler    if ($parts['scheme']) {
132*a1a3b679SAndreas Boehler        $parts['scheme'] = strtolower($parts['scheme']);
133*a1a3b679SAndreas Boehler        $defaultPorts = [
134*a1a3b679SAndreas Boehler            'http'  => '80',
135*a1a3b679SAndreas Boehler            'https' => '443',
136*a1a3b679SAndreas Boehler        ];
137*a1a3b679SAndreas Boehler
138*a1a3b679SAndreas Boehler        if (!empty($parts['port']) && isset($defaultPorts[$parts['scheme']]) && $defaultPorts[$parts['scheme']] == $parts['port']) {
139*a1a3b679SAndreas Boehler            // Removing default ports.
140*a1a3b679SAndreas Boehler            unset($parts['port']);
141*a1a3b679SAndreas Boehler        }
142*a1a3b679SAndreas Boehler        // A few HTTP specific rules.
143*a1a3b679SAndreas Boehler        switch($parts['scheme']) {
144*a1a3b679SAndreas Boehler            case 'http' :
145*a1a3b679SAndreas Boehler            case 'https' :
146*a1a3b679SAndreas Boehler                if (empty($parts['path'])) {
147*a1a3b679SAndreas Boehler                    // An empty path is equivalent to / in http.
148*a1a3b679SAndreas Boehler                    $parts['path'] = '/';
149*a1a3b679SAndreas Boehler                }
150*a1a3b679SAndreas Boehler                break;
151*a1a3b679SAndreas Boehler        }
152*a1a3b679SAndreas Boehler    }
153*a1a3b679SAndreas Boehler
154*a1a3b679SAndreas Boehler    if ($parts['host']) $parts['host'] = strtolower($parts['host']);
155*a1a3b679SAndreas Boehler
156*a1a3b679SAndreas Boehler    return build($parts);
157*a1a3b679SAndreas Boehler
158*a1a3b679SAndreas Boehler}
159*a1a3b679SAndreas Boehler
160*a1a3b679SAndreas Boehler/**
161*a1a3b679SAndreas Boehler * Parses a URI and returns its individual components.
162*a1a3b679SAndreas Boehler *
163*a1a3b679SAndreas Boehler * This method largely behaves the same as PHP's parse_url, except that it will
164*a1a3b679SAndreas Boehler * return an array with all the array keys, including the ones that are not
165*a1a3b679SAndreas Boehler * set by parse_url, which makes it a bit easier to work with.
166*a1a3b679SAndreas Boehler *
167*a1a3b679SAndreas Boehler * @param string $uri
168*a1a3b679SAndreas Boehler * @return array
169*a1a3b679SAndreas Boehler */
170*a1a3b679SAndreas Boehlerfunction parse($uri) {
171*a1a3b679SAndreas Boehler
172*a1a3b679SAndreas Boehler    return
173*a1a3b679SAndreas Boehler        parse_url($uri) + [
174*a1a3b679SAndreas Boehler            'scheme'   => null,
175*a1a3b679SAndreas Boehler            'host'     => null,
176*a1a3b679SAndreas Boehler            'path'     => null,
177*a1a3b679SAndreas Boehler            'port'     => null,
178*a1a3b679SAndreas Boehler            'user'     => null,
179*a1a3b679SAndreas Boehler            'query'    => null,
180*a1a3b679SAndreas Boehler            'fragment' => null,
181*a1a3b679SAndreas Boehler        ];
182*a1a3b679SAndreas Boehler
183*a1a3b679SAndreas Boehler}
184*a1a3b679SAndreas Boehler
185*a1a3b679SAndreas Boehler/**
186*a1a3b679SAndreas Boehler * This function takes the components returned from PHP's parse_url, and uses
187*a1a3b679SAndreas Boehler * it to generate a new uri.
188*a1a3b679SAndreas Boehler *
189*a1a3b679SAndreas Boehler * @param array $parts
190*a1a3b679SAndreas Boehler * @return string
191*a1a3b679SAndreas Boehler */
192*a1a3b679SAndreas Boehlerfunction build(array $parts) {
193*a1a3b679SAndreas Boehler
194*a1a3b679SAndreas Boehler    $uri = '';
195*a1a3b679SAndreas Boehler
196*a1a3b679SAndreas Boehler    $authority = '';
197*a1a3b679SAndreas Boehler    if (!empty($parts['host'])) {
198*a1a3b679SAndreas Boehler        $authority = $parts['host'];
199*a1a3b679SAndreas Boehler        if (!empty($parts['user'])) {
200*a1a3b679SAndreas Boehler            $authority = $parts['user'] . '@' . $authority;
201*a1a3b679SAndreas Boehler        }
202*a1a3b679SAndreas Boehler        if (!empty($parts['port'])) {
203*a1a3b679SAndreas Boehler            $authority = $authority . ':' . $parts['port'];
204*a1a3b679SAndreas Boehler        }
205*a1a3b679SAndreas Boehler    }
206*a1a3b679SAndreas Boehler
207*a1a3b679SAndreas Boehler    if (!empty($parts['scheme'])) {
208*a1a3b679SAndreas Boehler        // If there's a scheme, there's also a host.
209*a1a3b679SAndreas Boehler        $uri = $parts['scheme'] . ':';
210*a1a3b679SAndreas Boehler
211*a1a3b679SAndreas Boehler    }
212*a1a3b679SAndreas Boehler    if ($authority) {
213*a1a3b679SAndreas Boehler        // No scheme, but there is a host.
214*a1a3b679SAndreas Boehler        $uri .= '//' . $authority;
215*a1a3b679SAndreas Boehler
216*a1a3b679SAndreas Boehler    }
217*a1a3b679SAndreas Boehler
218*a1a3b679SAndreas Boehler    if (!empty($parts['path'])) {
219*a1a3b679SAndreas Boehler        $uri .= $parts['path'];
220*a1a3b679SAndreas Boehler    }
221*a1a3b679SAndreas Boehler    if (!empty($parts['query'])) {
222*a1a3b679SAndreas Boehler        $uri .= '?' . $parts['query'];
223*a1a3b679SAndreas Boehler    }
224*a1a3b679SAndreas Boehler    if (!empty($parts['fragment'])) {
225*a1a3b679SAndreas Boehler        $uri .= '#' . $parts['fragment'];
226*a1a3b679SAndreas Boehler    }
227*a1a3b679SAndreas Boehler
228*a1a3b679SAndreas Boehler    return $uri;
229*a1a3b679SAndreas Boehler
230*a1a3b679SAndreas Boehler}
231*a1a3b679SAndreas Boehler
232*a1a3b679SAndreas Boehler/**
233*a1a3b679SAndreas Boehler * Returns the 'dirname' and 'basename' for a path.
234*a1a3b679SAndreas Boehler *
235*a1a3b679SAndreas Boehler * The reason there is a custom function for this purpose, is because
236*a1a3b679SAndreas Boehler * basename() is locale aware (behaviour changes if C locale or a UTF-8 locale
237*a1a3b679SAndreas Boehler * is used) and we need a method that just operates on UTF-8 characters.
238*a1a3b679SAndreas Boehler *
239*a1a3b679SAndreas Boehler * In addition basename and dirname are platform aware, and will treat
240*a1a3b679SAndreas Boehler * backslash (\) as a directory separator on windows.
241*a1a3b679SAndreas Boehler *
242*a1a3b679SAndreas Boehler * This method returns the 2 components as an array.
243*a1a3b679SAndreas Boehler *
244*a1a3b679SAndreas Boehler * If there is no dirname, it will return an empty string. Any / appearing at
245*a1a3b679SAndreas Boehler * the end of the string is stripped off.
246*a1a3b679SAndreas Boehler *
247*a1a3b679SAndreas Boehler * @param string $path
248*a1a3b679SAndreas Boehler * @return array
249*a1a3b679SAndreas Boehler */
250*a1a3b679SAndreas Boehlerfunction split($path) {
251*a1a3b679SAndreas Boehler
252*a1a3b679SAndreas Boehler    $matches = [];
253*a1a3b679SAndreas Boehler    if(preg_match('/^(?:(?:(.*)(?:\/+))?([^\/]+))(?:\/?)$/u', $path, $matches)) {
254*a1a3b679SAndreas Boehler        return [$matches[1], $matches[2]];
255*a1a3b679SAndreas Boehler    }
256*a1a3b679SAndreas Boehler    return [null,null];
257*a1a3b679SAndreas Boehler
258*a1a3b679SAndreas Boehler}
259