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\files; 15 16use phpbb\filesystem\filesystem_interface; 17use phpbb\language\language; 18use phpbb\request\request_interface; 19 20/** 21 * File upload class 22 * Init class (all parameters optional and able to be set/overwritten separately) - scope is global and valid for all uploads 23 */ 24class upload 25{ 26 /** @var array Allowed file extensions */ 27 public $allowed_extensions = array(); 28 29 /** @var array Disallowed content */ 30 protected $disallowed_content = array('body', 'head', 'html', 'img', 'plaintext', 'a href', 'pre', 'script', 'table', 'title'); 31 32 /** @var int Maximum filesize */ 33 public $max_filesize = 0; 34 35 /** @var int Minimum width of images */ 36 public $min_width = 0; 37 38 /** @var int Minimum height of images */ 39 public $min_height = 0; 40 41 /** @var int Maximum width of images */ 42 public $max_width = 0; 43 44 /** @var int Maximum height of images */ 45 public $max_height = 0; 46 47 /** @var string Prefix for language variables of errors */ 48 public $error_prefix = ''; 49 50 /** @var int Timeout for remote upload */ 51 public $upload_timeout = 6; 52 53 /** @var filesystem_interface */ 54 protected $filesystem; 55 56 /** @var \phpbb\files\factory Files factory */ 57 protected $factory; 58 59 /** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper */ 60 protected $php_ini; 61 62 /** @var language Language class */ 63 protected $language; 64 65 /** @var request_interface Request class */ 66 protected $request; 67 68 /** 69 * Init file upload class. 70 * 71 * @param filesystem_interface $filesystem 72 * @param factory $factory Files factory 73 * @param language $language Language class 74 * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper 75 * @param request_interface $request Request class 76 */ 77 public function __construct(filesystem_interface $filesystem, factory $factory, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, request_interface $request) 78 { 79 $this->filesystem = $filesystem; 80 $this->factory = $factory; 81 $this->language = $language; 82 $this->php_ini = $php_ini; 83 $this->request = $request; 84 } 85 86 /** 87 * Reset vars 88 */ 89 public function reset_vars() 90 { 91 $this->max_filesize = 0; 92 $this->min_width = $this->min_height = $this->max_width = $this->max_height = 0; 93 $this->error_prefix = ''; 94 $this->allowed_extensions = array(); 95 $this->disallowed_content = array(); 96 } 97 98 /** 99 * Set allowed extensions 100 * 101 * @param array $allowed_extensions Allowed file extensions 102 * 103 * @return \phpbb\files\upload This instance of upload 104 */ 105 public function set_allowed_extensions($allowed_extensions) 106 { 107 if ($allowed_extensions !== false && is_array($allowed_extensions)) 108 { 109 $this->allowed_extensions = $allowed_extensions; 110 } 111 112 return $this; 113 } 114 115 /** 116 * Set allowed dimensions 117 * 118 * @param int $min_width Minimum image width 119 * @param int $min_height Minimum image height 120 * @param int $max_width Maximum image width 121 * @param int $max_height Maximum image height 122 * 123 * @return \phpbb\files\upload This instance of upload 124 */ 125 public function set_allowed_dimensions($min_width, $min_height, $max_width, $max_height) 126 { 127 $this->min_width = (int) $min_width; 128 $this->min_height = (int) $min_height; 129 $this->max_width = (int) $max_width; 130 $this->max_height = (int) $max_height; 131 132 return $this; 133 } 134 135 /** 136 * Set maximum allowed file size 137 * 138 * @param int $max_filesize Maximum file size 139 * 140 * @return \phpbb\files\upload This instance of upload 141 */ 142 public function set_max_filesize($max_filesize) 143 { 144 if ($max_filesize !== false && (int) $max_filesize) 145 { 146 $this->max_filesize = (int) $max_filesize; 147 } 148 149 return $this; 150 } 151 152 /** 153 * Set disallowed strings 154 * 155 * @param array $disallowed_content Disallowed content 156 * 157 * @return \phpbb\files\upload This instance of upload 158 */ 159 public function set_disallowed_content($disallowed_content) 160 { 161 if ($disallowed_content !== false && is_array($disallowed_content)) 162 { 163 $this->disallowed_content = array_diff($disallowed_content, array('')); 164 } 165 166 return $this; 167 } 168 169 /** 170 * Set error prefix 171 * 172 * @param string $error_prefix Prefix for language variables of errors 173 * 174 * @return \phpbb\files\upload This instance of upload 175 */ 176 public function set_error_prefix($error_prefix) 177 { 178 $this->error_prefix = $error_prefix; 179 180 return $this; 181 } 182 183 /** 184 * Handle upload based on type 185 * 186 * @param string $type Upload type 187 * 188 * @return \phpbb\files\filespec|bool A filespec instance if upload was 189 * successful, false if there were issues or the type is not supported 190 */ 191 public function handle_upload($type) 192 { 193 $args = func_get_args(); 194 array_shift($args); 195 $type_class = $this->factory->get($type) 196 ->set_upload($this); 197 198 return (is_object($type_class)) ? call_user_func_array(array($type_class, 'upload'), $args) : false; 199 } 200 201 /** 202 * Assign internal error 203 * 204 * @param string $errorcode Error code to assign 205 * 206 * @return string Error string 207 * @access public 208 */ 209 public function assign_internal_error($errorcode) 210 { 211 switch ($errorcode) 212 { 213 case UPLOAD_ERR_INI_SIZE: 214 $max_filesize = $this->php_ini->getString('upload_max_filesize'); 215 $unit = 'MB'; 216 217 if (!empty($max_filesize)) 218 { 219 $unit = strtolower(substr($max_filesize, -1, 1)); 220 $max_filesize = (int) $max_filesize; 221 222 $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); 223 } 224 225 $error = (empty($max_filesize)) ? $this->language->lang($this->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit)); 226 break; 227 228 case UPLOAD_ERR_FORM_SIZE: 229 $max_filesize = get_formatted_filesize($this->max_filesize, false); 230 231 $error = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); 232 break; 233 234 case UPLOAD_ERR_PARTIAL: 235 $error = $this->language->lang($this->error_prefix . 'PARTIAL_UPLOAD'); 236 break; 237 238 case UPLOAD_ERR_NO_FILE: 239 $error = $this->language->lang($this->error_prefix . 'NOT_UPLOADED'); 240 break; 241 242 case UPLOAD_ERR_NO_TMP_DIR: 243 case UPLOAD_ERR_CANT_WRITE: 244 $error = $this->language->lang($this->error_prefix . 'NO_TEMP_DIR'); 245 break; 246 247 case UPLOAD_ERR_EXTENSION: 248 $error = $this->language->lang($this->error_prefix . 'PHP_UPLOAD_STOPPED'); 249 break; 250 251 default: 252 $error = false; 253 break; 254 } 255 256 return $error; 257 } 258 259 /** 260 * Perform common file checks 261 * 262 * @param filespec $file Instance of filespec class 263 */ 264 public function common_checks($file) 265 { 266 // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form 267 if ($this->max_filesize && ($file->get('filesize') > $this->max_filesize || $file->get('filesize') == 0)) 268 { 269 $max_filesize = get_formatted_filesize($this->max_filesize, false); 270 271 $file->error[] = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); 272 } 273 274 // check Filename 275 if (preg_match("#[\\/:*?\"<>|]#i", $file->get('realname'))) 276 { 277 $file->error[] = $this->language->lang($this->error_prefix . 'INVALID_FILENAME', $file->get('realname')); 278 } 279 280 // Invalid Extension 281 if (!$this->valid_extension($file)) 282 { 283 $file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_EXTENSION', $file->get('extension')); 284 } 285 286 // MIME Sniffing 287 if (!$this->valid_content($file)) 288 { 289 $file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_CONTENT'); 290 } 291 } 292 293 /** 294 * Check for allowed extension 295 * 296 * @param filespec $file Instance of filespec class 297 * 298 * @return bool True if extension is allowed, false if not 299 */ 300 public function valid_extension($file) 301 { 302 return (in_array($file->get('extension'), $this->allowed_extensions)) ? true : false; 303 } 304 305 /** 306 * Check for allowed dimension 307 * 308 * @param filespec $file Instance of filespec class 309 * 310 * @return bool True if dimensions are valid or no constraints set, false 311 * if not 312 */ 313 public function valid_dimensions($file) 314 { 315 if (!$this->max_width && !$this->max_height && !$this->min_width && !$this->min_height) 316 { 317 return true; 318 } 319 320 if (($file->get('width') > $this->max_width && $this->max_width) || 321 ($file->get('height') > $this->max_height && $this->max_height) || 322 ($file->get('width') < $this->min_width && $this->min_width) || 323 ($file->get('height') < $this->min_height && $this->min_height)) 324 { 325 return false; 326 } 327 328 return true; 329 } 330 331 /** 332 * Check if form upload is valid 333 * 334 * @param string $form_name Name of form 335 * 336 * @return bool True if form upload is valid, false if not 337 */ 338 public function is_valid($form_name) 339 { 340 $upload = $this->request->file($form_name); 341 342 return (!empty($upload) && $upload['name'] !== 'none'); 343 } 344 345 346 /** 347 * Check for bad content (IE mime-sniffing) 348 * 349 * @param filespec $file Instance of filespec class 350 * 351 * @return bool True if content is valid, false if not 352 */ 353 public function valid_content($file) 354 { 355 return ($file->check_content($this->disallowed_content)); 356 } 357 358 /** 359 * Get image type/extension mapping 360 * 361 * @return array Array containing the image types and their extensions 362 */ 363 static public function image_types() 364 { 365 $result = [ 366 IMAGETYPE_GIF => ['gif'], 367 IMAGETYPE_JPEG => ['jpg', 'jpeg'], 368 IMAGETYPE_PNG => ['png'], 369 IMAGETYPE_SWF => ['swf'], 370 IMAGETYPE_PSD => ['psd'], 371 IMAGETYPE_BMP => ['bmp'], 372 IMAGETYPE_TIFF_II => ['tif', 'tiff'], 373 IMAGETYPE_TIFF_MM => ['tif', 'tiff'], 374 IMAGETYPE_JPC => ['jpg', 'jpeg'], 375 IMAGETYPE_JP2 => ['jpg', 'jpeg'], 376 IMAGETYPE_JPX => ['jpg', 'jpeg'], 377 IMAGETYPE_JB2 => ['jpg', 'jpeg'], 378 IMAGETYPE_IFF => ['iff'], 379 IMAGETYPE_WBMP => ['wbmp'], 380 IMAGETYPE_XBM => ['xbm'], 381 IMAGETYPE_WEBP => ['webp'], 382 ]; 383 384 if (defined('IMAGETYPE_SWC')) 385 { 386 $result[IMAGETYPE_SWC] = ['swc']; 387 } 388 389 return $result; 390 } 391} 392