diff --git a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php index b0c5487f5f..1da728d5b7 100644 --- a/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php +++ b/build/code_sniffer/phpbb/Sniffs/Namespaces/UnusedUseSniff.php @@ -192,6 +192,9 @@ class phpbb_Sniffs_Namespaces_UnusedUseSniff implements Sniff { $ok = $this->check($phpcsFile, $param['type_hint'], $class_name_full, $class_name_short, $function_declaration) || $ok; } + + $method_properties = $phpcsFile->getMethodProperties($function_declaration); + $ok = $this->check($phpcsFile, $method_properties['return_type'], $class_name_full, $class_name_short, $function_declaration) || $ok; } // Checks in catch blocks diff --git a/phpBB/config/default/container/services_avatar.yml b/phpBB/config/default/container/services_avatar.yml index 4f542c61a2..cec4dc771f 100644 --- a/phpBB/config/default/container/services_avatar.yml +++ b/phpBB/config/default/container/services_avatar.yml @@ -56,6 +56,7 @@ services: class: phpbb\avatar\driver\upload arguments: - '@config' + - '@controller.helper' - '%core.root_path%' - '%core.php_ext%' - '@storage.avatar' diff --git a/phpBB/config/default/container/services_storage.yml b/phpBB/config/default/container/services_storage.yml index 92f31779e6..c9caab3f84 100644 --- a/phpBB/config/default/container/services_storage.yml +++ b/phpBB/config/default/container/services_storage.yml @@ -82,3 +82,28 @@ services: arguments: tags: - { name: storage.provider } + +# Controllers + storage.controller.avatar: + class: phpbb\storage\controller\avatar + arguments: + - '@cache' + - '@config' + - '@dbal.conn' + - '@storage.avatar' + - '@symfony_request' + + storage.controller.attachment: + class: phpbb\storage\controller\attachment + arguments: + - '@auth' + - '@cache' + - '@config' + - '@content.visibility' + - '@dbal.conn' + - '@dispatcher' + - '@language' + - '@request' + - '@storage.attachment' + - '@symfony_request' + - '@user' diff --git a/phpBB/config/default/routing/routing.yml b/phpBB/config/default/routing/routing.yml index 441e544cbf..7fff9204f0 100644 --- a/phpBB/config/default/routing/routing.yml +++ b/phpBB/config/default/routing/routing.yml @@ -35,3 +35,7 @@ phpbb_report_routing: phpbb_ucp_routing: resource: ucp.yml prefix: /user + +phpbb_storage_routing: + resource: storage.yml + prefix: /download diff --git a/phpBB/config/default/routing/storage.yml b/phpBB/config/default/routing/storage.yml new file mode 100644 index 0000000000..ac135c5e6b --- /dev/null +++ b/phpBB/config/default/routing/storage.yml @@ -0,0 +1,11 @@ +phpbb_storage_avatar: + path: /avatar/{file} + defaults: + _controller: storage.controller.avatar:handle + +phpbb_storage_attachment: + path: /attachment/{file} + defaults: + _controller: storage.controller.attachment:handle + requirements: + id: \d+ diff --git a/phpBB/download/file.php b/phpBB/download/file.php index 6b0b577489..c71cc2274c 100644 --- a/phpBB/download/file.php +++ b/phpBB/download/file.php @@ -11,310 +11,45 @@ * */ +use Symfony\Component\HttpFoundation\RedirectResponse; + /** * @ignore */ define('IN_PHPBB', true); $phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../'; $phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); -// Thank you sun. -if (isset($_SERVER['CONTENT_TYPE'])) -{ - if ($_SERVER['CONTENT_TYPE'] === 'application/x-java-archive') - { - exit; - } -} -else if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Java') !== false) +// Start session management +$user->session_begin(); +$auth->acl($user->data); + +/** @var \phpbb\controller\helper $controller_helper */ +$controller_helper = $phpbb_container->get('controller.helper'); + +if ($request->is_set('avatar')) { + $response = new RedirectResponse( + $controller_helper->route('phpbb_storage_avatar', array( + 'file' => $request->variable('avatar', ''), + ), false), + 301 + ); + $response->send(); + exit; } -if (isset($_GET['avatar'])) -{ - require($phpbb_root_path . 'includes/startup.' . $phpEx); - - require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); - $phpbb_class_loader = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}phpbb/", $phpEx); - $phpbb_class_loader->register(); - - $phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); - extract($phpbb_config_php_file->get_all()); - - if (!defined('PHPBB_ENVIRONMENT')) - { - @define('PHPBB_ENVIRONMENT', 'production'); - } - - if (!defined('PHPBB_INSTALLED') || empty($dbms) || empty($acm_type)) - { - exit; - } - - require($phpbb_root_path . 'includes/constants.' . $phpEx); - require($phpbb_root_path . 'includes/functions.' . $phpEx); - require($phpbb_root_path . 'includes/functions_download' . '.' . $phpEx); - require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); - - // Setup class loader first - $phpbb_class_loader_ext = new \phpbb\class_loader('\\', "{$phpbb_root_path}ext/", $phpEx); - $phpbb_class_loader_ext->register(); - - // Set up container - $phpbb_container_builder = new \phpbb\di\container_builder($phpbb_root_path, $phpEx); - $phpbb_container = $phpbb_container_builder->with_config($phpbb_config_php_file)->get_container(); - - $phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); - $phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); - - // set up caching - /* @var $cache \phpbb\cache\service */ - $cache = $phpbb_container->get('cache'); - - /* @var $phpbb_dispatcher \phpbb\event\dispatcher */ - $phpbb_dispatcher = $phpbb_container->get('dispatcher'); - - /* @var $request \phpbb\request\request_interface */ - $request = $phpbb_container->get('request'); - - /* @var $db \phpbb\db\driver\driver_interface */ - $db = $phpbb_container->get('dbal.conn'); - - /* @var $phpbb_log \phpbb\log\log_interface */ - $phpbb_log = $phpbb_container->get('log'); - - unset($dbpasswd); - - /* @var $config \phpbb\config\config */ - $config = $phpbb_container->get('config'); - - // load extensions - /* @var $phpbb_extension_manager \phpbb\extension\manager */ - $phpbb_extension_manager = $phpbb_container->get('ext.manager'); - - // worst-case default - $browser = strtolower($request->header('User-Agent', 'msie 6.0')); - - /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ - $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); - - if (@is_file($phpbb_root_path . $config['exts_composer_vendor_dir'] . '/autoload.php')) - { - require_once($phpbb_root_path . $config['exts_composer_vendor_dir'] . '/autoload.php'); - } - - $filename = $request->variable('avatar', ''); - $avatar_group = false; - $exit = false; - - if (isset($filename[0]) && $filename[0] === 'g') - { - $avatar_group = true; - $filename = substr($filename, 1); - } - - // '==' is not a bug - . as the first char is as bad as no dot at all - if (strpos($filename, '.') == false) - { - send_status_line(403, 'Forbidden'); - $exit = true; - } - - if (!$exit) - { - $ext = substr(strrchr($filename, '.'), 1); - $stamp = (int) substr(stristr($filename, '_'), 1); - $filename = (int) $filename; - $exit = set_modified_headers($stamp, $browser); - } - if (!$exit && !in_array($ext, array('png', 'gif', 'jpg', 'jpeg'))) - { - // no way such an avatar could exist. They are not following the rules, stop the show. - send_status_line(403, 'Forbidden'); - $exit = true; - } - - - if (!$exit) - { - if (!$filename) - { - // no way such an avatar could exist. They are not following the rules, stop the show. - send_status_line(403, 'Forbidden'); - } - else - { - send_avatar_to_browser(($avatar_group ? 'g' : '') . $filename . '.' . $ext, $browser); - } - } - file_gc(); -} - -// implicit else: we are not in avatar mode -include($phpbb_root_path . 'common.' . $phpEx); -require($phpbb_root_path . 'includes/functions_download' . '.' . $phpEx); - $attach_id = $request->variable('id', 0); $mode = $request->variable('mode', ''); $thumbnail = $request->variable('t', false); -// Start session management, do not update session page. -$user->session_begin(false); -$auth->acl($user->data); -$user->setup('viewtopic'); - -$phpbb_content_visibility = $phpbb_container->get('content.visibility'); - -if (!$config['allow_attachments'] && !$config['allow_pm_attach']) -{ - send_status_line(404, 'Not Found'); - trigger_error('ATTACHMENT_FUNCTIONALITY_DISABLED'); -} - -if (!$attach_id) -{ - send_status_line(404, 'Not Found'); - trigger_error('NO_ATTACHMENT_SELECTED'); -} - -$sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id, is_orphan, physical_filename, real_filename, extension, mimetype, filesize, filetime - FROM ' . ATTACHMENTS_TABLE . " - WHERE attach_id = $attach_id"; -$result = $db->sql_query($sql); -$attachment = $db->sql_fetchrow($result); -$db->sql_freeresult($result); - -if (!$attachment) -{ - send_status_line(404, 'Not Found'); - trigger_error('ERROR_NO_ATTACHMENT'); -} -else if (!download_allowed()) -{ - send_status_line(403, 'Forbidden'); - trigger_error($user->lang['LINKAGE_FORBIDDEN']); -} -else -{ - $attachment['physical_filename'] = utf8_basename($attachment['physical_filename']); - - if (!$attachment['in_message'] && !$config['allow_attachments'] || $attachment['in_message'] && !$config['allow_pm_attach']) - { - send_status_line(404, 'Not Found'); - trigger_error('ATTACHMENT_FUNCTIONALITY_DISABLED'); - } - - if ($attachment['is_orphan']) - { - // We allow admins having attachment permissions to see orphan attachments... - $own_attachment = ($auth->acl_get('a_attach') || $attachment['poster_id'] == $user->data['user_id']) ? true : false; - - if (!$own_attachment || ($attachment['in_message'] && !$auth->acl_get('u_pm_download')) || (!$attachment['in_message'] && !$auth->acl_get('u_download'))) - { - send_status_line(404, 'Not Found'); - trigger_error('ERROR_NO_ATTACHMENT'); - } - - // Obtain all extensions... - $extensions = $cache->obtain_attach_extensions(true); - } - else - { - if (!$attachment['in_message']) - { - phpbb_download_handle_forum_auth($db, $auth, $attachment['topic_id']); - - $sql = 'SELECT forum_id, post_visibility - FROM ' . POSTS_TABLE . ' - WHERE post_id = ' . (int) $attachment['post_msg_id']; - $result = $db->sql_query($sql); - $post_row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - if (!$post_row || !$phpbb_content_visibility->is_visible('post', $post_row['forum_id'], $post_row)) - { - // Attachment of a soft deleted post and the user is not allowed to see the post - send_status_line(404, 'Not Found'); - trigger_error('ERROR_NO_ATTACHMENT'); - } - } - else - { - // Attachment is in a private message. - $post_row = array('forum_id' => false); - phpbb_download_handle_pm_auth($db, $auth, $user->data['user_id'], $attachment['post_msg_id']); - } - - $extensions = array(); - if (!extension_allowed($post_row['forum_id'], $attachment['extension'], $extensions)) - { - send_status_line(403, 'Forbidden'); - trigger_error(sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension'])); - } - } - - $display_cat = $extensions[$attachment['extension']]['display_cat']; - - if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg')) - { - $display_cat = ATTACHMENT_CATEGORY_NONE; - } - - if ($thumbnail) - { - $attachment['physical_filename'] = 'thumb_' . $attachment['physical_filename']; - } - else if ($display_cat == ATTACHMENT_CATEGORY_NONE && !$attachment['is_orphan'] && !phpbb_http_byte_range($attachment['filesize'])) - { - // Update download count - phpbb_increment_downloads($db, $attachment['attach_id']); - } - - $redirect = ''; - - /** - * Event to modify data before sending file to browser - * - * @event core.download_file_send_to_browser_before - * @var int attach_id The attachment ID - * @var array attachment Array with attachment data - * @var int display_cat Attachment category - * @var array extensions Array with file extensions data - * @var string mode Download mode - * @var bool thumbnail Flag indicating if the file is a thumbnail - * @var string redirect Do a redirection instead of reading the file - * @since 3.1.6-RC1 - * @changed 3.1.7-RC1 Fixing wrong name of a variable (replacing "extension" by "extensions") - * @changed 3.3.0-a1 Add redirect variable - */ - $vars = array( - 'attach_id', - 'attachment', - 'display_cat', - 'extensions', - 'mode', - 'thumbnail', - 'redirect', - ); - extract($phpbb_dispatcher->trigger_event('core.download_file_send_to_browser_before', compact($vars))); - - if ($display_cat == ATTACHMENT_CATEGORY_IMAGE && $mode === 'view' && (strpos($attachment['mimetype'], 'image') === 0) && (strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7)) - { - wrap_img_in_html(append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'id=' . $attachment['attach_id']), $attachment['real_filename']); - file_gc(); - } - else - { - if (!empty($redirect)) - { - redirect($redirect, false, true); - } - else - { - send_file_to_browser($attachment, $display_cat); - } - - file_gc(); - } -} +$response = new RedirectResponse( + $controller_helper->route('phpbb_storage_attachment', array( + 'file' => $attach_id, + 't' => $thumbnail, + ), false), + 301 +); +$response->send(); diff --git a/phpBB/includes/acp/acp_attachments.php b/phpBB/includes/acp/acp_attachments.php index 91396b80fe..92363b0ff9 100644 --- a/phpBB/includes/acp/acp_attachments.php +++ b/phpBB/includes/acp/acp_attachments.php @@ -14,6 +14,16 @@ /** * @ignore */ + +use phpbb\attachment\manager; +use phpbb\config\config; +use phpbb\controller\helper; +use phpbb\db\driver\driver_interface; +use phpbb\filesystem\filesystem_interface; +use phpbb\language\language; +use phpbb\template\template; +use phpbb\user; + if (!defined('IN_PHPBB')) { exit; @@ -21,30 +31,33 @@ if (!defined('IN_PHPBB')) class acp_attachments { - /** @var \phpbb\db\driver\driver_interface */ + /** @var driver_interface */ protected $db; - /** @var \phpbb\config\config */ + /** @var config */ protected $config; - /** @var \phpbb\language\language */ + /** @var language */ protected $language; /** @var ContainerBuilder */ protected $phpbb_container; - /** @var \phpbb\template\template */ + /** @var template */ protected $template; - /** @var \phpbb\user */ + /** @var user */ protected $user; - /** @var \phpbb\filesystem\filesystem_interface */ + /** @var filesystem_interface */ protected $filesystem; - /** @var \phpbb\attachment\manager */ + /** @var manager */ protected $attachment_manager; + /** @var helper */ + protected $controller_helper; + public $id; public $u_action; protected $new_config; @@ -63,6 +76,7 @@ class acp_attachments $this->phpbb_container = $phpbb_container; $this->filesystem = $phpbb_filesystem; $this->attachment_manager = $phpbb_container->get('attachment.manager'); + $this->controller_helper = $phpbb_container->get('controller.helper'); $user->add_lang(array('posting', 'viewtopic', 'acp/attachments')); @@ -1082,8 +1096,8 @@ class acp_attachments 'PHYSICAL_FILENAME' => utf8_basename($row['physical_filename']), 'ATTACH_ID' => $row['attach_id'], 'POST_IDS' => (!empty($post_ids[$row['attach_id']])) ? $post_ids[$row['attach_id']] : '', - 'U_FILE' => append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'mode=view&id=' . $row['attach_id'])) - ); + 'U_FILE' => $this->controller_helper->route('phpbb_storage_attachment', ['file' => (int) $row['attach_id']]) + )); } $db->sql_freeresult($result); @@ -1270,8 +1284,8 @@ class acp_attachments 'S_IN_MESSAGE' => (bool) $row['in_message'], 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t={$row['topic_id']}&p={$row['post_msg_id']}") . "#p{$row['post_msg_id']}", - 'U_FILE' => append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'mode=view&id=' . $row['attach_id'])) - ); + 'U_FILE' => $this->controller_helper->route('phpbb_storage_attachment', ['file' => $row['attach_id']]) + )); } break; diff --git a/phpBB/includes/acp/acp_users.php b/phpBB/includes/acp/acp_users.php index 4a041886b7..e2a28c05b1 100644 --- a/phpBB/includes/acp/acp_users.php +++ b/phpBB/includes/acp/acp_users.php @@ -14,6 +14,9 @@ /** * @ignore */ + +use phpbb\controller\helper; + if (!defined('IN_PHPBB')) { exit; @@ -36,6 +39,9 @@ class acp_users global $phpbb_dispatcher, $request; global $phpbb_container, $phpbb_log; + /** @var helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + $user->add_lang(array('posting', 'ucp', 'acp/users')); $this->tpl_name = 'acp_users'; @@ -2126,9 +2132,6 @@ class acp_users $decoded_message = generate_text_for_edit($signature, $bbcode_uid, $bbcode_flags); } - /** @var \phpbb\controller\helper $controller_helper */ - $controller_helper = $phpbb_container->get('controller.helper'); - $template->assign_vars(array( 'S_SIGNATURE' => true, @@ -2298,7 +2301,7 @@ class acp_users 'S_IN_MESSAGE' => $row['in_message'], - 'U_DOWNLOAD' => append_sid("{$phpbb_root_path}download/file.$phpEx", 'mode=view&id=' . $row['attach_id']), + 'U_DOWNLOAD' => $controller_helper->route('phpbb_storage_attachment', ['file' => (int) $row['attach_id']]), 'U_VIEW_TOPIC' => $view_topic) ); } diff --git a/phpBB/includes/functions_compatibility.php b/phpBB/includes/functions_compatibility.php index 927df642f9..64eb2333ce 100644 --- a/phpBB/includes/functions_compatibility.php +++ b/phpBB/includes/functions_compatibility.php @@ -927,7 +927,7 @@ function parse_cfg_file($filename, $lines = false) } else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"')) { - $value = htmlspecialchars(substr($value, 1, strlen($value)-2), ENT_COMPAT); + $value = htmlspecialchars(substr($value, 1, strlen($value) - 2), ENT_COMPAT); } else { @@ -944,3 +944,27 @@ function parse_cfg_file($filename, $lines = false) return $parsed_items; } + +/** +* Wraps an url into a simple html page. Used to display attachments in IE. +* this is a workaround for now; might be moved to template system later +* direct any complaints to 1 Microsoft Way, Redmond +* +* @deprecated: 3.3.0-dev (To be removed: 4.0.0) +*/ +function wrap_img_in_html($src, $title) +{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '' . $title . ''; + echo ''; + echo ''; + echo '
'; + echo '' . $title . ''; + echo '
'; + echo ''; + echo ''; +} diff --git a/phpBB/includes/functions_content.php b/phpBB/includes/functions_content.php index 3de8334674..e6dde54737 100644 --- a/phpBB/includes/functions_content.php +++ b/phpBB/includes/functions_content.php @@ -1124,6 +1124,9 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count_a $storage_attachment = $phpbb_container->get('storage.attachment'); + /** @var \phpbb\controller\helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + // $compiled_attachments = array(); @@ -1283,15 +1286,14 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count_a $display_cat = ATTACHMENT_CATEGORY_NONE; } - $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']); + $download_link = $controller_helper->route('phpbb_storage_attachment', ['file' => (int) $attachment['attach_id']]); $l_downloaded_viewed = 'VIEWED_COUNTS'; switch ($display_cat) { // Images case ATTACHMENT_CATEGORY_IMAGE: - $inline_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']); - $download_link .= '&mode=view'; + $inline_link = $controller_helper->route('phpbb_storage_attachment', ['file' => (int) $attachment['attach_id']]); $block_array += array( 'S_IMAGE' => true, @@ -1303,8 +1305,7 @@ function parse_attachments($forum_id, &$message, &$attachments, &$update_count_a // Images, but display Thumbnail case ATTACHMENT_CATEGORY_THUMB: - $thumbnail_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id'] . '&t=1'); - $download_link .= '&mode=view'; + $thumbnail_link = $controller_helper->route('phpbb_storage_attachment', ['file' => (int) $attachment['attach_id'], 't' => 1]); $block_array += array( 'S_THUMBNAIL' => true, diff --git a/phpBB/includes/functions_download.php b/phpBB/includes/functions_download.php deleted file mode 100644 index 3dcfb4cc98..0000000000 --- a/phpBB/includes/functions_download.php +++ /dev/null @@ -1,738 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -/** -* @ignore -*/ -if (!defined('IN_PHPBB')) -{ - exit; -} - -/** -* A simplified function to deliver avatars -* The argument needs to be checked before calling this function. -*/ -function send_avatar_to_browser($file, $browser) -{ - global $config, $phpbb_container; - - $storage = $phpbb_container->get('storage.avatar'); - - $prefix = $config['avatar_salt'] . '_'; - $file_path = $prefix . $file; - - if ($storage->exists($file_path) && !headers_sent()) - { - $file_info = $storage->file_info($file_path); - - header('Cache-Control: public'); - - try - { - header('Content-Type: ' . $file_info->mimetype); - } - catch (\phpbb\storage\exception\exception $e) - { - // Just don't send this header - } - - if ((strpos(strtolower($browser), 'msie') !== false) && !phpbb_is_greater_ie_version($browser, 7)) - { - header('Content-Disposition: attachment; ' . header_filename($file)); - - if (strpos(strtolower($browser), 'msie 6.0') !== false) - { - header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); - } - else - { - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); - } - } - else - { - header('Content-Disposition: inline; ' . header_filename($file)); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); - } - - try - { - header('Content-Length: ' . $file_info->size); - } - catch (\phpbb\storage\exception\exception $e) - { - // Just don't send this header - } - - try - { - $fp = $storage->read_stream($file_path); - $output = fopen('php://output', 'w+b'); - stream_copy_to_stream($fp, $output); - fclose($fp); - fclose($output); - } - catch (\Exception $e) - { - // Send nothing - } - - flush(); - } - else - { - header('HTTP/1.0 404 Not Found'); - } -} - -/** -* Wraps an url into a simple html page. Used to display attachments in IE. -* this is a workaround for now; might be moved to template system later -* direct any complaints to 1 Microsoft Way, Redmond -*/ -function wrap_img_in_html($src, $title) -{ - echo ''; - echo ''; - echo ''; - echo ''; - echo ''; - echo '' . $title . ''; - echo ''; - echo ''; - echo '
'; - echo '' . $title . ''; - echo '
'; - echo ''; - echo ''; -} - -/** -* Send file to browser -*/ -function send_file_to_browser($attachment, $category) -{ - global $user, $db, $phpbb_dispatcher, $request, $phpbb_container; - - $storage = $phpbb_container->get('storage.attachment'); - - $filename = $attachment['physical_filename']; - - if (!$storage->exists($filename)) - { - send_status_line(404, 'Not Found'); - trigger_error('ERROR_NO_ATTACHMENT'); - } - - // Correct the mime type - we force application/octetstream for all files, except images - // Please do not change this, it is a security precaution - if ($category != ATTACHMENT_CATEGORY_IMAGE || strpos($attachment['mimetype'], 'image') !== 0) - { - $attachment['mimetype'] = (strpos(strtolower($user->browser), 'msie') !== false || strpos(strtolower($user->browser), 'opera') !== false) ? 'application/octetstream' : 'application/octet-stream'; - } - - if (@ob_get_length()) - { - @ob_end_clean(); - } - - // Now send the File Contents to the Browser - try - { - $file_info = $storage->file_info($filename); - $size = $file_info->size; - } - catch (\Exception $e) - { - $size = 0; - } - - /** - * Event to alter attachment before it is sent to browser. - * - * @event core.send_file_to_browser_before - * @var array attachment Attachment data - * @var int category Attachment category - * @var string filename Path to file, including filename - * @var int size File size - * @since 3.1.11-RC1 - */ - $vars = array( - 'attachment', - 'category', - 'filename', - 'size', - ); - extract($phpbb_dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars))); - - // To correctly display further errors we need to make sure we are using the correct headers for both (unsetting content-length may not work) - - // Check if headers already sent or not able to get the file contents. - if (headers_sent()) - { - send_status_line(500, 'Internal Server Error'); - trigger_error('UNABLE_TO_DELIVER_FILE'); - } - - // Make sure the database record for the filesize is correct - if ($size > 0 && $size != $attachment['filesize'] && strpos($attachment['physical_filename'], 'thumb_') === false) - { - // Update database record - $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' - SET filesize = ' . (int) $size . ' - WHERE attach_id = ' . (int) $attachment['attach_id']; - $db->sql_query($sql); - } - - // Now the tricky part... let's dance - header('Cache-Control: private'); - - // Send out the Headers. Do not set Content-Disposition to inline please, it is a security measure for users using the Internet Explorer. - header('Content-Type: ' . $attachment['mimetype']); - - if (phpbb_is_greater_ie_version($user->browser, 7)) - { - header('X-Content-Type-Options: nosniff'); - } - - if (empty($user->browser) || ((strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7))) - { - header('Content-Disposition: attachment; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'], ENT_COMPAT))); - if (empty($user->browser) || (strpos(strtolower($user->browser), 'msie 6.0') !== false)) - { - header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); - } - } - else - { - header('Content-Disposition: ' . ((strpos($attachment['mimetype'], 'image') === 0) ? 'inline' : 'attachment') . '; ' . header_filename(htmlspecialchars_decode($attachment['real_filename'], ENT_COMPAT))); - if (phpbb_is_greater_ie_version($user->browser, 7) && (strpos($attachment['mimetype'], 'image') !== 0)) - { - header('X-Download-Options: noopen'); - } - } - - if (!set_modified_headers($attachment['filetime'], $user->browser)) - { - if ($size) - { - header("Content-Length: $size"); - } - - // Try to deliver in chunks - @set_time_limit(0); - - $fp = $storage->read_stream($filename); - - // Close the db connection before sending the file etc. - file_gc(false); - - if ($fp !== false) - { - $output = fopen('php://output', 'w+b'); - stream_copy_to_stream($fp, $output); - fclose($fp); - } - - flush(); - } - - exit; -} - -/** -* Get a browser friendly UTF-8 encoded filename -*/ -function header_filename($file) -{ - global $request; - - $user_agent = $request->header('User-Agent'); - - // There be dragons here. - // Not many follows the RFC... - if (strpos($user_agent, 'MSIE') !== false || strpos($user_agent, 'Konqueror') !== false) - { - return "filename=" . rawurlencode($file); - } - - // follow the RFC for extended filename for the rest - return "filename*=UTF-8''" . rawurlencode($file); -} - -/** -* Check if downloading item is allowed -*/ -function download_allowed() -{ - global $config, $user, $db, $request; - - if (!$config['secure_downloads']) - { - return true; - } - - $url = htmlspecialchars_decode($request->header('Referer'), ENT_COMPAT); - - if (!$url) - { - return ($config['secure_allow_empty_referer']) ? true : false; - } - - // Split URL into domain and script part - $url = @parse_url($url); - - if ($url === false) - { - return ($config['secure_allow_empty_referer']) ? true : false; - } - - $hostname = $url['host']; - unset($url); - - $allowed = ($config['secure_allow_deny']) ? false : true; - $iplist = array(); - - if (($ip_ary = @gethostbynamel($hostname)) !== false) - { - foreach ($ip_ary as $ip) - { - if ($ip) - { - $iplist[] = $ip; - } - } - } - - // Check for own server... - $server_name = $user->host; - - // Forcing server vars is the only way to specify/override the protocol - if ($config['force_server_vars'] || !$server_name) - { - $server_name = $config['server_name']; - } - - if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname)) - { - $allowed = true; - } - - // Get IP's and Hostnames - if (!$allowed) - { - $sql = 'SELECT site_ip, site_hostname, ip_exclude - FROM ' . SITELIST_TABLE; - $result = $db->sql_query($sql); - - while ($row = $db->sql_fetchrow($result)) - { - $site_ip = trim($row['site_ip']); - $site_hostname = trim($row['site_hostname']); - - if ($site_ip) - { - foreach ($iplist as $ip) - { - if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip)) - { - if ($row['ip_exclude']) - { - $allowed = ($config['secure_allow_deny']) ? false : true; - break 2; - } - else - { - $allowed = ($config['secure_allow_deny']) ? true : false; - } - } - } - } - - if ($site_hostname) - { - if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname)) - { - if ($row['ip_exclude']) - { - $allowed = ($config['secure_allow_deny']) ? false : true; - break; - } - else - { - $allowed = ($config['secure_allow_deny']) ? true : false; - } - } - } - } - $db->sql_freeresult($result); - } - - return $allowed; -} - -/** -* Check if the browser has the file already and set the appropriate headers- -* @returns false if a resend is in order. -*/ -function set_modified_headers($stamp, $browser) -{ - global $request; - - // let's see if we have to send the file at all - $last_load = $request->header('If-Modified-Since') ? strtotime(trim($request->header('If-Modified-Since'))) : false; - - if (strpos(strtolower($browser), 'msie 6.0') === false && !phpbb_is_greater_ie_version($browser, 7)) - { - if ($last_load !== false && $last_load >= $stamp) - { - send_status_line(304, 'Not Modified'); - // seems that we need those too ... browsers - header('Cache-Control: private'); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); - return true; - } - else - { - header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stamp) . ' GMT'); - } - } - return false; -} - -/** -* Garbage Collection -* -* @param bool $exit Whether to die or not. -* -* @return null -*/ -function file_gc($exit = true) -{ - global $cache, $db; - - if (!empty($cache)) - { - $cache->unload(); - } - - $db->sql_close(); - - if ($exit) - { - exit; - } -} - -/** -* HTTP range support (RFC 2616 Section 14.35) -* -* Allows browsers to request partial file content -* in case a download has been interrupted. -* -* @param int $filesize the size of the file in bytes we are about to deliver -* -* @return mixed false if the whole file has to be delivered -* associative array on success -*/ -function phpbb_http_byte_range($filesize) -{ - // Only call find_range_request() once. - static $request_array; - - if (!$filesize) - { - return false; - } - - if (!isset($request_array)) - { - $request_array = phpbb_find_range_request(); - } - - return (empty($request_array)) ? false : phpbb_parse_range_request($request_array, $filesize); -} - -/** -* Searches for HTTP range request in request headers. -* -* @return mixed false if no request found -* array of strings containing the requested ranges otherwise -* e.g. array(0 => '0-0', 1 => '123-125') -*/ -function phpbb_find_range_request() -{ - global $request; - - $value = $request->header('Range'); - - // Make sure range request starts with "bytes=" - if (strpos($value, 'bytes=') === 0) - { - // Strip leading 'bytes=' - // Multiple ranges can be separated by a comma - return explode(',', substr($value, 6)); - } - - return false; -} - -/** -* Analyses a range request array. -* -* A range request can contain multiple ranges, -* we however only handle the first request and -* only support requests from a given byte to the end of the file. -* -* @param array $request_array array of strings containing the requested ranges -* @param int $filesize the full size of the file in bytes that has been requested -* -* @return mixed false if the whole file has to be delivered -* associative array on success -* byte_pos_start the first byte position, can be passed to fseek() -* byte_pos_end the last byte position -* bytes_requested the number of bytes requested -* bytes_total the full size of the file -*/ -function phpbb_parse_range_request($request_array, $filesize) -{ - $first_byte_pos = -1; - $last_byte_pos = -1; - - // Go through all ranges - foreach ($request_array as $range_string) - { - $range = explode('-', trim($range_string)); - - // "-" is invalid, "0-0" however is valid and means the very first byte. - if (count($range) != 2 || $range[0] === '' && $range[1] === '') - { - continue; - } - - // Substitute defaults - if ($range[0] === '') - { - $range[0] = 0; - } - - if ($range[1] === '') - { - $range[1] = $filesize - 1; - } - - if ($last_byte_pos >= 0 && $last_byte_pos + 1 != $range[0]) - { - // We only support contiguous ranges, no multipart stuff :( - return false; - } - - if ($range[1] && $range[1] < $range[0]) - { - // The requested range contains 0 bytes. - continue; - } - - // Return bytes from $range[0] to $range[1] - if ($first_byte_pos < 0) - { - $first_byte_pos = (int) $range[0]; - } - - $last_byte_pos = (int) $range[1]; - - if ($first_byte_pos >= $filesize) - { - // Requested range not satisfiable - return false; - } - - // Adjust last-byte-pos if it is absent or greater than the content. - if ($range[1] === '' || $last_byte_pos >= $filesize) - { - $last_byte_pos = $filesize - 1; - } - } - - if ($first_byte_pos < 0 || $last_byte_pos < 0) - { - return false; - } - - return array( - 'byte_pos_start' => $first_byte_pos, - 'byte_pos_end' => $last_byte_pos, - 'bytes_requested' => $last_byte_pos - $first_byte_pos + 1, - 'bytes_total' => $filesize, - ); -} - -/** -* Increments the download count of all provided attachments -* -* @param \phpbb\db\driver\driver_interface $db The database object -* @param array|int $ids The attach_id of each attachment -* -* @return null -*/ -function phpbb_increment_downloads($db, $ids) -{ - if (!is_array($ids)) - { - $ids = array($ids); - } - - $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' - SET download_count = download_count + 1 - WHERE ' . $db->sql_in_set('attach_id', $ids); - $db->sql_query($sql); -} - -/** -* Handles authentication when downloading attachments from a post or topic -* -* @param \phpbb\db\driver\driver_interface $db The database object -* @param \phpbb\auth\auth $auth The authentication object -* @param int $topic_id The id of the topic that we are downloading from -* -* @return null -*/ -function phpbb_download_handle_forum_auth($db, $auth, $topic_id) -{ - global $phpbb_container; - - $sql_array = array( - 'SELECT' => 't.topic_visibility, t.forum_id, f.forum_name, f.forum_password, f.parent_id', - 'FROM' => array( - TOPICS_TABLE => 't', - FORUMS_TABLE => 'f', - ), - 'WHERE' => 't.topic_id = ' . (int) $topic_id . ' - AND t.forum_id = f.forum_id', - ); - - $sql = $db->sql_build_query('SELECT', $sql_array); - $result = $db->sql_query($sql); - $row = $db->sql_fetchrow($result); - $db->sql_freeresult($result); - - $phpbb_content_visibility = $phpbb_container->get('content.visibility'); - - if ($row && !$phpbb_content_visibility->is_visible('topic', $row['forum_id'], $row)) - { - send_status_line(404, 'Not Found'); - trigger_error('ERROR_NO_ATTACHMENT'); - } - else if ($row && $auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id'])) - { - if ($row['forum_password']) - { - // Do something else ... ? - login_forum_box($row); - } - } - else - { - send_status_line(403, 'Forbidden'); - trigger_error('SORRY_AUTH_VIEW_ATTACH'); - } -} - -/** -* Handles authentication when downloading attachments from PMs -* -* @param \phpbb\db\driver\driver_interface $db The database object -* @param \phpbb\auth\auth $auth The authentication object -* @param int $user_id The user id -* @param int $msg_id The id of the PM that we are downloading from -* -* @return null -*/ -function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id) -{ - global $phpbb_dispatcher; - - if (!$auth->acl_get('u_pm_download')) - { - send_status_line(403, 'Forbidden'); - trigger_error('SORRY_AUTH_VIEW_ATTACH'); - } - - $allowed = phpbb_download_check_pm_auth($db, $user_id, $msg_id); - - /** - * Event to modify PM attachments download auth - * - * @event core.modify_pm_attach_download_auth - * @var bool allowed Whether the user is allowed to download from that PM or not - * @var int msg_id The id of the PM to download from - * @var int user_id The user id for auth check - * @since 3.1.11-RC1 - */ - $vars = array('allowed', 'msg_id', 'user_id'); - extract($phpbb_dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars))); - - if (!$allowed) - { - send_status_line(403, 'Forbidden'); - trigger_error('ERROR_NO_ATTACHMENT'); - } -} - -/** -* Checks whether a user can download from a particular PM -* -* @param \phpbb\db\driver\driver_interface $db The database object -* @param int $user_id The user id -* @param int $msg_id The id of the PM that we are downloading from -* -* @return bool Whether the user is allowed to download from that PM or not -*/ -function phpbb_download_check_pm_auth($db, $user_id, $msg_id) -{ - // Check if the attachment is within the users scope... - $sql = 'SELECT msg_id - FROM ' . PRIVMSGS_TO_TABLE . ' - WHERE msg_id = ' . (int) $msg_id . ' - AND ( - user_id = ' . (int) $user_id . ' - OR author_id = ' . (int) $user_id . ' - )'; - $result = $db->sql_query_limit($sql, 1); - $allowed = (bool) $db->sql_fetchfield('msg_id'); - $db->sql_freeresult($result); - - return $allowed; -} - -/** -* Check if the browser is internet explorer version 7+ -* -* @param string $user_agent User agent HTTP header -* @param int $version IE version to check against -* -* @return bool true if internet explorer version is greater than $version -*/ -function phpbb_is_greater_ie_version($user_agent, $version) -{ - if (preg_match('/msie (\d+)/', strtolower($user_agent), $matches)) - { - $ie_version = (int) $matches[1]; - return ($ie_version > $version); - } - else - { - return false; - } -} diff --git a/phpBB/includes/functions_posting.php b/phpBB/includes/functions_posting.php index 04ad81fc76..4ec032c0a9 100644 --- a/phpBB/includes/functions_posting.php +++ b/phpBB/includes/functions_posting.php @@ -817,7 +817,7 @@ function posting_gen_inline_attachments(&$attachment_data) */ function posting_gen_attachment_entry($attachment_data, &$filename_data, $show_attach_box = true) { - global $template, $config, $phpbb_root_path, $phpEx, $user, $phpbb_dispatcher; + global $template, $config, $phpbb_root_path, $phpEx, $user, $phpbb_dispatcher, $phpbb_container; // Some default template variables $template->assign_vars(array( @@ -845,7 +845,7 @@ function posting_gen_attachment_entry($attachment_data, &$filename_data, $show_a $hidden .= ''; } - $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'mode=view&id=' . (int) $attach_row['attach_id'], true, ($attach_row['is_orphan']) ? $user->session_id : false); + $download_link = $phpbb_container->get('controller.helper')->route('phpbb_storage_attachment', ['file' => (int) $attach_row['attach_id']]); $attachrow_template_vars[(int) $attach_row['attach_id']] = array( 'FILENAME' => utf8_basename($attach_row['real_filename']), diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index c463179227..056f2c0bfa 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -1073,6 +1073,7 @@ class bbcode_firstpass extends bbcode if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path) { // Ok, actually we allow linking to some files (this may be able to be extended in some way later...) + // @deprecated if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0) { return false; @@ -1534,6 +1535,8 @@ class parse_message extends bbcode_firstpass global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request; global $phpbb_container, $phpbb_dispatcher; + $controller_helper = $phpbb_container->get('controller.helper'); + $error = array(); $num_attachments = count($this->attachment_data); @@ -1776,7 +1779,7 @@ class parse_message extends bbcode_firstpass if (isset($this->plupload) && $this->plupload->is_active()) { - $download_url = append_sid("{$phpbb_root_path}download/file.{$phpEx}", 'mode=view&id=' . $new_entry['attach_id']); + $download_url = $controller_helper->route('phpbb_storage_attachment', ['file' => (int) $new_entry['attach_id']]); // Send the client the attachment data to maintain state $json_response->send(array('data' => $this->attachment_data, 'download_url' => $download_url)); diff --git a/phpBB/includes/ucp/ucp_attachments.php b/phpBB/includes/ucp/ucp_attachments.php index 7808fed325..ab91c4f9f8 100644 --- a/phpBB/includes/ucp/ucp_attachments.php +++ b/phpBB/includes/ucp/ucp_attachments.php @@ -14,6 +14,9 @@ /** * @ignore */ + +use phpbb\controller\helper; + if (!defined('IN_PHPBB')) { exit; @@ -31,6 +34,9 @@ class ucp_attachments { global $template, $user, $db, $config, $phpEx, $phpbb_root_path, $phpbb_container, $request, $auth; + /** @var helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + $start = $request->variable('start', 0); $sort_key = $request->variable('sk', 'a'); $sort_dir = $request->variable('sd', 'a'); @@ -179,7 +185,7 @@ class ucp_attachments 'S_IN_MESSAGE' => $row['in_message'], 'S_LOCKED' => !$row['in_message'] && !$auth->acl_get('m_edit', $row['forum_id']) && ($row['forum_status'] == ITEM_LOCKED || $row['topic_status'] == ITEM_LOCKED || $row['post_edit_locked']), - 'U_VIEW_ATTACHMENT' => append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $row['attach_id']), + 'U_VIEW_ATTACHMENT' => $controller_helper->route('phpbb_storage_attachment', ['file' => (int) $row['attach_id']]), 'U_VIEW_TOPIC' => $view_topic) ); diff --git a/phpBB/phpbb/avatar/driver/upload.php b/phpBB/phpbb/avatar/driver/upload.php index 99de843f8f..ced772822e 100644 --- a/phpBB/phpbb/avatar/driver/upload.php +++ b/phpBB/phpbb/avatar/driver/upload.php @@ -14,7 +14,13 @@ namespace phpbb\avatar\driver; use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\config\config; +use phpbb\controller\helper; +use phpbb\event\dispatcher_interface; +use phpbb\files\factory; +use phpbb\path_helper; use phpbb\storage\exception\exception as storage_exception; +use phpbb\storage\storage; /** * Handles avatars uploaded to the board @@ -22,17 +28,22 @@ use phpbb\storage\exception\exception as storage_exception; class upload extends \phpbb\avatar\driver\driver { /** - * @var \phpbb\storage\storage + * @var helper + */ + private $controller_helper; + + /** + * @var storage */ protected $storage; /** - * @var \phpbb\event\dispatcher_interface + * @var dispatcher_interface */ protected $dispatcher; /** - * @var \phpbb\files\factory + * @var factory */ protected $files_factory; @@ -42,20 +53,22 @@ class upload extends \phpbb\avatar\driver\driver protected $php_ini; /** - * Construct a driver object - * - * @param \phpbb\config\config $config phpBB configuration - * @param string $phpbb_root_path Path to the phpBB root - * @param string $php_ext PHP file extension - * @param \phpbb\storage\storage phpBB avatar storage - * @param \phpbb\path_helper $path_helper phpBB path helper - * @param \phpbb\event\dispatcher_interface $dispatcher phpBB Event dispatcher object - * @param \phpbb\files\factory $files_factory File classes factory - * @param IniGetWrapper $php_ini ini_get() wrapper - */ - public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\storage\storage $storage, \phpbb\path_helper $path_helper, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\files\factory $files_factory, IniGetWrapper $php_ini) + * Construct a driver object + * + * @param config $config phpBB configuration + * @param helper $controller_helper + * @param string $phpbb_root_path Path to the phpBB root + * @param string $php_ext PHP file extension + * @param storage $storage phpBB avatar storage + * @param path_helper $path_helper phpBB path helper + * @param dispatcher_interface $dispatcher phpBB Event dispatcher object + * @param factory $files_factory File classes factory + * @param IniGetWrapper $php_ini ini_get() wrapper + */ + public function __construct(config $config, helper $controller_helper, string $phpbb_root_path, string $php_ext, storage $storage, path_helper $path_helper, dispatcher_interface $dispatcher, factory $files_factory, IniGetWrapper $php_ini) { $this->config = $config; + $this->controller_helper = $controller_helper; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->storage = $storage; @@ -70,10 +83,8 @@ class upload extends \phpbb\avatar\driver\driver */ public function get_data($row) { - $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $this->path_helper->get_web_root_path(); - return array( - 'src' => $root_path . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'], + 'src' => $this->controller_helper->route('phpbb_storage_avatar', ['file' => $row['avatar']]), 'width' => $row['avatar_width'], 'height' => $row['avatar_height'], ); diff --git a/phpBB/phpbb/feed/helper.php b/phpBB/phpbb/feed/helper.php index 6d185271cc..8261027eb0 100644 --- a/phpBB/phpbb/feed/helper.php +++ b/phpBB/phpbb/feed/helper.php @@ -167,7 +167,9 @@ class helper $content .= implode('
', $post_attachments); // Convert attachments' relative path to absolute path - $content = str_replace($this->path_helper->get_web_root_path() . 'download/file.' . $this->path_helper->get_php_ext(), $this->get_board_url() . '/download/file.' . $this->path_helper->get_php_ext(), $content); + $pattern = '#(/app.php)?/download/attachment/#'; + $replacement = $this->get_board_url() . '\1/download/attachment/'; + $content = preg_replace($pattern, $replacement, $content); } // Remove Comments from inline attachments [ia] diff --git a/phpBB/phpbb/storage/controller/attachment.php b/phpBB/phpbb/storage/controller/attachment.php new file mode 100644 index 0000000000..5768fc7d82 --- /dev/null +++ b/phpBB/phpbb/storage/controller/attachment.php @@ -0,0 +1,532 @@ + + * @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\storage\controller; + +use phpbb\auth\auth; +use phpbb\cache\service; +use phpbb\config\config; +use phpbb\content_visibility; +use phpbb\db\driver\driver_interface; +use phpbb\event\dispatcher_interface; +use phpbb\exception\http_exception; +use phpbb\language\language; +use phpbb\request\request; +use phpbb\storage\storage; +use phpbb\user; +use Symfony\Component\HttpFoundation\Request as symfony_request; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Controller for /download/attachment/{id} routes + */ +class attachment extends controller +{ + /** @var auth */ + protected $auth; + + /** @var config */ + protected $config; + + /** @var content_visibility */ + protected $content_visibility; + + /** @var dispatcher_interface */ + protected $dispatcher; + + /** @var language */ + protected $language; + + /** @var request */ + protected $request; + + /** @var user */ + protected $user; + + /** + * Constructor + * + * @param auth $auth + * @param service $cache + * @param config $config + * @param content_visibility $content_visibility + * @param driver_interface $db + * @param dispatcher_interface $dispatcher + * @param language $language + * @param request $request + * @param storage $storage + * @param symfony_request $symfony_request + * @param user $user + */ + public function __construct(auth $auth, service $cache, config $config, content_visibility $content_visibility, driver_interface $db, dispatcher_interface $dispatcher, language $language, request $request, storage $storage, symfony_request $symfony_request, user $user) + { + parent::__construct($cache, $db, $storage, $symfony_request); + + $this->auth = $auth; + $this->config = $config; + $this->content_visibility = $content_visibility; + $this->dispatcher = $dispatcher; + $this->language = $language; + $this->request = $request; + $this->user = $user; + } + + /** + * {@inheritdoc} + */ + public function handle(string $file): Response + { + $attach_id = (int) $file; + $thumbnail = $this->request->variable('t', false); + + $this->language->add_lang('viewtopic'); + + if (!$this->config['allow_attachments'] && !$this->config['allow_pm_attach']) + { + throw new http_exception(404, 'ATTACHMENT_FUNCTIONALITY_DISABLED'); + } + + if (!$attach_id) + { + throw new http_exception(404, 'NO_ATTACHMENT_SELECTED'); + } + + $sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id, + is_orphan, physical_filename, real_filename, extension, mimetype, + filesize, filetime + FROM ' . ATTACHMENTS_TABLE . " + WHERE attach_id = $attach_id"; + $result = $this->db->sql_query($sql); + $attachment = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$attachment) + { + throw new http_exception(404, 'ERROR_NO_ATTACHMENT'); + } + else if (!$this->download_allowed()) + { + throw new http_exception(403, 'LINKAGE_FORBIDDEN'); + } + + $attachment['physical_filename'] = utf8_basename($attachment['physical_filename']); + + if ((!$attachment['in_message'] && !$this->config['allow_attachments']) || + ($attachment['in_message'] && !$this->config['allow_pm_attach'])) + { + throw new http_exception(404, 'ATTACHMENT_FUNCTIONALITY_DISABLED'); + } + + if ($attachment['is_orphan']) + { + // We allow admins having attachment permissions to see orphan attachments... + $own_attachment = $this->auth->acl_get('a_attach') || $attachment['poster_id'] == $this->user->data['user_id']; + + if (!$own_attachment || ($attachment['in_message'] && !$this->auth->acl_get('u_pm_download')) || + (!$attachment['in_message'] && !$this->auth->acl_get('u_download'))) + { + throw new http_exception(404, 'ERROR_NO_ATTACHMENT'); + } + + // Obtain all extensions... + $extensions = $this->cache->obtain_attach_extensions(true); + } + else + { + if (!$attachment['in_message']) + { + $this->phpbb_download_handle_forum_auth($attachment['topic_id']); + + $sql = 'SELECT forum_id, post_visibility + FROM ' . POSTS_TABLE . ' + WHERE post_id = ' . (int) $attachment['post_msg_id']; + $result = $this->db->sql_query($sql); + $post_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$post_row || !$this->content_visibility->is_visible('post', $post_row['forum_id'], $post_row)) + { + // Attachment of a soft deleted post and the user is not allowed to see the post + throw new http_exception(404, 'ERROR_NO_ATTACHMENT'); + } + } + else + { + // Attachment is in a private message. + $post_row = array('forum_id' => false); + $this->phpbb_download_handle_pm_auth( $attachment['post_msg_id']); + } + + $extensions = array(); + if (!extension_allowed($post_row['forum_id'], $attachment['extension'], $extensions)) + { + throw new http_exception(403, 'EXTENSION_DISABLED_AFTER_POSTING', [$attachment['extension']]); + } + } + + $display_cat = $extensions[$attachment['extension']]['display_cat']; + + if ($thumbnail) + { + $attachment['physical_filename'] = 'thumb_' . $attachment['physical_filename']; + } + else if ($display_cat == ATTACHMENT_CATEGORY_NONE && !$attachment['is_orphan']) + { + if (!(($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$this->user->optionget('viewimg'))) + { + // Update download count + $this->phpbb_increment_downloads($attachment['attach_id']); + } + } + + $redirect = ''; + + /** + * Event to modify data before sending file to browser + * + * @event core.download_file_send_to_browser_before + * @var int attach_id The attachment ID + * @var array attachment Array with attachment data + * @var array extensions Array with file extensions data + * @var bool thumbnail Flag indicating if the file is a thumbnail + * @var string redirect Do a redirection instead of reading the file + * @since 3.1.6-RC1 + * @changed 3.1.7-RC1 Fixing wrong name of a variable (replacing "extension" by "extensions") + * @changed 3.3.0-a1 Add redirect variable + * @changed 3.3.0-a1 Remove display_cat variable + * @changed 3.3.0-a1 Remove mode variable + */ + $vars = array( + 'attach_id', + 'attachment', + 'extensions', + 'thumbnail', + 'redirect', + ); + extract($this->dispatcher->trigger_event('core.download_file_send_to_browser_before', compact($vars))); + + // If the redirect variable have been overwritten, do redirect there + if (!empty($redirect)) + { + return new RedirectResponse($redirect); + } + + // Check if the file exists in the storage table too + if (!$this->storage->exists($attachment['physical_filename'])) + { + throw new http_exception(404, 'ERROR_NO_ATTACHMENT'); + } + + /** + * Event to alter attachment before it is sent to browser. + * + * @event core.send_file_to_browser_before + * @var array attachment Attachment data + * @since 3.1.11-RC1 + * @changed 3.3.0-a1 Removed category variable + * @changed 3.3.0-a1 Removed size variable + * @changed 3.3.0-a1 Removed filename variable + */ + $vars = array( + 'attachment', + ); + extract($this->dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars))); + + // TODO: The next lines should go better in prepare, also the mimetype is handled by the storage table + // so probably can be removed + + $response = new StreamedResponse(); + + // Content-type header + $response->headers->set('Content-Type', $attachment['mimetype']); + + // Display images in browser and force download for other file types + if (strpos($attachment['mimetype'], 'image') !== false) + { + $disposition = $response->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_INLINE, + $attachment['real_filename'], + $this->filenameFallback($attachment['real_filename']) + ); + } + else + { + $disposition = $response->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + $attachment['real_filename'], + $this->filenameFallback($attachment['real_filename']) + ); + } + + $response->headers->set('Content-Disposition', $disposition); + + // Set expires header for browser cache + $time = new \Datetime(); + $response->setExpires($time->modify('+1 year')); + + return parent::handle($attachment['physical_filename']); + } + + /** + * Remove non valid characters https://github.com/symfony/http-foundation/commit/c7df9082ee7205548a97031683bc6550b5dc9551 + */ + protected function filenameFallback($filename) + { + $filename = preg_replace(['/[^\x20-\x7e]/', '/%/', '/\//', '/\\\/'], '', $filename); + + return (!empty($filename)) ?: 'File'; + } + + /** + * {@inheritdoc} + */ + protected function prepare(StreamedResponse $response, string $file): void + { + $response->setPrivate(); // By default should be private, but make sure of it + + parent::prepare($response, $file); + } + + /** + * Handles authentication when downloading attachments from a post or topic + * + * @param int $topic_id The id of the topic that we are downloading from + * + * @return void + * @throws http_exception If attachment is not found + * If user don't have permission to download the attachment + */ + protected function phpbb_download_handle_forum_auth(int $topic_id): void + { + $sql_array = array( + 'SELECT' => 't.topic_visibility, t.forum_id, f.forum_name, f.forum_password, f.parent_id', + 'FROM' => array( + TOPICS_TABLE => 't', + FORUMS_TABLE => 'f', + ), + 'WHERE' => 't.topic_id = ' . (int) $topic_id . ' + AND t.forum_id = f.forum_id', + ); + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row && !$this->content_visibility->is_visible('topic', $row['forum_id'], $row)) + { + throw new http_exception(404, 'ERROR_NO_ATTACHMENT'); + } + else if ($row && $this->auth->acl_get('u_download') && $this->auth->acl_get('f_download', $row['forum_id'])) + { + if ($row['forum_password']) + { + // Do something else ... ? + login_forum_box($row); + } + } + else + { + throw new http_exception(403, 'SORRY_AUTH_VIEW_ATTACH'); + } + } + + /** + * Handles authentication when downloading attachments from PMs + * + * @param int $msg_id The id of the PM that we are downloading from + * + * @return void + * @throws http_exception If attachment is not found + */ + protected function phpbb_download_handle_pm_auth(int $msg_id): void + { + if (!$this->auth->acl_get('u_pm_download')) + { + throw new http_exception(403, 'SORRY_AUTH_VIEW_ATTACH'); + } + + $allowed = $this->phpbb_download_check_pm_auth($msg_id); + + /** + * Event to modify PM attachments download auth + * + * @event core.modify_pm_attach_download_auth + * @var bool allowed Whether the user is allowed to download from that PM or not + * @var int msg_id The id of the PM to download from + * @var int user_id The user id for auth check + * @since 3.1.11-RC1 + */ + $vars = array('allowed', 'msg_id', 'user_id'); + extract($this->dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars))); + + if (!$allowed) + { + throw new http_exception(403, 'ERROR_NO_ATTACHMENT'); + } + } + + /** + * Checks whether a user can download from a particular PM + * + * @param int $msg_id The id of the PM that we are downloading from + * + * @return bool Whether the user is allowed to download from that PM or not + */ + protected function phpbb_download_check_pm_auth(int $msg_id): bool + { + $user_id = $this->user->data['user_id']; + + // Check if the attachment is within the users scope... + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE msg_id = ' . (int) $msg_id . ' + AND ( + user_id = ' . (int) $user_id . ' + OR author_id = ' . (int) $user_id . ' + )'; + $result = $this->db->sql_query_limit($sql, 1); + $allowed = (bool) $this->db->sql_fetchfield('msg_id'); + $this->db->sql_freeresult($result); + + return $allowed; + } + + /** + * Increments the download count of all provided attachments + * + * @param int $id The attach_id of the attachment + * + * @return void + */ + protected function phpbb_increment_downloads(int $id): void + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET download_count = download_count + 1 + WHERE attach_id = ' . $id; + $this->db->sql_query($sql); + } + + /** + * Check if downloading item is allowed + * FIXME (See: https://tracker.phpbb.com/browse/PHPBB3-15264 and http://area51.phpbb.com/phpBB/viewtopic.php?f=81&t=51921) + */ + protected function download_allowed(): bool + { + if (!$this->config['secure_downloads']) + { + return true; + } + + $url = htmlspecialchars_decode($this->request->header('Referer')); + + if (!$url) + { + return ($this->config['secure_allow_empty_referer']) ? true : false; + } + + // Split URL into domain and script part + $url = @parse_url($url); + + if ($url === false) + { + return ($this->config['secure_allow_empty_referer']) ? true : false; + } + + $hostname = $url['host']; + unset($url); + + $allowed = ($this->config['secure_allow_deny']) ? false : true; + $iplist = array(); + + if (($ip_ary = @gethostbynamel($hostname)) !== false) + { + foreach ($ip_ary as $ip) + { + if ($ip) + { + $iplist[] = $ip; + } + } + } + + // Check for own server... + $server_name = $this->user->host; + + // Forcing server vars is the only way to specify/override the protocol + if ($this->config['force_server_vars'] || !$server_name) + { + $server_name = $this->config['server_name']; + } + + if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname)) + { + $allowed = true; + } + + // Get IP's and Hostnames + if (!$allowed) + { + $sql = 'SELECT site_ip, site_hostname, ip_exclude + FROM ' . SITELIST_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $site_ip = trim($row['site_ip']); + $site_hostname = trim($row['site_hostname']); + + if ($site_ip) + { + foreach ($iplist as $ip) + { + if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip)) + { + if ($row['ip_exclude']) + { + $allowed = ($this->config['secure_allow_deny']) ? false : true; + break 2; + } + else + { + $allowed = ($this->config['secure_allow_deny']) ? true : false; + } + } + } + } + + if ($site_hostname) + { + if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname)) + { + if ($row['ip_exclude']) + { + $allowed = ($this->config['secure_allow_deny']) ? false : true; + break; + } + else + { + $allowed = ($this->config['secure_allow_deny']) ? true : false; + } + } + } + } + $this->db->sql_freeresult($result); + } + + return $allowed; + } +} diff --git a/phpBB/phpbb/storage/controller/avatar.php b/phpBB/phpbb/storage/controller/avatar.php new file mode 100644 index 0000000000..aaf347fd79 --- /dev/null +++ b/phpBB/phpbb/storage/controller/avatar.php @@ -0,0 +1,115 @@ + + * @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\storage\controller; + +use phpbb\cache\service; +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use phpbb\storage\storage; +use Symfony\Component\HttpFoundation\Request as symfony_request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Controller for /download/avatar/{file} routes + */ +class avatar extends controller +{ + /** @var config */ + protected $config; + + /** @var array */ + protected $allowed_extensions = ['png', 'gif', 'jpg', 'jpeg']; + + /** + * Constructor + * + * @param service $cache + * @param config $config + * @param driver_interface $db + * @param storage $storage + * @param symfony_request $symfony_request + */ + public function __construct(service $cache, config $config, driver_interface $db, storage $storage, symfony_request $symfony_request) + { + parent::__construct($cache, $db, $storage, $symfony_request); + + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function handle(string $file): Response + { + $file = $this->decode_filename($file); + + return parent::handle($file); + } + + /** + * {@inheritdoc} + */ + protected function is_allowed(string $file): bool + { + $ext = substr(strrchr($file, '.'), 1); + + // If filename have point and have an allowed extension + return strpos($file, '.') && in_array($ext, $this->allowed_extensions, true); + } + + /** + * Decode avatar filename + * + * @param string $file Filename + * + * @return string Filename in filesystem + */ + protected function decode_filename(string $file): string + { + $avatar_group = false; + + if (isset($file[0]) && $file[0] === 'g') + { + $avatar_group = true; + $file = substr($file, 1); + } + + $ext = substr(strrchr($file, '.'), 1); + $file = (int) $file; + + return $this->config['avatar_salt'] . '_' . ($avatar_group ? 'g' : '') . $file . '.' . $ext; + } + + /** + * {@inheritdoc} + */ + protected function prepare(StreamedResponse $response, string $file): void + { + $response->setPublic(); + + $disposition = $response->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_INLINE, + rawurlencode($file) + ); + + $response->headers->set('Content-Disposition', $disposition); + + $time = new \Datetime(); + $response->setExpires($time->modify('+1 year')); + + parent::prepare($response, $file); + } +} diff --git a/phpBB/phpbb/storage/controller/controller.php b/phpBB/phpbb/storage/controller/controller.php new file mode 100644 index 0000000000..54f95aa224 --- /dev/null +++ b/phpBB/phpbb/storage/controller/controller.php @@ -0,0 +1,188 @@ + + * @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\storage\controller; + +use phpbb\cache\service; +use phpbb\db\driver\driver_interface; +use phpbb\exception\http_exception; +use phpbb\storage\exception\exception; +use phpbb\storage\storage; +use Symfony\Component\HttpFoundation\Request as symfony_request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Generic controller for storage + */ +class controller +{ + /** @var service */ + protected $cache; + + /** @var driver_interface */ + protected $db; + + /** @var storage */ + protected $storage; + + /** @var symfony_request */ + protected $symfony_request; + + /** + * Constructor + * + * @param service $cache + * @param driver_interface $db + * @param storage $storage + * @param symfony_request $symfony_request + */ + public function __construct(service $cache, driver_interface $db, storage $storage, symfony_request $symfony_request) + { + $this->cache = $cache; + $this->db = $db; + $this->storage = $storage; + $this->symfony_request = $symfony_request; + } + + /** + * Handler + * + * @param string $file File path + * + * @return Response a Symfony response object + * + * @throws http_exception when can't access $file + * @throws exception when there is an error reading the file + */ + public function handle(string $file): Response + { + $response = new StreamedResponse(); + + if (!static::is_allowed($file)) + { + throw new http_exception(403, 'Forbidden'); + } + + if (!static::file_exists($file)) + { + throw new http_exception(404, 'Not Found'); + } + + static::prepare($response, $file); + + if (headers_sent()) + { + throw new http_exception(500, 'Headers already sent'); + } + + return $response; + } + + /** + * If the user is allowed to download the file + * + * @param string $file File path + * + * @return bool + */ + protected function is_allowed(string $file): bool + { + return true; + } + + /** + * Check if file exists + * + * @param string $file File path + * + * @return bool + */ + protected function file_exists(string $file): bool + { + return $this->storage->exists($file); + } + + /** + * Prepare response + * + * @param StreamedResponse $response + * @param string $file File path + * + * @return void + * @throws exception when there is an error reading the file + */ + protected function prepare(StreamedResponse $response, string $file): void + { + $file_info = $this->storage->file_info($file); + + // Add Content-Type header + if (!$response->headers->has('Content-Type')) + { + try + { + $content_type = $file_info->get('mimetype'); + } + catch (exception $e) + { + $content_type = 'application/octet-stream'; + } + + $response->headers->set('Content-Type', $content_type); + } + + // Add Content-Length header if we have the file size + if (!$response->headers->has('Content-Length')) + { + try + { + $response->headers->set('Content-Length', $file_info->get('size')); + } + catch (exception $e) + { + // Just don't send this header + } + } + + @set_time_limit(0); + + $fp = $this->storage->read_stream($file); + + // Close db connection + $this->file_gc(); + + $output = fopen('php://output', 'w+b'); + + $response->setCallback(function () use ($fp, $output) { + stream_copy_to_stream($fp, $output); + fclose($fp); + fclose($output); + flush(); + + // Terminate script to avoid the execution of terminate events + // This avoid possible errors with db connection closed + exit; + }); + + $response->isNotModified($this->symfony_request); + } + + /** + * Garbage Collection + */ + protected function file_gc(): void + { + $this->cache->unload(); // Equivalent to $this->cache->get_driver()->unload(); + $this->db->sql_close(); + } +} diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php index 8e5f0f4025..572d434319 100644 --- a/tests/avatar/manager_test.php +++ b/tests/avatar/manager_test.php @@ -56,6 +56,8 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case $dispatcher = new phpbb_mock_event_dispatcher(); + $controller_helper = $this->createMock('\phpbb\controller\helper'); + // $this->avatar_foobar will be needed later on $this->avatar_foobar = $this->getMockBuilder('\phpbb\avatar\driver\foobar') ->setMethods(array('get_name')) @@ -93,7 +95,7 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case { $cur_avatar = $this->getMockBuilder('\phpbb\avatar\driver\\' . $driver) ->setMethods(array('get_name')) - ->setConstructorArgs(array($this->config, $phpbb_root_path, $phpEx, $storage, $path_helper, $dispatcher, $files_factory, $php_ini)) + ->setConstructorArgs(array($this->config, $controller_helper, $phpbb_root_path, $phpEx, $storage, $path_helper, $dispatcher, $files_factory, $php_ini)) ->getMock(); } $cur_avatar->expects($this->any()) diff --git a/tests/download/http_byte_range_test.php b/tests/download/http_byte_range_test.php deleted file mode 100644 index b138c4da30..0000000000 --- a/tests/download/http_byte_range_test.php +++ /dev/null @@ -1,116 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -require_once __DIR__ . '/../../phpBB/includes/functions_download.php'; - -class phpbb_download_http_byte_range_test extends phpbb_test_case -{ - public function test_find_range_request() - { - // Missing 'bytes=' prefix - $GLOBALS['request'] = new phpbb_mock_request(); - $GLOBALS['request']->set_header('Range', 'bztes='); - $this->assertEquals(false, phpbb_find_range_request()); - unset($GLOBALS['request']); - - $GLOBALS['request'] = new phpbb_mock_request(); - $_ENV['HTTP_RANGE'] = 'bztes='; - $this->assertEquals(false, phpbb_find_range_request()); - unset($_ENV['HTTP_RANGE']); - - $GLOBALS['request'] = new phpbb_mock_request(); - $GLOBALS['request']->set_header('Range', 'bytes=0-0,123-125'); - $this->assertEquals(array('0-0', '123-125'), phpbb_find_range_request()); - unset($GLOBALS['request']); - } - - /** - * @dataProvider parse_range_request_data() - */ - public function test_parse_range_request($request_array, $filesize, $expected) - { - $this->assertEquals($expected, phpbb_parse_range_request($request_array, $filesize)); - } - - public function parse_range_request_data() - { - return array( - // Valid request - array( - array('3-4'), - 10, - array( - 'byte_pos_start' => 3, - 'byte_pos_end' => 4, - 'bytes_requested' => 2, - 'bytes_total' => 10, - ), - ), - - // Get the beginning - array( - array('-5'), - 10, - array( - 'byte_pos_start' => 0, - 'byte_pos_end' => 5, - 'bytes_requested' => 6, - 'bytes_total' => 10, - ), - ), - - // Get the end - array( - array('5-'), - 10, - array( - 'byte_pos_start' => 5, - 'byte_pos_end' => 9, - 'bytes_requested' => 5, - 'bytes_total' => 10, - ), - ), - - // Overlong request - array( - array('3-20'), - 10, - array( - 'byte_pos_start' => 3, - 'byte_pos_end' => 9, - 'bytes_requested' => 7, - 'bytes_total' => 10, - ), - ), - - // Multiple, contiguous range - array( - array('10-20', '21-30'), - 125, - array( - 'byte_pos_start' => 10, - 'byte_pos_end' => 30, - 'bytes_requested' => 21, - 'bytes_total' => 125, - ) - ), - - // We don't do multiple, non-contiguous range - array( - array('0-0', '120-125'), - 125, - false, - ), - ); - } -} diff --git a/tests/download/http_user_agent_test.php b/tests/download/http_user_agent_test.php deleted file mode 100644 index 74a626432f..0000000000 --- a/tests/download/http_user_agent_test.php +++ /dev/null @@ -1,134 +0,0 @@ - -* @license GNU General Public License, version 2 (GPL-2.0) -* -* For full copyright and license information, please see -* the docs/CREDITS.txt file. -* -*/ - -require_once __DIR__ . '/../../phpBB/includes/functions_download.php'; - -class phpbb_download_http_user_agent_test extends phpbb_test_case -{ - public function user_agents_check_greater_ie_version() - { - return array( - // user agent - // IE version - // expected - array( - 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)', - 7, - true, - ), - array( - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', - 7, - true, - ), - array( - 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)', - 7, - true, - ), - array( - 'Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)', - 7, - false, - ), - array( - 'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', - 7, - false, - ), - array( - 'Mozilla/4.0 (compatible; MSIE 6.01; Windows NT 6.0)', - 7, - false, - ), - array( - 'Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)', - 7, - false, - ), - array( - 'Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0', - 7, - false, - ), - array( - 'Mozilla/5.0 (Windows NT 6.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1464.0 Safari/537.36', - 7, - false, - ), - array( - 'Googlebot-Image/1.0', - 7, - false, - ), - array( - 'Googlebot/2.1 ( http://www.google.com/bot.html)', - 7, - false, - ), - array( - 'Lynx/2.8.3dev.9 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.6', - 7, - false, - ), - array( - 'Links (0.9x; Linux 2.4.7-10 i686)', - 7, - false, - ), - array( - 'Opera/9.60 (Windows NT 5.1; U; de) Presto/2.1.1', - 7, - false, - ), - array( - 'Mozilla/4.0 (compatible; MSIE 5.0; Windows NT;)', - 7, - false, - ), - array( - 'Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 4.0) Opera 6.01 [en]', - 7, - false, - ), - array( - 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.24', - 7, - false, - ), - array( - 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)', - 8, - true, - ), - array( - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', - 9, - true, - ), - array( - 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)', - 10, - false, - ), - ); - } - - /** - * @dataProvider user_agents_check_greater_ie_version - */ - public function test_is_greater_ie_version($user_agent, $version, $expected) - { - $this->assertEquals($expected, phpbb_is_greater_ie_version($user_agent, $version)); - } -} diff --git a/tests/functional/download_test.php b/tests/functional/download_test.php index 7eed292052..d96092efb2 100644 --- a/tests/functional/download_test.php +++ b/tests/functional/download_test.php @@ -83,7 +83,7 @@ class phpbb_functional_download_test extends phpbb_functional_test_case )); // Download attachment as guest - $crawler = self::request('GET', "download/file.php?id={$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); + $crawler = self::request('GET', "download/attachment/{$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); self::assert_response_status_code(200); $content = self::$client->getResponse()->getContent(); $finfo = new finfo(FILEINFO_MIME_TYPE); @@ -141,7 +141,7 @@ class phpbb_functional_download_test extends phpbb_functional_test_case $this->add_lang('viewtopic'); // No download attachment as guest - $crawler = self::request('GET', "download/file.php?id={$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); + $crawler = self::request('GET', "download/attachment/{$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); self::assert_response_html(404); $this->assertContainsLang('ERROR_NO_ATTACHMENT', $crawler->filter('#message')->text()); @@ -149,7 +149,7 @@ class phpbb_functional_download_test extends phpbb_functional_test_case $this->login(); // Download attachment as admin - $crawler = self::request('GET', "download/file.php?id={$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); + $crawler = self::request('GET', "download/attachment/{$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); self::assert_response_status_code(200); $content = self::$client->getResponse()->getContent(); $finfo = new finfo(FILEINFO_MIME_TYPE); @@ -208,7 +208,7 @@ class phpbb_functional_download_test extends phpbb_functional_test_case $this->add_lang('viewtopic'); // No download attachment as guest - $crawler = self::request('GET', "download/file.php?id={$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); + $crawler = self::request('GET', "download/attachment/{$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); self::assert_response_html(404); $this->assertContainsLang('ERROR_NO_ATTACHMENT', $crawler->filter('#message')->text()); @@ -216,7 +216,7 @@ class phpbb_functional_download_test extends phpbb_functional_test_case $this->login(); // Download attachment as admin - $crawler = self::request('GET', "download/file.php?id={$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); + $crawler = self::request('GET', "download/attachment/{$this->data['attachments'][$this->data['posts']['Re: Download Topic #1-#2']]}", array(), false); self::assert_response_status_code(200); $content = self::$client->getResponse()->getContent(); $finfo = new finfo(FILEINFO_MIME_TYPE); diff --git a/tests/functional/feed_test.php b/tests/functional/feed_test.php index 159703744f..4c1f05542d 100644 --- a/tests/functional/feed_test.php +++ b/tests/functional/feed_test.php @@ -1417,7 +1417,7 @@ class phpbb_functional_feed_test extends phpbb_functional_test_case $content = $crawler->filterXPath("//entry[{$entry_id}]/content")->text(); foreach ($attachments as $i => $attachment) { - $url = self::$root_url . "download/file.php?id={$attachment['id']}"; + $url = self::$root_url . "app.php/download/attachment/{$attachment['id']}"; $string = "Attachment #{$i}"; if ($attachment['displayed']) diff --git a/tests/template/extension_test.php b/tests/template/extension_test.php index b53efea0bb..156c7e2563 100644 --- a/tests/template/extension_test.php +++ b/tests/template/extension_test.php @@ -11,6 +11,8 @@ * */ +use phpbb\controller\helper; + require_once __DIR__ . '/template_test_case.php'; class phpbb_template_extension_test extends phpbb_template_template_test_case @@ -66,10 +68,17 @@ class phpbb_template_extension_test extends phpbb_template_template_test_case ->disableOriginalConstructor() ->getMock(); + $controller_helper = $this->createMock(helper::class); + $controller_helper + ->method('route') + ->willReturnCallback(function($route, $params) { + return 'download/avatar/' . $params['file']; + }); + $phpbb_dispatcher = new phpbb_mock_event_dispatcher(); $phpbb_container = new phpbb_mock_container_builder(); $files = new phpbb\files\factory($phpbb_container); - $upload_avatar_driver = new phpbb\avatar\driver\upload($config, $phpbb_root_path, $phpEx, $storage, $phpbb_path_helper, $phpbb_dispatcher, $files, new \bantu\IniGetWrapper\IniGetWrapper()); + $upload_avatar_driver = new phpbb\avatar\driver\upload($config, $controller_helper, $phpbb_root_path, $phpEx, $storage, $phpbb_path_helper, $phpbb_dispatcher, $files, new \bantu\IniGetWrapper\IniGetWrapper()); $upload_avatar_driver->set_name('avatar.driver.upload'); $phpbb_container->set('avatar.manager', new \phpbb\avatar\manager($config, $phpbb_dispatcher, [ $upload_avatar_driver, @@ -141,7 +150,7 @@ class phpbb_template_extension_test extends phpbb_template_template_test_case ], [], [], - 'foo', + 'foo', [] ], [ @@ -159,7 +168,7 @@ class phpbb_template_extension_test extends phpbb_template_template_test_case ], [], [], - 'foo', + 'foo', [] ], [ diff --git a/tests/test_framework/phpbb_functional_test_case.php b/tests/test_framework/phpbb_functional_test_case.php index c45107f47d..75330ba8cd 100644 --- a/tests/test_framework/phpbb_functional_test_case.php +++ b/tests/test_framework/phpbb_functional_test_case.php @@ -47,7 +47,6 @@ class phpbb_functional_test_case extends phpbb_test_case parent::setUpBeforeClass(); self::$config = phpbb_test_case_helpers::get_test_config(); - self::$root_url = self::$config['phpbb_functional_url']; // Important: this is used both for installation and by // test cases for querying the tables. @@ -60,6 +59,8 @@ class phpbb_functional_test_case extends phpbb_test_case self::markTestSkipped('phpbb_functional_url was not set in test_config and wasn\'t set as PHPBB_FUNCTIONAL_URL environment variable either.'); } + self::$root_url = self::$config['phpbb_functional_url']; + if (!self::$already_installed) { self::install_board();