1<?php 2namespace TYPO3\PharStreamWrapper; 3 4/* 5 * This file is part of the TYPO3 project. 6 * 7 * It is free software; you can redistribute it and/or modify it under the terms 8 * of the MIT License (MIT). For the full copyright and license information, 9 * please read the LICENSE file that was distributed with this source code. 10 * 11 * The TYPO3 project - inspiring people to share! 12 */ 13 14/** 15 * Helper provides low-level tools on file name resolving. However it does not 16 * (and should not) maintain any runtime state information. In order to resolve 17 * Phar archive paths according resolvers have to be used. 18 * 19 * @see \TYPO3\PharStreamWrapper\Resolvable::resolve() 20 */ 21class Helper 22{ 23 /* 24 * Resets PHP's OPcache if enabled as work-around for issues in `include()` 25 * or `require()` calls and OPcache delivering wrong results. 26 * 27 * @see https://bugs.php.net/bug.php?id=66569 28 */ 29 public static function resetOpCache() 30 { 31 if (function_exists('opcache_reset') 32 && function_exists('opcache_get_status') 33 ) { 34 $status = opcache_get_status(); 35 if (!empty($status['opcache_enabled'])) { 36 opcache_reset(); 37 } 38 } 39 } 40 41 /** 42 * Determines base file that can be accessed using the regular file system. 43 * For e.g. "phar:///home/user/bundle.phar/content.txt" that would result 44 * into "/home/user/bundle.phar". 45 * 46 * @param string $path 47 * @return string|null 48 */ 49 public static function determineBaseFile($path) 50 { 51 $parts = explode('/', static::normalizePath($path)); 52 53 while (count($parts)) { 54 $currentPath = implode('/', $parts); 55 if (@is_file($currentPath) && realpath($currentPath) !== false) { 56 return $currentPath; 57 } 58 array_pop($parts); 59 } 60 61 return null; 62 } 63 64 /** 65 * @param string $path 66 * @return bool 67 */ 68 public static function hasPharPrefix($path) 69 { 70 return stripos($path, 'phar://') === 0; 71 } 72 73 /** 74 * @param string $path 75 * @return string 76 */ 77 public static function removePharPrefix($path) 78 { 79 $path = trim($path); 80 if (!static::hasPharPrefix($path)) { 81 return $path; 82 } 83 return substr($path, 7); 84 } 85 86 /** 87 * Normalizes a path, removes phar:// prefix, fixes Windows directory 88 * separators. Result is without trailing slash. 89 * 90 * @param string $path 91 * @return string 92 */ 93 public static function normalizePath($path) 94 { 95 return rtrim( 96 static::normalizeWindowsPath( 97 static::removePharPrefix($path) 98 ), 99 '/' 100 ); 101 } 102 103 /** 104 * Fixes a path for windows-backslashes and reduces double-slashes to single slashes 105 * 106 * @param string $path File path to process 107 * @return string 108 */ 109 public static function normalizeWindowsPath($path) 110 { 111 return str_replace('\\', '/', $path); 112 } 113 114 /** 115 * Resolves all dots, slashes and removes spaces after or before a path... 116 * 117 * @param string $path Input string 118 * @return string Canonical path, always without trailing slash 119 */ 120 private static function getCanonicalPath($path) 121 { 122 $path = static::normalizeWindowsPath($path); 123 124 $absolutePathPrefix = ''; 125 if (static::isAbsolutePath($path)) { 126 if (static::isWindows() && strpos($path, ':/') === 1) { 127 $absolutePathPrefix = substr($path, 0, 3); 128 $path = substr($path, 3); 129 } else { 130 $path = ltrim($path, '/'); 131 $absolutePathPrefix = '/'; 132 } 133 } 134 135 $pathParts = explode('/', $path); 136 $pathPartsLength = count($pathParts); 137 for ($partCount = 0; $partCount < $pathPartsLength; $partCount++) { 138 // double-slashes in path: remove element 139 if ($pathParts[$partCount] === '') { 140 array_splice($pathParts, $partCount, 1); 141 $partCount--; 142 $pathPartsLength--; 143 } 144 // "." in path: remove element 145 if ((isset($pathParts[$partCount]) ? $pathParts[$partCount] : '') === '.') { 146 array_splice($pathParts, $partCount, 1); 147 $partCount--; 148 $pathPartsLength--; 149 } 150 // ".." in path: 151 if ((isset($pathParts[$partCount]) ? $pathParts[$partCount] : '') === '..') { 152 if ($partCount === 0) { 153 array_splice($pathParts, $partCount, 1); 154 $partCount--; 155 $pathPartsLength--; 156 } elseif ($partCount >= 1) { 157 // Rremove this and previous element 158 array_splice($pathParts, $partCount - 1, 2); 159 $partCount -= 2; 160 $pathPartsLength -= 2; 161 } elseif ($absolutePathPrefix) { 162 // can't go higher than root dir 163 // simply remove this part and continue 164 array_splice($pathParts, $partCount, 1); 165 $partCount--; 166 $pathPartsLength--; 167 } 168 } 169 } 170 171 return $absolutePathPrefix . implode('/', $pathParts); 172 } 173 174 /** 175 * Checks if the $path is absolute or relative (detecting either '/' or 176 * 'x:/' as first part of string) and returns TRUE if so. 177 * 178 * @param string $path File path to evaluate 179 * @return bool 180 */ 181 private static function isAbsolutePath($path) 182 { 183 // Path starting with a / is always absolute, on every system 184 // On Windows also a path starting with a drive letter is absolute: X:/ 185 return (isset($path[0]) ? $path[0] : null) === '/' 186 || static::isWindows() && ( 187 strpos($path, ':/') === 1 188 || strpos($path, ':\\') === 1 189 ); 190 } 191 192 /** 193 * @return bool 194 */ 195 private static function isWindows() 196 { 197 return stripos(PHP_OS, 'WIN') === 0; 198 } 199}