mirror of
https://github.com/phpbb/phpbb.git
synced 2025-07-31 05:50:42 +02:00
Merge pull request #5274 from rubencm/ticket/14285
[ticket/14285] Move downloads to controller
This commit is contained in:
@@ -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'],
|
||||
);
|
||||
|
@@ -167,7 +167,9 @@ class helper
|
||||
$content .= implode('<br />', $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]
|
||||
|
532
phpBB/phpbb/storage/controller/attachment.php
Normal file
532
phpBB/phpbb/storage/controller/attachment.php
Normal file
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the phpBB Forum Software package.
|
||||
*
|
||||
* @copyright (c) phpBB Limited <https://www.phpbb.com>
|
||||
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||
*
|
||||
* For full copyright and license information, please see
|
||||
* the docs/CREDITS.txt file.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace phpbb\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;
|
||||
}
|
||||
}
|
115
phpBB/phpbb/storage/controller/avatar.php
Normal file
115
phpBB/phpbb/storage/controller/avatar.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the phpBB Forum Software package.
|
||||
*
|
||||
* @copyright (c) phpBB Limited <https://www.phpbb.com>
|
||||
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||
*
|
||||
* For full copyright and license information, please see
|
||||
* the docs/CREDITS.txt file.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace phpbb\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);
|
||||
}
|
||||
}
|
188
phpBB/phpbb/storage/controller/controller.php
Normal file
188
phpBB/phpbb/storage/controller/controller.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the phpBB Forum Software package.
|
||||
*
|
||||
* @copyright (c) phpBB Limited <https://www.phpbb.com>
|
||||
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||
*
|
||||
* For full copyright and license information, please see
|
||||
* the docs/CREDITS.txt file.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace phpbb\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();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user