Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
16 / 16 |
CRAP | |
100.00% |
209 / 209 |
filespec | |
100.00% |
1 / 1 |
|
100.00% |
16 / 16 |
78 | |
100.00% |
209 / 209 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
8 / 8 |
|||
set_upload_ary | |
100.00% |
1 / 1 |
7 | |
100.00% |
21 / 21 |
|||
set_upload_namespace | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
init_error | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
set_error | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
clean_filename | |
100.00% |
1 / 1 |
7 | |
100.00% |
25 / 25 |
|||
get | |
100.00% |
1 / 1 |
3 | |
100.00% |
4 / 4 |
|||
is_image | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
is_uploaded | |
100.00% |
1 / 1 |
8 | |
100.00% |
8 / 8 |
|||
remove | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
get_extension | |
100.00% |
1 / 1 |
2 | |
100.00% |
6 / 6 |
|||
get_mimetype | |
100.00% |
1 / 1 |
3 | |
100.00% |
9 / 9 |
|||
get_filesize | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
check_content | |
100.00% |
1 / 1 |
5 | |
100.00% |
15 / 15 |
|||
move_file | |
100.00% |
1 / 1 |
29 | |
100.00% |
82 / 82 |
|||
additional_checks | |
100.00% |
1 / 1 |
6 | |
100.00% |
19 / 19 |
<?php | |
/** | |
* | |
* This file is part of the phpBB Forum Software package. | |
* | |
* @copyright (c) phpBB Limited <https://www.phpbb.com> | |
* @license GNU General Public License, version 2 (GPL-2.0) | |
* | |
* For full copyright and license information, please see | |
* the docs/CREDITS.txt file. | |
* | |
*/ | |
namespace phpbb\files; | |
use phpbb\language\language; | |
/** | |
* Responsible for holding all file relevant information, as well as doing file-specific operations. | |
* The {@link fileupload fileupload class} can be used to upload several files, each of them being this object to operate further on. | |
*/ | |
class filespec | |
{ | |
/** @var string File name */ | |
protected $filename = ''; | |
/** @var string Real name of file */ | |
protected $realname = ''; | |
/** @var string Upload name of file */ | |
protected $uploadname = ''; | |
/** @var string Mimetype of file */ | |
protected $mimetype = ''; | |
/** @var string File extension */ | |
protected $extension = ''; | |
/** @var int File size */ | |
protected $filesize = 0; | |
/** @var int Width of file */ | |
protected $width = 0; | |
/** @var int Height of file */ | |
protected $height = 0; | |
/** @var array Image info including type and size */ | |
protected $image_info = array(); | |
/** @var string Destination file name */ | |
protected $destination_file = ''; | |
/** @var string Destination file path */ | |
protected $destination_path = ''; | |
/** @var bool Whether file was moved */ | |
protected $file_moved = false; | |
/** @var bool Whether file is local */ | |
protected $local = false; | |
/** @var bool Class initialization flag */ | |
protected $class_initialized = false; | |
/** @var array Error array */ | |
public $error = array(); | |
/** @var upload Instance of upload class */ | |
public $upload; | |
/** @var \phpbb\filesystem\filesystem_interface */ | |
protected $filesystem; | |
/** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper class */ | |
protected $php_ini; | |
/** @var \FastImageSize\FastImageSize */ | |
protected $imagesize; | |
/** @var language Language class */ | |
protected $language; | |
/** @var string phpBB root path */ | |
protected $phpbb_root_path; | |
/** @var \phpbb\plupload\plupload The plupload object */ | |
protected $plupload; | |
/** @var \phpbb\mimetype\guesser phpBB Mimetype guesser */ | |
protected $mimetype_guesser; | |
/** | |
* File upload class | |
* | |
* @param \phpbb\filesystem\filesystem_interface $phpbb_filesystem Filesystem | |
* @param language $language Language | |
* @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper | |
* @param \FastImageSize\FastImageSize $imagesize Imagesize class | |
* @param string $phpbb_root_path phpBB root path | |
* @param \phpbb\mimetype\guesser $mimetype_guesser Mime type guesser | |
* @param \phpbb\plupload\plupload $plupload Plupload | |
*/ | |
public function __construct(\phpbb\filesystem\filesystem_interface $phpbb_filesystem, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \FastImageSize\FastImageSize $imagesize, $phpbb_root_path, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null) | |
{ | |
$this->filesystem = $phpbb_filesystem; | |
$this->language = $language; | |
$this->php_ini = $php_ini; | |
$this->imagesize = $imagesize; | |
$this->phpbb_root_path = $phpbb_root_path; | |
$this->plupload = $plupload; | |
$this->mimetype_guesser = $mimetype_guesser; | |
} | |
/** | |
* Set upload ary | |
* | |
* @param array $upload_ary Upload ary | |
* | |
* @return filespec This instance of the filespec class | |
*/ | |
public function set_upload_ary($upload_ary) | |
{ | |
if (!isset($upload_ary) || !count($upload_ary)) | |
{ | |
return $this; | |
} | |
$this->class_initialized = true; | |
$this->filename = $upload_ary['tmp_name']; | |
$this->filesize = $upload_ary['size']; | |
$name = $upload_ary['name']; | |
$name = trim(utf8_basename($name)); | |
$this->realname = $this->uploadname = $name; | |
$this->mimetype = $upload_ary['type']; | |
// Opera adds the name to the mime type | |
$this->mimetype = (strpos($this->mimetype, '; name') !== false) ? str_replace(strstr($this->mimetype, '; name'), '', $this->mimetype) : $this->mimetype; | |
if (!$this->mimetype) | |
{ | |
$this->mimetype = 'application/octet-stream'; | |
} | |
$this->extension = strtolower(self::get_extension($this->realname)); | |
// Try to get real filesize from temporary folder (not always working) ;) | |
$this->filesize = ($this->get_filesize($this->filename)) ?: $this->filesize; | |
$this->width = $this->height = 0; | |
$this->file_moved = false; | |
$this->local = (isset($upload_ary['local_mode'])) ? true : false; | |
return $this; | |
} | |
/** | |
* Set the upload namespace | |
* | |
* @param upload $namespace Instance of upload class | |
* | |
* @return filespec This instance of the filespec class | |
*/ | |
public function set_upload_namespace($namespace) | |
{ | |
$this->upload = $namespace; | |
return $this; | |
} | |
/** | |
* Check if class members were not properly initialised yet | |
* | |
* @return bool True if there was an init error, false if not | |
*/ | |
public function init_error() | |
{ | |
return !$this->class_initialized; | |
} | |
/** | |
* Set error in error array | |
* | |
* @param mixed $error Content for error array | |
* | |
* @return \phpbb\files\filespec This instance of the filespec class | |
*/ | |
public function set_error($error) | |
{ | |
$this->error[] = $error; | |
return $this; | |
} | |
/** | |
* Cleans destination filename | |
* | |
* @param string $mode Either real, unique, or unique_ext. Real creates a | |
* realname, filtering some characters, lowering every | |
* character. Unique creates a unique filename. | |
* @param string $prefix Prefix applied to filename | |
* @param string $user_id The user_id is only needed for when cleaning a user's avatar | |
*/ | |
public function clean_filename($mode = 'unique', $prefix = '', $user_id = '') | |
{ | |
if ($this->init_error()) | |
{ | |
return; | |
} | |
switch ($mode) | |
{ | |
case 'real': | |
// Remove every extension from filename (to not let the mime bug being exposed) | |
if (strpos($this->realname, '.') !== false) | |
{ | |
$this->realname = substr($this->realname, 0, strpos($this->realname, '.')); | |
} | |
// Replace any chars which may cause us problems with _ | |
$bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|'); | |
$this->realname = rawurlencode(str_replace($bad_chars, '_', strtolower($this->realname))); | |
$this->realname = preg_replace("/%(\w{2})/", '_', $this->realname); | |
$this->realname = $prefix . $this->realname . '.' . $this->extension; | |
break; | |
case 'unique': | |
$this->realname = $prefix . md5(unique_id()); | |
break; | |
case 'avatar': | |
$this->extension = strtolower($this->extension); | |
$this->realname = $prefix . $user_id . '.' . $this->extension; | |
break; | |
case 'unique_ext': | |
default: | |
$this->realname = $prefix . md5(unique_id()) . '.' . $this->extension; | |
} | |
} | |
/** | |
* Get property from file object | |
* | |
* @param string $property Name of property | |
* | |
* @return mixed Content of property | |
*/ | |
public function get($property) | |
{ | |
if ($this->init_error() || !isset($this->$property)) | |
{ | |
return false; | |
} | |
return $this->$property; | |
} | |
/** | |
* Check if file is an image (mime type) | |
* | |
* @return bool true if it is an image, false if not | |
*/ | |
public function is_image() | |
{ | |
return (strpos($this->mimetype, 'image/') === 0); | |
} | |
/** | |
* Check if the file got correctly uploaded | |
* | |
* @return bool true if it is a valid upload, false if not | |
*/ | |
public function is_uploaded() | |
{ | |
$is_plupload = $this->plupload && $this->plupload->is_active(); | |
if (!$this->local && !$is_plupload && !is_uploaded_file($this->filename)) | |
{ | |
return false; | |
} | |
if (($this->local || $is_plupload) && !file_exists($this->filename)) | |
{ | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Remove file | |
*/ | |
public function remove() | |
{ | |
if ($this->file_moved) | |
{ | |
@unlink($this->destination_file); | |
} | |
} | |
/** | |
* Get file extension | |
* | |
* @param string $filename Filename that needs to be checked | |
* | |
* @return string Extension of the supplied filename | |
*/ | |
static public function get_extension($filename) | |
{ | |
$filename = utf8_basename($filename); | |
if (strpos($filename, '.') === false) | |
{ | |
return ''; | |
} | |
$filename = explode('.', $filename); | |
return array_pop($filename); | |
} | |
/** | |
* Get mime type | |
* | |
* @param string $filename Filename that needs to be checked | |
* @return string Mime type of supplied filename | |
*/ | |
public function get_mimetype($filename) | |
{ | |
if ($this->mimetype_guesser !== null) | |
{ | |
$mimetype = $this->mimetype_guesser->guess($filename, $this->uploadname); | |
if ($mimetype !== 'application/octet-stream') | |
{ | |
$this->mimetype = $mimetype; | |
} | |
} | |
return $this->mimetype; | |
} | |
/** | |
* Get file size | |
* | |
* @param string $filename File name of file to check | |
* | |
* @return int File size | |
*/ | |
public function get_filesize($filename) | |
{ | |
return @filesize($filename); | |
} | |
/** | |
* Check the first 256 bytes for forbidden content | |
* | |
* @param array $disallowed_content Array containg disallowed content | |
* | |
* @return bool False if disallowed content found, true if not | |
*/ | |
public function check_content($disallowed_content) | |
{ | |
if (empty($disallowed_content)) | |
{ | |
return true; | |
} | |
$fp = @fopen($this->filename, 'rb'); | |
if ($fp !== false) | |
{ | |
$ie_mime_relevant = fread($fp, 256); | |
fclose($fp); | |
foreach ($disallowed_content as $forbidden) | |
{ | |
if (stripos($ie_mime_relevant, '<' . $forbidden) !== false) | |
{ | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
/** | |
* Move file to destination folder | |
* The phpbb_root_path variable will be applied to the destination path | |
* | |
* @param string $destination Destination path, for example $config['avatar_path'] | |
* @param bool $overwrite If set to true, an already existing file will be overwritten | |
* @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped | |
* @param string|bool $chmod Permission mask for chmodding the file after a successful move. | |
* The mode entered here reflects the mode defined by {@link phpbb_chmod()} | |
* | |
* @return bool True if file was moved, false if not | |
* @access public | |
*/ | |
public function move_file($destination, $overwrite = false, $skip_image_check = false, $chmod = false) | |
{ | |
if (count($this->error)) | |
{ | |
return false; | |
} | |
$chmod = ($chmod === false) ? \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE : $chmod; | |
// We need to trust the admin in specifying valid upload directories and an attacker not being able to overwrite it... | |
$this->destination_path = $this->phpbb_root_path . $destination; | |
// Check if the destination path exist... | |
if (!file_exists($this->destination_path)) | |
{ | |
@unlink($this->filename); | |
return false; | |
} | |
$upload_mode = ($this->php_ini->getBool('open_basedir') || $this->php_ini->getBool('safe_mode')) ? 'move' : 'copy'; | |
$upload_mode = ($this->local) ? 'local' : $upload_mode; | |
$this->destination_file = $this->destination_path . '/' . utf8_basename($this->realname); | |
// Check if the file already exist, else there is something wrong... | |
if (file_exists($this->destination_file) && !$overwrite) | |
{ | |
@unlink($this->filename); | |
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); | |
$this->file_moved = false; | |
return false; | |
} | |
else | |
{ | |
if (file_exists($this->destination_file)) | |
{ | |
@unlink($this->destination_file); | |
} | |
switch ($upload_mode) | |
{ | |
case 'copy': | |
if (!@copy($this->filename, $this->destination_file)) | |
{ | |
if (!@move_uploaded_file($this->filename, $this->destination_file)) | |
{ | |
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); | |
} | |
} | |
break; | |
case 'move': | |
if (!@move_uploaded_file($this->filename, $this->destination_file)) | |
{ | |
if (!@copy($this->filename, $this->destination_file)) | |
{ | |
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); | |
} | |
} | |
break; | |
case 'local': | |
if (!@copy($this->filename, $this->destination_file)) | |
{ | |
$this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); | |
} | |
break; | |
} | |
// Remove temporary filename | |
@unlink($this->filename); | |
if (count($this->error)) | |
{ | |
return false; | |
} | |
try | |
{ | |
$this->filesystem->phpbb_chmod($this->destination_file, $chmod); | |
} | |
catch (\phpbb\filesystem\exception\filesystem_exception $e) | |
{ | |
// Do nothing | |
} | |
} | |
// Try to get real filesize from destination folder | |
$this->filesize = ($this->get_filesize($this->destination_file)) ?: $this->filesize; | |
// Get mimetype of supplied file | |
$this->mimetype = $this->get_mimetype($this->destination_file); | |
if ($this->is_image() && !$skip_image_check) | |
{ | |
$this->width = $this->height = 0; | |
$this->image_info = $this->imagesize->getImageSize($this->destination_file, $this->mimetype); | |
if ($this->image_info !== false) | |
{ | |
$this->width = $this->image_info['width']; | |
$this->height = $this->image_info['height']; | |
// Check image type | |
$types = upload::image_types(); | |
if (!isset($types[$this->image_info['type']]) || !in_array($this->extension, $types[$this->image_info['type']])) | |
{ | |
if (!isset($types[$this->image_info['type']])) | |
{ | |
$this->error[] = $this->language->lang('IMAGE_FILETYPE_INVALID', $this->image_info['type'], $this->mimetype); | |
} | |
else | |
{ | |
$this->error[] = $this->language->lang('IMAGE_FILETYPE_MISMATCH', $types[$this->image_info['type']][0], $this->extension); | |
} | |
} | |
// Make sure the dimensions match a valid image | |
if (empty($this->width) || empty($this->height)) | |
{ | |
$this->error[] = $this->language->lang('ATTACHED_IMAGE_NOT_IMAGE'); | |
} | |
} | |
else | |
{ | |
$this->error[] = $this->language->lang('UNABLE_GET_IMAGE_SIZE'); | |
} | |
} | |
$this->file_moved = true; | |
$this->additional_checks(); | |
unset($this->upload); | |
return true; | |
} | |
/** | |
* Performing additional checks | |
* | |
* @return bool False if issue was found, true if not | |
*/ | |
public function additional_checks() | |
{ | |
if (!$this->file_moved) | |
{ | |
return false; | |
} | |
// Filesize is too big or it's 0 if it was larger than the maxsize in the upload form | |
if ($this->upload->max_filesize && ($this->get('filesize') > $this->upload->max_filesize || $this->filesize == 0)) | |
{ | |
$max_filesize = get_formatted_filesize($this->upload->max_filesize, false); | |
$this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); | |
return false; | |
} | |
if (!$this->upload->valid_dimensions($this)) | |
{ | |
$this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_SIZE', | |
$this->language->lang('PIXELS', (int) $this->upload->min_width), | |
$this->language->lang('PIXELS', (int) $this->upload->min_height), | |
$this->language->lang('PIXELS', (int) $this->upload->max_width), | |
$this->language->lang('PIXELS', (int) $this->upload->max_height), | |
$this->language->lang('PIXELS', (int) $this->width), | |
$this->language->lang('PIXELS', (int) $this->height)); | |
return false; | |
} | |
return true; | |
} | |
} |