Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
67.89% covered (warning)
67.89%
74 / 109
42.86% covered (danger)
42.86%
3 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
helper
67.89% covered (warning)
67.89%
74 / 109
42.86% covered (danger)
42.86%
3 / 7
199.61
0.00% covered (danger)
0.00%
0 / 1
 clean_path
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
8
 is_absolute_path
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
4
 phpbb_own_realpath
71.43% covered (warning)
71.43%
20 / 28
0.00% covered (danger)
0.00%
0 / 1
15.36
 realpath
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
6.07
 make_path_relative
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolve_path
56.90% covered (warning)
56.90%
33 / 58
0.00% covered (danger)
0.00%
0 / 1
107.96
 get_symfony_filesystem
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2/**
3 *
4 * This file is part of the phpBB Forum Software package.
5 *
6 * @copyright (c) phpBB Limited <https://www.phpbb.com>
7 * @license GNU General Public License, version 2 (GPL-2.0)
8 *
9 * For full copyright and license information, please see
10 * the docs/CREDITS.txt file.
11 *
12 */
13
14namespace phpbb\filesystem;
15
16use Symfony\Component\Filesystem\Filesystem as symfony_filesystem;
17
18class helper
19{
20    /**
21    * @var symfony_filesystem
22    */
23    protected static $symfony_filesystem;
24
25    /**
26     * Eliminates useless . and .. components from specified path.
27     *
28     * @param string $path Path to clean
29     *
30     * @return string Cleaned path
31     */
32    public static function clean_path($path)
33    {
34        $exploded = explode('/', $path);
35        $filtered = array();
36        foreach ($exploded as $part)
37        {
38            if ($part === '.' && !empty($filtered))
39            {
40                continue;
41            }
42
43            if ($part === '..' && !empty($filtered) && $filtered[count($filtered) - 1] !== '.' && $filtered[count($filtered) - 1] !== '..')
44            {
45                array_pop($filtered);
46            }
47            else
48            {
49                $filtered[] = $part;
50            }
51        }
52        $path = implode('/', $filtered);
53        return $path;
54    }
55
56    /**
57     * Checks if a path is absolute or not
58     *
59     * @param string    $path    Path to check
60     *
61     * @return    bool    true if the path is absolute, false otherwise
62     */
63    public static function is_absolute_path($path)
64    {
65        return (isset($path[0]) && $path[0] === '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false;
66    }
67
68    /**
69     * Try to resolve real path when PHP's realpath failes to do so
70     *
71     * @param string    $path
72     * @return string|false
73     */
74    protected static function phpbb_own_realpath($path)
75    {
76        // Replace all directory separators with '/'
77        $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
78
79        $is_absolute_path = false;
80        $path_prefix = '';
81
82        if (self::is_absolute_path($path))
83        {
84            $is_absolute_path = true;
85        }
86        else
87        {
88            if (function_exists('getcwd'))
89            {
90                $working_directory = str_replace(DIRECTORY_SEPARATOR, '/', getcwd());
91            }
92
93            //
94            // From this point on we really just guessing
95            // If chdir were called we screwed
96            //
97            else if (function_exists('debug_backtrace'))
98            {
99                $call_stack = debug_backtrace(0);
100                $working_directory = str_replace(DIRECTORY_SEPARATOR, '/', dirname($call_stack[max(0, count($call_stack) - 1)]['file']));
101            }
102            else
103            {
104                //
105                // Assuming that the working directory is phpBB root
106                // we could use this as a fallback, when phpBB will use controllers
107                // everywhere this will be a safe assumption
108                //
109                //$dir_parts = explode(DIRECTORY_SEPARATOR, __DIR__);
110                //$namespace_parts = explode('\\', trim(__NAMESPACE__, '\\'));
111
112                //$namespace_part_count = count($namespace_parts);
113
114                // Check if we still loading from root
115                //if (array_slice($dir_parts, -$namespace_part_count) === $namespace_parts)
116                //{
117                //    $working_directory = implode('/', array_slice($dir_parts, 0, -$namespace_part_count));
118                //}
119                //else
120                //{
121                //    $working_directory = false;
122                //}
123
124                $working_directory = false;
125            }
126
127            if ($working_directory !== false)
128            {
129                $is_absolute_path = true;
130                $path = $working_directory . '/' . $path;
131            }
132        }
133
134        if ($is_absolute_path)
135        {
136            if (defined('PHP_WINDOWS_VERSION_MAJOR'))
137            {
138                $path_prefix = $path[0] . ':';
139                $path = substr($path, 2);
140            }
141            else
142            {
143                $path_prefix = '';
144            }
145        }
146
147        $resolved_path = self::resolve_path($path, $path_prefix, $is_absolute_path);
148        if ($resolved_path === false)
149        {
150            return false;
151        }
152
153        if (!@file_exists($resolved_path) || (!@is_dir($resolved_path . '/') && !is_file($resolved_path)))
154        {
155            return false;
156        }
157
158        // Return OS specific directory separators
159        $resolved = str_replace('/', DIRECTORY_SEPARATOR, (string) $resolved_path);
160
161        // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
162        if (substr($resolved, -1) === DIRECTORY_SEPARATOR)
163        {
164            return substr($resolved, 0, -1);
165        }
166
167        return $resolved;
168    }
169
170    /**
171     * A wrapper for PHP's realpath
172     *
173     * Try to resolve realpath when PHP's realpath is not available, or
174     * known to be buggy.
175     *
176     * @param string    $path    Path to resolve
177     *
178     * @return string|false    Resolved path or false if path could not be resolved
179     */
180    public static function realpath($path)
181    {
182        if (!function_exists('realpath'))
183        {
184            return self::phpbb_own_realpath($path);
185        }
186
187        $realpath = realpath($path);
188
189        // Strangely there are provider not disabling realpath but returning strange values. :o
190        // We at least try to cope with them.
191        if ((!self::is_absolute_path($path) && $realpath === $path) || $realpath === false)
192        {
193            return self::phpbb_own_realpath($path);
194        }
195
196        // Check for DIRECTORY_SEPARATOR at the end (and remove it!)
197        if (substr($realpath, -1) === DIRECTORY_SEPARATOR)
198        {
199            $realpath = substr($realpath, 0, -1);
200        }
201
202        return $realpath;
203    }
204
205    /**
206     * Given an existing path, convert it to a path relative to a given starting path
207     *
208     * @param string $end_path        Absolute path of target
209     * @param string $start_path    Absolute path where traversal begins
210     *
211     * @return string Path of target relative to starting path
212     */
213    public static function make_path_relative($end_path, $start_path)
214    {
215        return self::get_symfony_filesystem()->makePathRelative($end_path, $start_path);
216    }
217
218    /**
219     * Try to resolve symlinks in path
220     *
221     * @param string    $path            The path to resolve
222     * @param string    $prefix            The path prefix (on windows the drive letter)
223     * @param bool         $absolute        Whether or not the path is absolute
224     * @param bool        $return_array    Whether or not to return path parts
225     *
226     * @return string|array|bool    returns the resolved path or an array of parts of the path if $return_array is true
227     *                                 or false if path cannot be resolved
228     */
229    public static function resolve_path($path, $prefix = '', $absolute = false, $return_array = false)
230    {
231        if ($return_array)
232        {
233            $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
234        }
235
236        //$path = trim($path, '/'); // TODO: Check this
237        $path_parts = explode('/', $path);
238        $resolved = array();
239        $resolved_path = $prefix;
240        $file_found = false;
241
242        foreach ($path_parts as $path_part)
243        {
244            if ($file_found)
245            {
246                return false;
247            }
248
249            if (empty($path_part) || ($path_part === '.' && ($absolute || !empty($resolved))))
250            {
251                continue;
252            }
253            else if ($absolute && $path_part === '..')
254            {
255                if (empty($resolved))
256                {
257                    // No directories above root
258                    return false;
259                }
260
261                array_pop($resolved);
262                $resolved_path = false;
263            }
264            else if ($path_part === '..' && !empty($resolved) && !in_array($resolved[count($resolved) - 1], array('.', '..')))
265            {
266                array_pop($resolved);
267                $resolved_path = false;
268            }
269            else
270            {
271                if ($resolved_path === false)
272                {
273                    if (empty($resolved))
274                    {
275                        $resolved_path = ($absolute) ? $prefix . '/' . $path_part : $path_part;
276                    }
277                    else
278                    {
279                        $tmp_array = $resolved;
280                        if ($absolute)
281                        {
282                            array_unshift($tmp_array, $prefix);
283                        }
284
285                        $resolved_path = implode('/', $tmp_array);
286                    }
287                }
288
289                $current_path = $resolved_path . '/' . $path_part;
290
291                // Resolve symlinks
292                if (@is_link($current_path))
293                {
294                    if (!function_exists('readlink'))
295                    {
296                        return false;
297                    }
298
299                    $link = readlink($current_path);
300
301                    // Is link has an absolute path in it?
302                    if (self::is_absolute_path($link))
303                    {
304                        if (defined('PHP_WINDOWS_VERSION_MAJOR'))
305                        {
306                            $prefix = $link[0] . ':';
307                            $link = substr($link, 2);
308                        }
309                        else
310                        {
311                            $prefix = '';
312                        }
313
314                        $resolved = self::resolve_path($link, $prefix, true, true);
315                        $absolute = true;
316                    }
317                    else
318                    {
319                        $resolved = self::resolve_path($resolved_path . '/' . $link, $prefix, $absolute, true);
320                    }
321
322                    if (!$resolved)
323                    {
324                        return false;
325                    }
326
327                    $resolved_path = false;
328                }
329                else if (@is_dir($current_path . '/'))
330                {
331                    $resolved[] = $path_part;
332                    $resolved_path = $current_path;
333                }
334                else if (@is_file($current_path))
335                {
336                    $resolved[] = $path_part;
337                    $resolved_path = $current_path;
338                    $file_found = true;
339                }
340                else
341                {
342                    return false;
343                }
344            }
345        }
346
347        // If at the end of the path there were a .. or .
348        // we need to build the path again.
349        // Only doing this when a string is expected in return
350        if ($resolved_path === false && $return_array === false)
351        {
352            if (empty($resolved))
353            {
354                $resolved_path = ($absolute) ? $prefix . '/' : './';
355            }
356            else
357            {
358                $tmp_array = $resolved;
359                if ($absolute)
360                {
361                    array_unshift($tmp_array, $prefix);
362                }
363
364                $resolved_path = implode('/', $tmp_array);
365            }
366        }
367
368        return $return_array ? $resolved : $resolved_path;
369    }
370
371    /**
372     * Get an instance of symfony's filesystem object.
373     *
374     * @return symfony_filesystem    Symfony filesystem
375     */
376    protected static function get_symfony_filesystem()
377    {
378        if (self::$symfony_filesystem === null)
379        {
380            self::$symfony_filesystem = new symfony_filesystem();
381        }
382
383        return self::$symfony_filesystem;
384    }
385}