From 2050a39da793b9ed219beed868ec86ebade423f6 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Fri, 11 Oct 2013 17:40:16 +0200 Subject: [feature/plupload/integration] Integration of Plupload This commit is a highly-refactored and up-to-date version of Fyorl's work which was part of his Google Summer of Code 2012 project "Attachment Improvements". PHPBB3-10929 --- phpBB/phpbb/plupload/plupload.php | 374 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 phpBB/phpbb/plupload/plupload.php (limited to 'phpBB/phpbb/plupload') diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php new file mode 100644 index 0000000000..6eb5adf864 --- /dev/null +++ b/phpBB/phpbb/plupload/plupload.php @@ -0,0 +1,374 @@ +phpbb_root_path = $phpbb_root_path; + $this->config = $config; + $this->request = $request; + $this->user = $user; + $this->php_ini = $php_ini; + + $this->upload_directory = $this->phpbb_root_path . $this->config['upload_path']; + $this->temporary_directory = $this->upload_directory . '/plupload'; + } + + /** + * Plupload allows for chunking so we must check for that and assemble + * the whole file first before performing any checks on it. + * + * @param string $form_name The name of the file element in the upload form + * + * @return array|null null if there are no chunks to piece together + * otherwise array containing the path to the + * pieced-together file and its size + */ + public function handle_upload($form_name) + { + $chunks_expected = $this->request->variable('chunks', 0); + + // If chunking is disabled or we are not using plupload, just return + // and handle the file as usual + if ($chunks_expected < 2) + { + return; + } + + $file_name = $this->request->variable('name', ''); + $chunk = $this->request->variable('chunk', 0); + + $this->user->add_lang('plupload'); + $this->prepare_temporary_directory(); + + $file_path = $this->temporary_filepath($file_name); + $this->integrate_uploaded_file($form_name, $chunk, $file_path); + + // If we are done with all the chunks, strip the .part suffix and then + // handle the resulting file as normal, otherwise die and await the + // next chunk. + if ($chunk == $chunks_expected - 1) + { + rename("{$file_path}.part", $file_path); + + $file_info = new \Symfony\Component\HttpFoundation\File\File($file_path); + + // Need to modify some of the $_FILES values to reflect the new file + return array( + 'tmp_name' => $file_path, + 'name' => $this->request->variable('real_filename', ''), + 'size' => filesize($file_path), + 'type' => $file_info->getMimeType($file_path), + ); + } + else + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'jsonrpc' => '2.0', + 'id' => 'id', + 'result' => null, + )); + } + } + + /** + * Fill in the plupload configuration options in the template + * + * @param \phpbb\cache\service $cache + * @param \phpbb\template\template $template + * @param string $s_action The URL to submit the POST data to + * @param int $forum_id The ID of the forum + * + * @return null + */ + public function configure(\phpbb\cache\service $cache, \phpbb\template\template $template, $s_action, $forum_id) + { + $filters = $this->generate_filter_string($cache, $forum_id); + $chunk_size = $this->get_chunk_size(); + $resize = $this->generate_resize_string(); + + $template->assign_vars(array( + 'S_RESIZE' => $resize, + 'S_PLUPLOAD' => true, + 'FILTERS' => $filters, + 'CHUNK_SIZE' => $chunk_size, + 'S_PLUPLOAD_URL' => htmlspecialchars_decode($s_action), + )); + + $this->user->add_lang('plupload'); + } + + /** + * Checks whether the page request was sent by plupload or not + * + * @return bool + */ + public function is_active() + { + return $this->request->header('X-PHPBB-USING-PLUPLOAD', false); + } + + /** + * Returns whether the current HTTP request is a multipart request. + * + * @return bool + */ + public function is_multipart() + { + $content_type = $this->request->server('CONTENT_TYPE'); + + return strpos($content_type, 'multipart') === 0; + } + + /** + * Sends an error message back to the client via JSON response + * + * @param int $code The error code + * @param string $msg The translation string of the message to be sent + * + * @return null + */ + public function emit_error($code, $msg) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'jsonrpc' => '2.0', + 'id' => 'id', + 'error' => array( + 'code' => $code, + 'message' => $this->user->lang($msg), + ), + )); + } + + /** + * Looks at the list of allowed extensions and generates a string + * appropriate for use in configuring plupload with + * + * @param \phpbb\cache\service $cache + * @param string $forum_id The ID of the forum + * + * @return string + */ + public function generate_filter_string(\phpbb\cache\service $cache, $forum_id) + { + $attach_extensions = $cache->obtain_attach_extensions($forum_id); + unset($attach_extensions['_allowed_']); + $groups = array(); + + // Re-arrange the extension array to $groups[$group_name][] + foreach ($attach_extensions as $extension => $extension_info) + { + if (!isset($groups[$extension_info['group_name']])) + { + $groups[$extension_info['group_name']] = array(); + } + + $groups[$extension_info['group_name']][] = $extension; + } + + $filters = array(); + foreach ($groups as $group => $extensions) + { + $filters[] = sprintf( + "{title: '%s', extensions: '%s'}", + addslashes(ucfirst(strtolower($group))), + addslashes(implode(',', $extensions)) + ); + } + + return implode(',', $filters); + } + + /** + * Generates a string that is used to tell plupload to automatically resize + * files before uploading them. + * + * @return string + */ + public function generate_resize_string() + { + $resize = ''; + if ($this->config['img_max_height'] > 0 && $this->config['img_max_width'] > 0) + { + $resize = sprintf( + 'resize: {width: %d, height: %d, quality: 100},', + (int) $this->config['img_max_height'], + (int) $this->config['img_max_width'] + ); + } + + return $resize; + } + + /** + * Checks various php.ini values and the maximum file size to determine + * the maximum size chunks a file can be split up into for upload + * + * @return int + */ + public function get_chunk_size() + { + $max = min( + $this->php_ini->get_bytes('upload_max_filesize'), + $this->php_ini->get_bytes('post_max_size'), + max(1, $this->php_ini->get_bytes('memory_limit')), + $this->config['max_filesize'] + ); + + // Use half of the maximum possible to leave plenty of room for other + // POST data. + return floor($max / 2); + } + + protected function temporary_filepath($file_name) + { + // Must preserve the extension for plupload to work. + return sprintf( + '%s/%s_%s%s', + $this->temporary_directory, + $this->config['plupload_salt'], + md5($file_name), + \filespec::get_extension($file_name) + ); + } + + /** + * Checks whether the chunk we are about to deal with was actually uploaded + * by PHP and actually exists, if not, it generates an error + * + * @param string $form_name The name of the file in the form data + * + * @return null + */ + protected function integrate_uploaded_file($form_name, $chunk, $file_path) + { + $is_multipart = $this->is_multipart(); + $upload = $this->request->file($form_name); + if ($is_multipart && (!isset($upload['tmp_name']) || !is_uploaded_file($upload['tmp_name']))) + { + $this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED'); + } + + $tmp_file = $this->temporary_filepath($upload['tmp_name']); + + if (!move_uploaded_file($upload['tmp_name'], $tmp_file)) + { + $this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED'); + } + + $out = fopen("{$file_path}.part", $chunk == 0 ? 'wb' : 'ab'); + if (!$out) + { + $this->emit_error(102, 'PLUPLOAD_ERR_OUTPUT'); + } + + $in = fopen(($is_multipart) ? $tmp_file : 'php://input', 'rb'); + if (!$in) + { + $this->emit_error(101, 'PLUPLOAD_ERR_INPUT'); + } + + while ($buf = fread($in, 4096)) + { + fwrite($out, $buf); + } + + fclose($in); + fclose($out); + + if ($is_multipart) + { + unlink($tmp_file); + } + } + + /** + * Creates the temporary directory if it does not already exist. + * + * @return null + */ + protected function prepare_temporary_directory() + { + if (!file_exists($this->temporary_directory)) + { + mkdir($this->temporary_directory); + + copy( + $this->upload_directory . '/index.htm', + $this->temporary_directory . '/index.htm' + ); + } + } +} -- cgit v1.2.1 From 789d49359510f10c68be61eaa56d77b3ab428328 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Wed, 23 Oct 2013 18:34:06 +0200 Subject: [ticket/11912] Integrate mimetype guesser with plupload PHPBB3-11912 --- phpBB/phpbb/plupload/plupload.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'phpBB/phpbb/plupload') diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index 6eb5adf864..29a4aff39b 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -49,6 +49,11 @@ class plupload */ protected $php_ini; + /** + * @var \phpbb\mimetype\guesser + */ + protected $mimetype_guesser; + /** * Final destination for uploaded files, i.e. the "files" directory. * @var string @@ -69,16 +74,18 @@ class plupload * @param \phpbb\request\request_interface $request * @param \phpbb\user $user * @param \phpbb\php\ini $php_ini + * @param \phpbb\mimetype\guesser $mimetype_guesser * * @return null */ - public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, \phpbb\php\ini $php_ini) + public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, \phpbb\php\ini $php_ini, \phpbb\mimetype\guesser $mimetype_guesser) { $this->phpbb_root_path = $phpbb_root_path; $this->config = $config; $this->request = $request; $this->user = $user; $this->php_ini = $php_ini; + $this->mimetype_guesser = $mimetype_guesser; $this->upload_directory = $this->phpbb_root_path . $this->config['upload_path']; $this->temporary_directory = $this->upload_directory . '/plupload'; @@ -128,7 +135,7 @@ class plupload 'tmp_name' => $file_path, 'name' => $this->request->variable('real_filename', ''), 'size' => filesize($file_path), - 'type' => $file_info->getMimeType($file_path), + 'type' => $this->mimetype_guesser->guess($file_path), ); } else -- cgit v1.2.1 From bc7ff47537bae4f6db9de781cf8ba3487e28a30b Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Thu, 24 Oct 2013 12:05:00 +0200 Subject: [ticket/11912] Supply filename to content_guesser for guessing on windows The filename of the files sent to the guesser by plupload do not contain the file extension. Therefore, it's impossible to guess the mimetype if only the content_guesser is available and the function mime_content_type() doesn't exist. By supplying the filename we can circumvent this issue. PHPBB3-11912 --- phpBB/phpbb/plupload/plupload.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'phpBB/phpbb/plupload') diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index 29a4aff39b..dedc3cbcd4 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -135,7 +135,7 @@ class plupload 'tmp_name' => $file_path, 'name' => $this->request->variable('real_filename', ''), 'size' => filesize($file_path), - 'type' => $this->mimetype_guesser->guess($file_path), + 'type' => $this->mimetype_guesser->guess($file_path, $file_name), ); } else -- cgit v1.2.1 From 7aa8f6461f1e85cf91931f56b95384e54fec07c2 Mon Sep 17 00:00:00 2001 From: Andreas Fischer Date: Wed, 30 Oct 2013 13:05:28 +0100 Subject: [task/code-sniffer] Remove the IN_PHPBB check side-effect from class files. PHPBB3-11980 --- phpBB/phpbb/plupload/plupload.php | 8 -------- 1 file changed, 8 deletions(-) (limited to 'phpBB/phpbb/plupload') diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index 6eb5adf864..f21ec40450 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -9,14 +9,6 @@ namespace phpbb\plupload; -/** - * @ignore - */ -if (!defined('IN_PHPBB')) -{ - exit; -} - /** * This class handles all server-side plupload functions * -- cgit v1.2.1 From 81caa35955b696e1ffdcafe4ccdd72a83700b773 Mon Sep 17 00:00:00 2001 From: Marc Alexander Date: Mon, 11 Nov 2013 21:24:44 +0100 Subject: [ticket/11912] Remove obsolete $file_info from plupload PHPBB3-11912 --- phpBB/phpbb/plupload/plupload.php | 2 -- 1 file changed, 2 deletions(-) (limited to 'phpBB/phpbb/plupload') diff --git a/phpBB/phpbb/plupload/plupload.php b/phpBB/phpbb/plupload/plupload.php index dedc3cbcd4..f9f2fdd812 100644 --- a/phpBB/phpbb/plupload/plupload.php +++ b/phpBB/phpbb/plupload/plupload.php @@ -128,8 +128,6 @@ class plupload { rename("{$file_path}.part", $file_path); - $file_info = new \Symfony\Component\HttpFoundation\File\File($file_path); - // Need to modify some of the $_FILES values to reflect the new file return array( 'tmp_name' => $file_path, -- cgit v1.2.1