Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
65.14% |
71 / 109 |
|
28.57% |
2 / 7 |
CRAP | |
0.00% |
0 / 1 |
| helper | |
65.14% |
71 / 109 |
|
28.57% |
2 / 7 |
237.55 | |
0.00% |
0 / 1 |
| clean_path | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
8 | |||
| is_absolute_path | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
4 | |||
| phpbb_own_realpath | |
71.43% |
20 / 28 |
|
0.00% |
0 / 1 |
15.36 | |||
| realpath | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
6.07 | |||
| make_path_relative | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| resolve_path | |
56.90% |
33 / 58 |
|
0.00% |
0 / 1 |
107.96 | |||
| get_symfony_filesystem | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| 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 | |
| 14 | namespace phpbb\filesystem; |
| 15 | |
| 16 | use Symfony\Component\Filesystem\Filesystem as symfony_filesystem; |
| 17 | |
| 18 | class 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 = substr($link, 0, 1) . ':'; |
| 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 | } |