diff options
Diffstat (limited to 'phpBB/phpbb/files')
| -rw-r--r-- | phpBB/phpbb/files/factory.php | 58 | ||||
| -rw-r--r-- | phpBB/phpbb/files/filespec.php | 584 | ||||
| -rw-r--r-- | phpBB/phpbb/files/types/base.php | 65 | ||||
| -rw-r--r-- | phpBB/phpbb/files/types/form.php | 138 | ||||
| -rw-r--r-- | phpBB/phpbb/files/types/local.php | 136 | ||||
| -rw-r--r-- | phpBB/phpbb/files/types/remote.php | 260 | ||||
| -rw-r--r-- | phpBB/phpbb/files/types/type_interface.php | 38 | ||||
| -rw-r--r-- | phpBB/phpbb/files/upload.php | 390 | 
8 files changed, 1669 insertions, 0 deletions
| diff --git a/phpBB/phpbb/files/factory.php b/phpBB/phpbb/files/factory.php new file mode 100644 index 0000000000..84b7cc9449 --- /dev/null +++ b/phpBB/phpbb/files/factory.php @@ -0,0 +1,58 @@ +<?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; + +class factory +{ +	/** +	 * @var \Symfony\Component\DependencyInjection\ContainerInterface +	 */ +	private $container; + +	/** +	 * Constructor +	 * +	 * @param \Symfony\Component\DependencyInjection\ContainerInterface $container +	 */ +	public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) +	{ +		$this->container = $container; +	} + +	/** +	 * Get files service +	 * +	 * @param string $name Service name +	 * +	 * @return object|bool Requested service or false if service could not be +	 *				found by the container +	 */ +	public function get($name) +	{ +		$service = false; + +		$name = (strpos($name, '.') === false) ? 'files.' . $name : $name; + +		try +		{ +			$service = $this->container->get($name); +		} +		catch (\Exception $e) +		{ +			// do nothing +		} + +		return $service; +	} +} diff --git a/phpBB/phpbb/files/filespec.php b/phpBB/phpbb/files/filespec.php new file mode 100644 index 0000000000..2ff2a92c83 --- /dev/null +++ b/phpBB/phpbb/files/filespec.php @@ -0,0 +1,584 @@ +<?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) || !sizeof($upload_ary)) +		{ +			return $this; +		} + +		$this->class_initialized = true; +		$this->filename = $upload_ary['tmp_name']; +		$this->filesize = $upload_ary['size']; +		$name = (STRIP) ? stripslashes($upload_ary['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 (sizeof($this->error)) +		{ +			return false; +		} + +		$chmod = ($chmod === false) ? CHMOD_READ | 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 (sizeof($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; +	} +} diff --git a/phpBB/phpbb/files/types/base.php b/phpBB/phpbb/files/types/base.php new file mode 100644 index 0000000000..3313ad040b --- /dev/null +++ b/phpBB/phpbb/files/types/base.php @@ -0,0 +1,65 @@ +<?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\types; + +abstract class base implements type_interface +{ +	/** @var \phpbb\language\language */ +	protected $language; + +	/** @var \bantu\IniGetWrapper\IniGetWrapper */ +	protected $php_ini; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** +	 * Check if upload exceeds maximum file size +	 * +	 * @param \phpbb\files\filespec $file Filespec object +	 * +	 * @return \phpbb\files\filespec Returns same filespec instance +	 */ +	public function check_upload_size($file) +	{ +		// PHP Upload filesize exceeded +		if ($file->get('filename') == 'none') +		{ +			$max_filesize = $this->php_ini->getString('upload_max_filesize'); +			$unit = 'MB'; + +			if (!empty($max_filesize)) +			{ +				$unit = strtolower(substr($max_filesize, -1, 1)); +				$max_filesize = (int) $max_filesize; + +				$unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); +			} + +			$file->error[] = (empty($max_filesize)) ? $this->language->lang($this->upload->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->upload->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit)); +		} + +		return $file; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function set_upload(\phpbb\files\upload $upload) +	{ +		$this->upload = $upload; + +		return $this; +	} +} diff --git a/phpBB/phpbb/files/types/form.php b/phpBB/phpbb/files/types/form.php new file mode 100644 index 0000000000..832f090c47 --- /dev/null +++ b/phpBB/phpbb/files/types/form.php @@ -0,0 +1,138 @@ +<?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\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\plupload\plupload; +use phpbb\request\request_interface; + +class form extends base +{ +	/** @var factory Files factory */ +	protected $factory; + +	/** @var language */ +	protected $language; + +	/** @var IniGetWrapper */ +	protected $php_ini; + +	/** @var plupload */ +	protected $plupload; + +	/** @var request_interface */ +	protected $request; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** +	 * Construct a form upload type +	 * +	 * @param factory			$factory	Files factory +	 * @param language			$language	Language class +	 * @param IniGetWrapper		$php_ini	ini_get() wrapper +	 * @param plupload			$plupload	Plupload +	 * @param request_interface	$request	Request object +	 */ +	public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, plupload $plupload, request_interface $request) +	{ +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->plupload = $plupload; +		$this->request = $request; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function upload() +	{ +		$args = func_get_args(); +		return $this->form_upload($args[0]); +	} + +	/** +	 * Form upload method +	 * Upload file from users harddisk +	 * +	 * @param string $form_name Form name assigned to the file input field (if it is an array, the key has to be specified) +	 * +	 * @return filespec $file Object "filespec" is returned, all further operations can be done with this object +	 * @access public +	 */ +	protected function form_upload($form_name) +	{ +		$upload = $this->request->file($form_name); +		unset($upload['local_mode']); + +		$result = $this->plupload->handle_upload($form_name); +		if (is_array($result)) +		{ +			$upload = array_merge($upload, $result); +		} + +		/** @var filespec $file */ +		$file = $this->factory->get('filespec') +			->set_upload_ary($upload) +			->set_upload_namespace($this->upload); + +		if ($file->init_error()) +		{ +			$file->error[] = ''; +			return $file; +		} + +		// Error array filled? +		if (isset($upload['error'])) +		{ +			$error = $this->upload->assign_internal_error($upload['error']); + +			if ($error !== false) +			{ +				$file->error[] = $error; +				return $file; +			} +		} + +		// Check if empty file got uploaded (not catched by is_uploaded_file) +		if (isset($upload['size']) && $upload['size'] == 0) +		{ +			$file->error[] = $this->language->lang($this->upload->error_prefix . 'EMPTY_FILEUPLOAD'); +			return $file; +		} + +		// PHP Upload file size check +		$file = $this->check_upload_size($file); +		if (sizeof($file->error)) +		{ +			return $file; +		} + +		// Not correctly uploaded +		if (!$file->is_uploaded()) +		{ +			$file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'); +			return $file; +		} + +		$this->upload->common_checks($file); + +		return $file; +	} +} diff --git a/phpBB/phpbb/files/types/local.php b/phpBB/phpbb/files/types/local.php new file mode 100644 index 0000000000..7e9210b196 --- /dev/null +++ b/phpBB/phpbb/files/types/local.php @@ -0,0 +1,136 @@ +<?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\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\request\request_interface; + +class local extends base +{ +	/** @var factory Files factory */ +	protected $factory; + +	/** @var language */ +	protected $language; + +	/** @var IniGetWrapper */ +	protected $php_ini; + +	/** @var request_interface */ +	protected $request; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** +	 * Construct a form upload type +	 * +	 * @param factory $factory Files factory +	 * @param language $language Language class +	 * @param IniGetWrapper $php_ini ini_get() wrapper +	 * @param request_interface $request Request object +	 */ +	public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request) +	{ +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->request = $request; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function upload() +	{ +		$args = func_get_args(); +		return $this->local_upload($args[0], isset($args[1]) ? $args[1] : false); +	} + +	/** +	 * Move file from another location to phpBB +	 * +	 * @param string $source_file Filename of source file +	 * @param array|bool $filedata Array with filedata or false +	 * +	 * @return filespec Object "filespec" is returned, all further operations can be done with this object +	 */ +	protected function local_upload($source_file, $filedata = false) +	{ +		$upload = $this->get_upload_ary($source_file, $filedata); + +		/** @var filespec $file */ +		$file = $this->factory->get('filespec') +			->set_upload_ary($upload) +			->set_upload_namespace($this->upload); + +		if ($file->init_error()) +		{ +			$file->error[] = ''; +			return $file; +		} + +		// PHP Upload file size check +		$file = $this->check_upload_size($file); +		if (sizeof($file->error)) +		{ +			return $file; +		} + +		// Not correctly uploaded +		if (!$file->is_uploaded()) +		{ +			$file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'); +			return $file; +		} + +		$this->upload->common_checks($file); +		$this->request->overwrite('local', $upload, request_interface::FILES); + +		return $file; +	} + +	/** +	 * Retrieve upload array +	 * +	 * @param string $source_file Source file name +	 * @param array $filedata File data array +	 * +	 * @return array Upload array +	 */ +	protected function get_upload_ary($source_file, $filedata) +	{ +		$upload = array(); + +		$upload['local_mode'] = true; +		$upload['tmp_name'] = $source_file; + +		if ($filedata === false) +		{ +			$upload['name'] = utf8_basename($source_file); +			$upload['size'] = 0; +		} +		else +		{ +			$upload['name'] = $filedata['realname']; +			$upload['size'] = $filedata['size']; +			$upload['type'] = $filedata['type']; +		} + +		return $upload; +	} +} diff --git a/phpBB/phpbb/files/types/remote.php b/phpBB/phpbb/files/types/remote.php new file mode 100644 index 0000000000..44feab0ece --- /dev/null +++ b/phpBB/phpbb/files/types/remote.php @@ -0,0 +1,260 @@ +<?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\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\request\request_interface; + +class remote extends base +{ +	/** @var factory Files factory */ +	protected $factory; + +	/** @var language */ +	protected $language; + +	/** @var IniGetWrapper */ +	protected $php_ini; + +	/** @var request_interface */ +	protected $request; + +	/** @var \phpbb\files\upload */ +	protected $upload; + +	/** @var string phpBB root path */ +	protected $phpbb_root_path; + +	/** +	 * Construct a form upload type +	 * +	 * @param factory $factory Files factory +	 * @param language $language Language class +	 * @param IniGetWrapper $php_ini ini_get() wrapper +	 * @param request_interface $request Request object +	 * @param string $phpbb_root_path phpBB root path +	 */ +	public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request, $phpbb_root_path) +	{ +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->request = $request; +		$this->phpbb_root_path = $phpbb_root_path; +	} + +	/** +	 * {@inheritdoc} +	 */ +	public function upload() +	{ +		$args = func_get_args(); +		return $this->remote_upload($args[0]); +	} + +	/** +	 * Remote upload method +	 * Uploads file from given url +	 * +	 * @param string $upload_url URL pointing to file to upload, for example http://www.foobar.com/example.gif +	 * @return filespec $file Object "filespec" is returned, all further operations can be done with this object +	 * @access public +	 */ +	protected function remote_upload($upload_url) +	{ +		$upload_ary = array(); +		$upload_ary['local_mode'] = true; + +		if (!preg_match('#^(https?://).*?\.(' . implode('|', $this->upload->allowed_extensions) . ')$#i', $upload_url, $match)) +		{ +			return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'URL_INVALID')); +		} + +		$url = parse_url($upload_url); + +		$host = $url['host']; +		$path = $url['path']; +		$port = (!empty($url['port'])) ? (int) $url['port'] : 80; + +		$upload_ary['type'] = 'application/octet-stream'; + +		$url['path'] = explode('.', $url['path']); +		$ext = array_pop($url['path']); + +		$url['path'] = implode('', $url['path']); +		$upload_ary['name'] = utf8_basename($url['path']) . (($ext) ? '.' . $ext : ''); +		$filename = $url['path']; +		$filesize = 0; + +		$remote_max_filesize = $this->get_max_file_size(); + +		$errno = 0; +		$errstr = ''; + +		if (!($fsock = @fsockopen($host, $port, $errno, $errstr))) +		{ +			return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED')); +		} + +		// Make sure $path not beginning with / +		if (strpos($path, '/') === 0) +		{ +			$path = substr($path, 1); +		} + +		fputs($fsock, 'GET /' . $path . " HTTP/1.1\r\n"); +		fputs($fsock, "HOST: " . $host . "\r\n"); +		fputs($fsock, "Connection: close\r\n\r\n"); + +		// Set a proper timeout for the socket +		socket_set_timeout($fsock, $this->upload->upload_timeout); + +		$get_info = false; +		$data = ''; +		$length = false; +		$timer_stop = time() + $this->upload->upload_timeout; + +		while ((!$length || $filesize < $length) && !@feof($fsock)) +		{ +			if ($get_info) +			{ +				if ($length) +				{ +					// Don't attempt to read past end of file if server indicated length +					$block = @fread($fsock, min($length - $filesize, 1024)); +				} +				else +				{ +					$block = @fread($fsock, 1024); +				} + +				$filesize += strlen($block); + +				if ($remote_max_filesize && $filesize > $remote_max_filesize) +				{ +					$max_filesize = get_formatted_filesize($remote_max_filesize, false); + +					return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit'])); +				} + +				$data .= $block; +			} +			else +			{ +				$line = @fgets($fsock, 1024); + +				if ($line == "\r\n") +				{ +					$get_info = true; +				} +				else +				{ +					if (stripos($line, 'content-type: ') !== false) +					{ +						$upload_ary['type'] = rtrim(str_replace('content-type: ', '', strtolower($line))); +					} +					else if ($this->upload->max_filesize && stripos($line, 'content-length: ') !== false) +					{ +						$length = (int) str_replace('content-length: ', '', strtolower($line)); + +						if ($remote_max_filesize && $length && $length > $remote_max_filesize) +						{ +							$max_filesize = get_formatted_filesize($remote_max_filesize, false); + +							return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit'])); +						} +					} +					else if (stripos($line, '404 not found') !== false) +					{ +						return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'URL_NOT_FOUND'); +					} +				} +			} + +			$stream_meta_data = stream_get_meta_data($fsock); + +			// Cancel upload if we exceed timeout +			if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop) +			{ +				return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'REMOTE_UPLOAD_TIMEOUT'); +			} +		} +		@fclose($fsock); + +		if (empty($data)) +		{ +			return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'EMPTY_REMOTE_DATA'); +		} + +		$tmp_path = (!$this->php_ini->getBool('safe_mode')) ? false : $this->phpbb_root_path . 'cache'; +		$filename = tempnam($tmp_path, unique_id() . '-'); + +		if (!($fp = @fopen($filename, 'wb'))) +		{ +			return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'NOT_UPLOADED'); +		} + +		$upload_ary['size'] = fwrite($fp, $data); +		fclose($fp); +		unset($data); + +		$upload_ary['tmp_name'] = $filename; + +		/** @var filespec $file */ +		$file = $this->factory->get('filespec') +			->set_upload_ary($upload_ary) +			->set_upload_namespace($this->upload); +		$this->upload->common_checks($file); + +		return $file; +	} + +	/** +	 * Get maximum file size for remote uploads +	 * +	 * @return int Maximum file size +	 */ +	protected function get_max_file_size() +	{ +		$max_file_size = $this->upload->max_filesize; +		if (!$max_file_size) +		{ +			$max_file_size = $this->php_ini->getString('upload_max_filesize'); + +			if (!empty($max_file_size)) +			{ +				$unit = strtolower(substr($max_file_size, -1, 1)); +				$max_file_size = (int) $max_file_size; + +				switch ($unit) +				{ +					case 'g': +						$max_file_size *= 1024; +					// no break +					case 'm': +						$max_file_size *= 1024; +					// no break +					case 'k': +						$max_file_size *= 1024; +					// no break +				} +			} +		} + +		return $max_file_size; +	} +} diff --git a/phpBB/phpbb/files/types/type_interface.php b/phpBB/phpbb/files/types/type_interface.php new file mode 100644 index 0000000000..e07078349a --- /dev/null +++ b/phpBB/phpbb/files/types/type_interface.php @@ -0,0 +1,38 @@ +<?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\types; + +use phpbb\files\upload; + +interface type_interface +{ +	/** +	 * Handle upload for upload types. Arguments passed to this method will be +	 * handled by the upload type classes themselves. +	 * +	 * @return \phpbb\files\filespec|bool Filespec instance if upload is +	 *                                    successful or false if not +	 */ +	public function upload(); + +	/** +	 * Set upload instance +	 * Needs to be executed before every upload. +	 * +	 * @param upload $upload Upload instance +	 * +	 * @return type_interface Returns itself +	 */ +	public function set_upload(upload $upload); +} diff --git a/phpBB/phpbb/files/upload.php b/phpBB/phpbb/files/upload.php new file mode 100644 index 0000000000..a9bf74094d --- /dev/null +++ b/phpBB/phpbb/files/upload.php @@ -0,0 +1,390 @@ +<?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\filesystem\filesystem_interface; +use phpbb\language\language; +use phpbb\request\request_interface; + +/** + * File upload class + * Init class (all parameters optional and able to be set/overwritten separately) - scope is global and valid for all uploads + */ +class upload +{ +	/** @var array Allowed file extensions */ +	public $allowed_extensions = array(); + +	/** @var array Disallowed content */ +	protected $disallowed_content = array('body', 'head', 'html', 'img', 'plaintext', 'a href', 'pre', 'script', 'table', 'title'); + +	/** @var int Maximum filesize */ +	public $max_filesize = 0; + +	/** @var int Minimum width of images */ +	public $min_width = 0; + +	/** @var int Minimum height of images */ +	public $min_height = 0; + +	/** @var int Maximum width of images */ +	public $max_width = 0; + +	/** @var int Maximum height of images */ +	public $max_height = 0; + +	/** @var string Prefix for language variables of errors */ +	public $error_prefix = ''; + +	/** @var int Timeout for remote upload */ +	public $upload_timeout = 6; + +	/** @var filesystem_interface */ +	protected $filesystem; + +	/** @var \phpbb\files\factory Files factory */ +	protected $factory; + +	/** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper */ +	protected $php_ini; + +	/** @var \phpbb\language\language Language class */ +	protected $language; + +	/** @var request_interface Request class */ +	protected $request; + +	/** +	 * Init file upload class. +	 * +	 * @param filesystem_interface $filesystem +	 * @param factory $factory Files factory +	 * @param language $language Language class +	 * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper +	 * @param request_interface $request Request class +	 */ +	public function __construct(filesystem_interface $filesystem, factory $factory, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, request_interface $request) +	{ +		$this->filesystem = $filesystem; +		$this->factory = $factory; +		$this->language = $language; +		$this->php_ini = $php_ini; +		$this->request = $request; +	} + +	/** +	 * Reset vars +	 */ +	public function reset_vars() +	{ +		$this->max_filesize = 0; +		$this->min_width = $this->min_height = $this->max_width = $this->max_height = 0; +		$this->error_prefix = ''; +		$this->allowed_extensions = array(); +		$this->disallowed_content = array(); +	} + +	/** +	 * Set allowed extensions +	 * +	 * @param array $allowed_extensions Allowed file extensions +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_allowed_extensions($allowed_extensions) +	{ +		if ($allowed_extensions !== false && is_array($allowed_extensions)) +		{ +			$this->allowed_extensions = $allowed_extensions; +		} + +		return $this; +	} + +	/** +	 * Set allowed dimensions +	 * +	 * @param int $min_width Minimum image width +	 * @param int $min_height Minimum image height +	 * @param int $max_width Maximum image width +	 * @param int $max_height Maximum image height +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_allowed_dimensions($min_width, $min_height, $max_width, $max_height) +	{ +		$this->min_width = (int) $min_width; +		$this->min_height = (int) $min_height; +		$this->max_width = (int) $max_width; +		$this->max_height = (int) $max_height; + +		return $this; +	} + +	/** +	 * Set maximum allowed file size +	 * +	 * @param int $max_filesize Maximum file size +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_max_filesize($max_filesize) +	{ +		if ($max_filesize !== false && (int) $max_filesize) +		{ +			$this->max_filesize = (int) $max_filesize; +		} + +		return $this; +	} + +	/** +	 * Set disallowed strings +	 * +	 * @param array $disallowed_content Disallowed content +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_disallowed_content($disallowed_content) +	{ +		if ($disallowed_content !== false && is_array($disallowed_content)) +		{ +			$this->disallowed_content = array_diff($disallowed_content, array('')); +		} + +		return $this; +	} + +	/** +	 * Set error prefix +	 * +	 * @param string $error_prefix Prefix for language variables of errors +	 * +	 * @return \phpbb\files\upload This instance of upload +	 */ +	public function set_error_prefix($error_prefix) +	{ +		$this->error_prefix = $error_prefix; + +		return $this; +	} + +	/** +	 * Handle upload based on type +	 * +	 * @param string $type Upload type +	 * +	 * @return \phpbb\files\filespec|bool A filespec instance if upload was +	 *		successful, false if there were issues or the type is not supported +	 */ +	public function handle_upload($type) +	{ +		$args = func_get_args(); +		array_shift($args); +		$type_class = $this->factory->get($type) +			->set_upload($this); + +		return (is_object($type_class)) ? call_user_func_array(array($type_class, 'upload'), $args) : false; +	} + +	/** +	 * Assign internal error +	 * +	 * @param string $errorcode Error code to assign +	 * +	 * @return string Error string +	 * @access public +	 */ +	public function assign_internal_error($errorcode) +	{ +		switch ($errorcode) +		{ +			case UPLOAD_ERR_INI_SIZE: +				$max_filesize = $this->php_ini->getString('upload_max_filesize'); +				$unit = 'MB'; + +				if (!empty($max_filesize)) +				{ +					$unit = strtolower(substr($max_filesize, -1, 1)); +					$max_filesize = (int) $max_filesize; + +					$unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); +				} + +				$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)); +			break; + +			case UPLOAD_ERR_FORM_SIZE: +				$max_filesize = get_formatted_filesize($this->max_filesize, false); + +				$error = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); +			break; + +			case UPLOAD_ERR_PARTIAL: +				$error = $this->language->lang($this->error_prefix . 'PARTIAL_UPLOAD'); +			break; + +			case UPLOAD_ERR_NO_FILE: +				$error = $this->language->lang($this->error_prefix . 'NOT_UPLOADED'); +			break; + +			case UPLOAD_ERR_NO_TMP_DIR: +			case UPLOAD_ERR_CANT_WRITE: +				$error = $this->language->lang($this->error_prefix . 'NO_TEMP_DIR'); +			break; + +			case UPLOAD_ERR_EXTENSION: +				$error = $this->language->lang($this->error_prefix . 'PHP_UPLOAD_STOPPED'); +			break; + +			default: +				$error = false; +			break; +		} + +		return $error; +	} + +	/** +	 * Perform common file checks +	 * +	 * @param filespec $file Instance of filespec class +	 */ +	public function common_checks(&$file) +	{ +		// Filesize is too big or it's 0 if it was larger than the maxsize in the upload form +		if ($this->max_filesize && ($file->get('filesize') > $this->max_filesize || $file->get('filesize') == 0)) +		{ +			$max_filesize = get_formatted_filesize($this->max_filesize, false); + +			$file->error[] = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); +		} + +		// check Filename +		if (preg_match("#[\\/:*?\"<>|]#i", $file->get('realname'))) +		{ +			$file->error[] = $this->language->lang($this->error_prefix . 'INVALID_FILENAME', $file->get('realname')); +		} + +		// Invalid Extension +		if (!$this->valid_extension($file)) +		{ +			$file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_EXTENSION', $file->get('extension')); +		} + +		// MIME Sniffing +		if (!$this->valid_content($file)) +		{ +			$file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_CONTENT'); +		} +	} + +	/** +	 * Check for allowed extension +	 * +	 * @param filespec $file Instance of filespec class +	 * +	 * @return bool True if extension is allowed, false if not +	 */ +	public function valid_extension(&$file) +	{ +		return (in_array($file->get('extension'), $this->allowed_extensions)) ? true : false; +	} + +	/** +	 * Check for allowed dimension +	 * +	 * @param filespec $file Instance of filespec class +	 * +	 * @return bool True if dimensions are valid or no constraints set, false +	 *			if not +	 */ +	public function valid_dimensions(&$file) +	{ +		if (!$this->max_width && !$this->max_height && !$this->min_width && !$this->min_height) +		{ +			return true; +		} + +		if (($file->get('width') > $this->max_width && $this->max_width) || +			($file->get('height') > $this->max_height && $this->max_height) || +			($file->get('width') < $this->min_width && $this->min_width) || +			($file->get('height') < $this->min_height && $this->min_height)) +		{ +			return false; +		} + +		return true; +	} + +	/** +	 * Check if form upload is valid +	 * +	 * @param string $form_name Name of form +	 * +	 * @return bool True if form upload is valid, false if not +	 */ +	public function is_valid($form_name) +	{ +		$upload = $this->request->file($form_name); + +		return (!empty($upload) && $upload['name'] !== 'none'); +	} + + +	/** +	 * Check for bad content (IE mime-sniffing) +	 * +	 * @param filespec $file Instance of filespec class +	 * +	 * @return bool True if content is valid, false if not +	 */ +	public function valid_content(&$file) +	{ +		return ($file->check_content($this->disallowed_content)); +	} + +	/** +	 * Get image type/extension mapping +	 * +	 * @return array Array containing the image types and their extensions +	 */ +	static public function image_types() +	{ +		$result = array( +			IMAGETYPE_GIF		=> array('gif'), +			IMAGETYPE_JPEG		=> array('jpg', 'jpeg'), +			IMAGETYPE_PNG		=> array('png'), +			IMAGETYPE_SWF		=> array('swf'), +			IMAGETYPE_PSD		=> array('psd'), +			IMAGETYPE_BMP		=> array('bmp'), +			IMAGETYPE_TIFF_II	=> array('tif', 'tiff'), +			IMAGETYPE_TIFF_MM	=> array('tif', 'tiff'), +			IMAGETYPE_JPC		=> array('jpg', 'jpeg'), +			IMAGETYPE_JP2		=> array('jpg', 'jpeg'), +			IMAGETYPE_JPX		=> array('jpg', 'jpeg'), +			IMAGETYPE_JB2		=> array('jpg', 'jpeg'), +			IMAGETYPE_IFF		=> array('iff'), +			IMAGETYPE_WBMP		=> array('wbmp'), +			IMAGETYPE_XBM		=> array('xbm'), +		); + +		if (defined('IMAGETYPE_SWC')) +		{ +			$result[IMAGETYPE_SWC] = array('swc'); +		} + +		return $result; +	} +} | 
