1<?php 2 3namespace League\Flysystem; 4 5use League\Flysystem\Util\MimeType; 6use LogicException; 7 8class Util 9{ 10 /** 11 * Get normalized pathinfo. 12 * 13 * @param string $path 14 * 15 * @return array pathinfo 16 */ 17 public static function pathinfo($path) 18 { 19 $pathinfo = compact('path'); 20 21 if ('' !== $dirname = dirname($path)) { 22 $pathinfo['dirname'] = static::normalizeDirname($dirname); 23 } 24 25 $pathinfo['basename'] = static::basename($path); 26 27 $pathinfo += pathinfo($pathinfo['basename']); 28 29 return $pathinfo + ['dirname' => '']; 30 } 31 32 /** 33 * Normalize a dirname return value. 34 * 35 * @param string $dirname 36 * 37 * @return string normalized dirname 38 */ 39 public static function normalizeDirname($dirname) 40 { 41 return $dirname === '.' ? '' : $dirname; 42 } 43 44 /** 45 * Get a normalized dirname from a path. 46 * 47 * @param string $path 48 * 49 * @return string dirname 50 */ 51 public static function dirname($path) 52 { 53 return static::normalizeDirname(dirname($path)); 54 } 55 56 /** 57 * Map result arrays. 58 * 59 * @param array $object 60 * @param array $map 61 * 62 * @return array mapped result 63 */ 64 public static function map(array $object, array $map) 65 { 66 $result = []; 67 68 foreach ($map as $from => $to) { 69 if ( ! isset($object[$from])) { 70 continue; 71 } 72 73 $result[$to] = $object[$from]; 74 } 75 76 return $result; 77 } 78 79 /** 80 * Normalize path. 81 * 82 * @param string $path 83 * 84 * @throws LogicException 85 * 86 * @return string 87 */ 88 public static function normalizePath($path) 89 { 90 return static::normalizeRelativePath($path); 91 } 92 93 /** 94 * Normalize relative directories in a path. 95 * 96 * @param string $path 97 * 98 * @throws LogicException 99 * 100 * @return string 101 */ 102 public static function normalizeRelativePath($path) 103 { 104 $path = str_replace('\\', '/', $path); 105 $path = static::removeFunkyWhiteSpace($path); 106 107 $parts = []; 108 109 foreach (explode('/', $path) as $part) { 110 switch ($part) { 111 case '': 112 case '.': 113 break; 114 115 case '..': 116 if (empty($parts)) { 117 throw new LogicException( 118 'Path is outside of the defined root, path: [' . $path . ']' 119 ); 120 } 121 array_pop($parts); 122 break; 123 124 default: 125 $parts[] = $part; 126 break; 127 } 128 } 129 130 return implode('/', $parts); 131 } 132 133 /** 134 * Removes unprintable characters and invalid unicode characters. 135 * 136 * @param string $path 137 * 138 * @return string $path 139 */ 140 protected static function removeFunkyWhiteSpace($path) 141 { 142 // We do this check in a loop, since removing invalid unicode characters 143 // can lead to new characters being created. 144 while (preg_match('#\p{C}+|^\./#u', $path)) { 145 $path = preg_replace('#\p{C}+|^\./#u', '', $path); 146 } 147 148 return $path; 149 } 150 151 /** 152 * Normalize prefix. 153 * 154 * @param string $prefix 155 * @param string $separator 156 * 157 * @return string normalized path 158 */ 159 public static function normalizePrefix($prefix, $separator) 160 { 161 return rtrim($prefix, $separator) . $separator; 162 } 163 164 /** 165 * Get content size. 166 * 167 * @param string $contents 168 * 169 * @return int content size 170 */ 171 public static function contentSize($contents) 172 { 173 return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); 174 } 175 176 /** 177 * Guess MIME Type based on the path of the file and it's content. 178 * 179 * @param string $path 180 * @param string|resource $content 181 * 182 * @return string|null MIME Type or NULL if no extension detected 183 */ 184 public static function guessMimeType($path, $content) 185 { 186 $mimeType = MimeType::detectByContent($content); 187 188 if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { 189 return $mimeType; 190 } 191 192 return MimeType::detectByFilename($path); 193 } 194 195 /** 196 * Emulate directories. 197 * 198 * @param array $listing 199 * 200 * @return array listing with emulated directories 201 */ 202 public static function emulateDirectories(array $listing) 203 { 204 $directories = []; 205 $listedDirectories = []; 206 207 foreach ($listing as $object) { 208 list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories); 209 } 210 211 $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); 212 213 foreach ($directories as $directory) { 214 $listing[] = static::pathinfo($directory) + ['type' => 'dir']; 215 } 216 217 return $listing; 218 } 219 220 /** 221 * Ensure a Config instance. 222 * 223 * @param null|array|Config $config 224 * 225 * @return Config config instance 226 * 227 * @throw LogicException 228 */ 229 public static function ensureConfig($config) 230 { 231 if ($config === null) { 232 return new Config(); 233 } 234 235 if ($config instanceof Config) { 236 return $config; 237 } 238 239 if (is_array($config)) { 240 return new Config($config); 241 } 242 243 throw new LogicException('A config should either be an array or a Flysystem\Config object.'); 244 } 245 246 /** 247 * Rewind a stream. 248 * 249 * @param resource $resource 250 */ 251 public static function rewindStream($resource) 252 { 253 if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { 254 rewind($resource); 255 } 256 } 257 258 public static function isSeekableStream($resource) 259 { 260 $metadata = stream_get_meta_data($resource); 261 262 return $metadata['seekable']; 263 } 264 265 /** 266 * Get the size of a stream. 267 * 268 * @param resource $resource 269 * 270 * @return int|null stream size 271 */ 272 public static function getStreamSize($resource) 273 { 274 $stat = fstat($resource); 275 276 if ( ! is_array($stat) || ! isset($stat['size'])) { 277 return null; 278 } 279 280 return $stat['size']; 281 } 282 283 /** 284 * Emulate the directories of a single object. 285 * 286 * @param array $object 287 * @param array $directories 288 * @param array $listedDirectories 289 * 290 * @return array 291 */ 292 protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) 293 { 294 if ($object['type'] === 'dir') { 295 $listedDirectories[] = $object['path']; 296 } 297 298 if ( ! isset($object['dirname']) || trim($object['dirname']) === '') { 299 return [$directories, $listedDirectories]; 300 } 301 302 $parent = $object['dirname']; 303 304 while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) { 305 $directories[] = $parent; 306 $parent = static::dirname($parent); 307 } 308 309 if (isset($object['type']) && $object['type'] === 'dir') { 310 $listedDirectories[] = $object['path']; 311 312 return [$directories, $listedDirectories]; 313 } 314 315 return [$directories, $listedDirectories]; 316 } 317 318 /** 319 * Returns the trailing name component of the path. 320 * 321 * @param string $path 322 * 323 * @return string 324 */ 325 private static function basename($path) 326 { 327 $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; 328 329 $path = rtrim($path, $separators); 330 331 $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); 332 333 if (DIRECTORY_SEPARATOR === '/') { 334 return $basename; 335 } 336 // @codeCoverageIgnoreStart 337 // Extra Windows path munging. This is tested via AppVeyor, but code 338 // coverage is not reported. 339 340 // Handle relative paths with drive letters. c:file.txt. 341 while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { 342 $basename = substr($basename, 2); 343 } 344 345 // Remove colon for standalone drive letter names. 346 if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { 347 $basename = rtrim($basename, ':'); 348 } 349 350 return $basename; 351 // @codeCoverageIgnoreEnd 352 } 353} 354