1<?php 2/** 3 * Joomla! Content Management System 4 * 5 * @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE.txt 7 */ 8 9namespace Joomla\CMS\Filesystem; 10 11defined('JPATH_PLATFORM') or die; 12 13/** 14 * File system helper 15 * 16 * Holds support functions for the filesystem, particularly the stream 17 * 18 * @since 1.7.0 19 */ 20class FilesystemHelper 21{ 22 /** 23 * Remote file size function for streams that don't support it 24 * 25 * @param string $url TODO Add text 26 * 27 * @return mixed 28 * 29 * @link https://www.php.net/manual/en/function.filesize.php 30 * @since 1.7.0 31 */ 32 public static function remotefsize($url) 33 { 34 $sch = parse_url($url, PHP_URL_SCHEME); 35 36 if (($sch != 'http') && ($sch != 'https') && ($sch != 'ftp') && ($sch != 'ftps')) 37 { 38 return false; 39 } 40 41 if (($sch == 'http') || ($sch == 'https')) 42 { 43 $headers = get_headers($url, 1); 44 45 if ((!array_key_exists('Content-Length', $headers))) 46 { 47 return false; 48 } 49 50 return $headers['Content-Length']; 51 } 52 53 if (($sch == 'ftp') || ($sch == 'ftps')) 54 { 55 $server = parse_url($url, PHP_URL_HOST); 56 $port = parse_url($url, PHP_URL_PORT); 57 $path = parse_url($url, PHP_URL_PATH); 58 $user = parse_url($url, PHP_URL_USER); 59 $pass = parse_url($url, PHP_URL_PASS); 60 61 if ((!$server) || (!$path)) 62 { 63 return false; 64 } 65 66 if (!$port) 67 { 68 $port = 21; 69 } 70 71 if (!$user) 72 { 73 $user = 'anonymous'; 74 } 75 76 if (!$pass) 77 { 78 $pass = ''; 79 } 80 81 switch ($sch) 82 { 83 case 'ftp': 84 $ftpid = ftp_connect($server, $port); 85 break; 86 87 case 'ftps': 88 $ftpid = ftp_ssl_connect($server, $port); 89 break; 90 } 91 92 if (!$ftpid) 93 { 94 return false; 95 } 96 97 $login = ftp_login($ftpid, $user, $pass); 98 99 if (!$login) 100 { 101 return false; 102 } 103 104 $ftpsize = ftp_size($ftpid, $path); 105 ftp_close($ftpid); 106 107 if ($ftpsize == -1) 108 { 109 return false; 110 } 111 112 return $ftpsize; 113 } 114 } 115 116 /** 117 * Quick FTP chmod 118 * 119 * @param string $url Link identifier 120 * @param integer $mode The new permissions, given as an octal value. 121 * 122 * @return mixed 123 * 124 * @link https://www.php.net/manual/en/function.ftp-chmod.php 125 * @since 1.7.0 126 */ 127 public static function ftpChmod($url, $mode) 128 { 129 $sch = parse_url($url, PHP_URL_SCHEME); 130 131 if (($sch != 'ftp') && ($sch != 'ftps')) 132 { 133 return false; 134 } 135 136 $server = parse_url($url, PHP_URL_HOST); 137 $port = parse_url($url, PHP_URL_PORT); 138 $path = parse_url($url, PHP_URL_PATH); 139 $user = parse_url($url, PHP_URL_USER); 140 $pass = parse_url($url, PHP_URL_PASS); 141 142 if ((!$server) || (!$path)) 143 { 144 return false; 145 } 146 147 if (!$port) 148 { 149 $port = 21; 150 } 151 152 if (!$user) 153 { 154 $user = 'anonymous'; 155 } 156 157 if (!$pass) 158 { 159 $pass = ''; 160 } 161 162 switch ($sch) 163 { 164 case 'ftp': 165 $ftpid = ftp_connect($server, $port); 166 break; 167 168 case 'ftps': 169 $ftpid = ftp_ssl_connect($server, $port); 170 break; 171 } 172 173 if (!$ftpid) 174 { 175 return false; 176 } 177 178 $login = ftp_login($ftpid, $user, $pass); 179 180 if (!$login) 181 { 182 return false; 183 } 184 185 $res = ftp_chmod($ftpid, $mode, $path); 186 ftp_close($ftpid); 187 188 return $res; 189 } 190 191 /** 192 * Modes that require a write operation 193 * 194 * @return array 195 * 196 * @since 1.7.0 197 */ 198 public static function getWriteModes() 199 { 200 return array('w', 'w+', 'a', 'a+', 'r+', 'x', 'x+'); 201 } 202 203 /** 204 * Stream and Filter Support Operations 205 * 206 * Returns the supported streams, in addition to direct file access 207 * Also includes Joomla! streams as well as PHP streams 208 * 209 * @return array Streams 210 * 211 * @since 1.7.0 212 */ 213 public static function getSupported() 214 { 215 // Really quite cool what php can do with arrays when you let it... 216 static $streams; 217 218 if (!$streams) 219 { 220 $streams = array_merge(stream_get_wrappers(), self::getJStreams()); 221 } 222 223 return $streams; 224 } 225 226 /** 227 * Returns a list of transports 228 * 229 * @return array 230 * 231 * @since 1.7.0 232 */ 233 public static function getTransports() 234 { 235 // Is this overkill? 236 return stream_get_transports(); 237 } 238 239 /** 240 * Returns a list of filters 241 * 242 * @return array 243 * 244 * @since 1.7.0 245 */ 246 public static function getFilters() 247 { 248 // Note: This will look like the getSupported() function with J! filters. 249 // TODO: add user space filter loading like user space stream loading 250 return stream_get_filters(); 251 } 252 253 /** 254 * Returns a list of J! streams 255 * 256 * @return array 257 * 258 * @since 1.7.0 259 */ 260 public static function getJStreams() 261 { 262 static $streams = array(); 263 264 if (!$streams) 265 { 266 $files = new \DirectoryIterator(__DIR__ . '/Streams'); 267 268 /* @type $file DirectoryIterator */ 269 foreach ($files as $file) 270 { 271 // Only load for php files. 272 if (!$file->isFile() || $file->getExtension() !== 'php') 273 { 274 continue; 275 } 276 277 $streams[] = str_replace('stream', '', strtolower($file->getBasename('.php'))); 278 } 279 } 280 281 return $streams; 282 } 283 284 /** 285 * Determine if a stream is a Joomla stream. 286 * 287 * @param string $streamname The name of a stream 288 * 289 * @return boolean True for a Joomla Stream 290 * 291 * @since 1.7.0 292 */ 293 public static function isJoomlaStream($streamname) 294 { 295 return in_array($streamname, self::getJStreams()); 296 } 297 298 /** 299 * Calculates the maximum upload file size and returns string with unit or the size in bytes 300 * 301 * Call it with JFilesystemHelper::fileUploadMaxSize(); 302 * 303 * @param bool $unitOutput This parameter determines whether the return value should be a string with a unit 304 * 305 * @return float|string The maximum upload size of files with the appropriate unit or in bytes 306 * 307 * @since 3.4 308 */ 309 public static function fileUploadMaxSize($unitOutput = true) 310 { 311 static $max_size = false; 312 static $output_type = true; 313 314 if ($max_size === false || $output_type != $unitOutput) 315 { 316 $max_size = self::parseSize(ini_get('post_max_size')); 317 $upload_max = self::parseSize(ini_get('upload_max_filesize')); 318 319 if ($upload_max > 0 && ($upload_max < $max_size || $max_size == 0)) 320 { 321 $max_size = $upload_max; 322 } 323 324 if ($unitOutput == true) 325 { 326 $max_size = self::parseSizeUnit($max_size); 327 } 328 329 $output_type = $unitOutput; 330 } 331 332 return $max_size; 333 } 334 335 /** 336 * Returns the size in bytes without the unit for the comparison 337 * 338 * @param string $size The size which is received from the PHP settings 339 * 340 * @return float The size in bytes without the unit 341 * 342 * @since 3.4 343 */ 344 private static function parseSize($size) 345 { 346 $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); 347 $size = preg_replace('/[^0-9\.]/', '', $size); 348 349 $return = round($size); 350 351 if ($unit) 352 { 353 $return = round($size * pow(1024, stripos('bkmgtpezy', $unit[0]))); 354 } 355 356 return $return; 357 } 358 359 /** 360 * Creates the rounded size of the size with the appropriate unit 361 * 362 * @param float $maxSize The maximum size which is allowed for the uploads 363 * 364 * @return string String with the size and the appropriate unit 365 * 366 * @since 3.4 367 */ 368 private static function parseSizeUnit($maxSize) 369 { 370 $base = log($maxSize) / log(1024); 371 $suffixes = array('', 'k', 'M', 'G', 'T'); 372 373 return round(pow(1024, $base - floor($base)), 0) . $suffixes[floor($base)]; 374 } 375} 376