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 '

';
+ 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 '

';
- 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
],
[],
[],
- '
',
+ '
',
[]
],
[
@@ -159,7 +168,7 @@ class phpbb_template_extension_test extends phpbb_template_template_test_case
],
[],
[],
- '
',
+ '
',
[]
],
[
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();