2009-05-15 20:04:58 +01:00
|
|
|
<?php
|
2019-10-19 01:57:36 +01:00
|
|
|
// This file is part of GNU social - https://www.gnu.org/software/social
|
|
|
|
//
|
|
|
|
// GNU social is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// GNU social is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Affero General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
|
|
// along with GNU social. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
defined('GNUSOCIAL') || die();
|
2009-05-15 20:04:58 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Show notice attachments
|
|
|
|
*
|
|
|
|
* @category Personal
|
2009-08-25 23:12:20 +01:00
|
|
|
* @package StatusNet
|
2009-08-25 23:19:04 +01:00
|
|
|
* @author Evan Prodromou <evan@status.net>
|
2009-05-15 20:04:58 +01:00
|
|
|
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
|
2009-08-25 23:16:46 +01:00
|
|
|
* @link http://status.net/
|
2009-05-15 20:04:58 +01:00
|
|
|
*/
|
2014-05-18 11:57:46 +01:00
|
|
|
class AttachmentAction extends ManagedAction
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
|
|
|
/**
|
[MEDIA] File downloader now in PHP, added proper name in the UI and changed the format for new attachment file names
The file downloader was changed from a simple redirect to the file to one
implemented in PHP, which should make it safer, by making it possible disallow
direct access to the file, to prevent executing of atttachments
The filename has a new format:
bin2hex("{$original_name}")."-{$filehash}"
This format should be respected. Notice the dash, which is important to distinguish it from the previous
format, which was "{$hash}.{$ext}"
This change was made to both make the experience more user friendly, by
providing a readable name for files, as opposed to it's hash. This name is taken
from the upload filename, but, clearly, as this wasn't done before, it's
impossible to have a proper name for older files, so those are displayed as
"untitled.{$ext}".
This new name is displayed in the UI, instead of the previous name.
2019-06-11 02:42:33 +01:00
|
|
|
* Attachment File object to show
|
2009-05-15 20:04:58 +01:00
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public $attachment = null;
|
|
|
|
|
|
|
|
public $filehash = null;
|
|
|
|
public $filepath = null;
|
|
|
|
public $filesize = null;
|
|
|
|
public $mimetype = null;
|
|
|
|
public $filename = null;
|
2009-05-15 20:04:58 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Load attributes based on database arguments
|
|
|
|
*
|
|
|
|
* Loads all the DB stuff
|
|
|
|
*
|
|
|
|
* @param array $args $_REQUEST array
|
|
|
|
*
|
2019-10-19 01:57:36 +01:00
|
|
|
* @return bool flag
|
|
|
|
* @throws ClientException
|
|
|
|
* @throws FileNotFoundException
|
|
|
|
* @throws FileNotStoredLocallyException
|
|
|
|
* @throws InvalidFilenameException
|
|
|
|
* @throws ServerException
|
2009-05-15 20:04:58 +01:00
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
protected function prepare(array $args = [])
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
|
|
|
parent::prepare($args);
|
|
|
|
|
2019-06-28 00:18:27 +01:00
|
|
|
try {
|
|
|
|
if (!empty($id = $this->trimmed('attachment'))) {
|
|
|
|
$this->attachment = File::getByID($id);
|
2019-10-19 01:57:36 +01:00
|
|
|
} elseif (!empty($this->filehash = $this->trimmed('filehash'))) {
|
|
|
|
$this->attachment = File::getByHash($this->filehash);
|
2019-06-28 00:18:27 +01:00
|
|
|
}
|
|
|
|
} catch (Exception $e) {
|
|
|
|
// Not found
|
2009-05-24 09:43:34 +01:00
|
|
|
}
|
2014-05-18 11:57:46 +01:00
|
|
|
if (!$this->attachment instanceof File) {
|
2010-10-30 23:58:35 +01:00
|
|
|
// TRANS: Client error displayed trying to get a non-existing attachment.
|
2019-10-19 01:57:36 +01:00
|
|
|
$this->clientError(_m('No such attachment.'), 404);
|
2019-06-29 20:10:20 +01:00
|
|
|
}
|
|
|
|
|
2019-10-19 01:57:36 +01:00
|
|
|
$this->filepath = $this->attachment->getFileOrThumbnailPath();
|
|
|
|
if (empty($this->filepath)) {
|
|
|
|
$this->clientError(_m('Requested local URL for a file that is not stored locally.'), 404);
|
2009-05-15 20:04:58 +01:00
|
|
|
}
|
2019-10-19 01:57:36 +01:00
|
|
|
$this->filesize = $this->attachment->getFileOrThumbnailSize();
|
|
|
|
$this->mimetype = $this->attachment->getFileOrThumbnailMimetype();
|
|
|
|
$this->filename = MediaFile::getDisplayName($this->attachment);
|
|
|
|
|
2009-05-15 20:04:58 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is this action read-only?
|
|
|
|
*
|
2019-10-19 01:57:36 +01:00
|
|
|
* @return bool true
|
2009-05-15 20:04:58 +01:00
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function isReadOnly($args): bool
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Title of the page
|
|
|
|
*
|
|
|
|
* @return string title of the page
|
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function title(): string
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
|
|
|
$a = new Attachment($this->attachment);
|
|
|
|
return $a->title();
|
|
|
|
}
|
|
|
|
|
2019-10-19 01:57:36 +01:00
|
|
|
public function showPage(): void
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
2019-10-19 01:57:36 +01:00
|
|
|
if (empty($this->filepath)) {
|
2009-06-25 23:42:17 +01:00
|
|
|
// if it's not a local file, gtfo
|
2016-03-07 21:33:34 +00:00
|
|
|
common_redirect($this->attachment->getUrl(), 303);
|
2009-06-25 23:42:17 +01:00
|
|
|
}
|
2014-04-21 10:35:42 +01:00
|
|
|
|
2015-01-25 01:25:28 +00:00
|
|
|
parent::showPage();
|
2009-05-15 20:04:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Fill the content area of the page
|
|
|
|
*
|
|
|
|
* Shows a single notice list item.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function showContent(): void
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
|
|
|
$ali = new Attachment($this->attachment, $this);
|
2019-10-19 01:57:36 +01:00
|
|
|
$ali->show();
|
2009-05-15 20:04:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Don't show page notice
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function showPageNoticeBlock(): void
|
2009-05-15 20:04:58 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show aside: this attachments appears in what notices
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function showSections(): void
|
|
|
|
{
|
2009-05-18 03:06:08 +01:00
|
|
|
$ns = new AttachmentNoticeSection($this);
|
|
|
|
$ns->show();
|
2009-05-15 20:04:58 +01:00
|
|
|
}
|
2019-06-26 03:25:59 +01:00
|
|
|
|
2019-06-26 03:39:39 +01:00
|
|
|
/**
|
|
|
|
* Last-modified date for file
|
|
|
|
*
|
|
|
|
* @return int last-modified date as unix timestamp
|
2019-10-19 01:57:36 +01:00
|
|
|
* @throws ServerException
|
2019-06-26 03:39:39 +01:00
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function lastModified(): ?int
|
2019-06-26 03:39:39 +01:00
|
|
|
{
|
|
|
|
if (common_config('site', 'use_x_sendfile')) {
|
|
|
|
return null;
|
|
|
|
}
|
2019-10-19 01:57:36 +01:00
|
|
|
$path = $this->filepath;
|
2019-06-29 20:10:20 +01:00
|
|
|
if (!empty($path)) {
|
|
|
|
return filemtime($path);
|
|
|
|
} else {
|
2019-06-28 00:18:27 +01:00
|
|
|
return null;
|
|
|
|
}
|
2019-06-26 03:39:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* etag header for file
|
|
|
|
*
|
|
|
|
* This returns the same data (inode, size, mtime) as Apache would,
|
|
|
|
* but in decimal instead of hex.
|
|
|
|
*
|
|
|
|
* @return string etag http header
|
2019-10-19 01:57:36 +01:00
|
|
|
* @throws ServerException
|
2019-06-26 03:39:39 +01:00
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function etag(): string
|
2019-06-26 03:39:39 +01:00
|
|
|
{
|
|
|
|
if (common_config('site', 'use_x_sendfile')) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-10-19 01:57:36 +01:00
|
|
|
$path = $this->filepath;
|
2019-06-29 20:10:20 +01:00
|
|
|
|
2019-06-26 03:39:39 +01:00
|
|
|
$cache = Cache::instance();
|
2019-10-19 01:57:36 +01:00
|
|
|
if ($cache) {
|
2019-06-29 20:10:20 +01:00
|
|
|
if (empty($path)) {
|
2019-06-28 00:18:27 +01:00
|
|
|
return null;
|
2019-06-26 03:39:39 +01:00
|
|
|
}
|
2019-06-29 20:10:20 +01:00
|
|
|
$key = Cache::key('attachments:etag:' . $path);
|
|
|
|
$etag = $cache->get($key);
|
2019-10-19 01:57:36 +01:00
|
|
|
if ($etag === false) {
|
2019-06-29 20:10:20 +01:00
|
|
|
$etag = crc32(file_get_contents($path));
|
2019-10-19 01:57:36 +01:00
|
|
|
$cache->set($key, $etag);
|
2019-06-29 20:10:20 +01:00
|
|
|
}
|
2019-06-26 03:39:39 +01:00
|
|
|
return $etag;
|
|
|
|
}
|
|
|
|
|
2019-06-29 20:10:20 +01:00
|
|
|
if (!empty($path)) {
|
|
|
|
$stat = stat($path);
|
|
|
|
return '"' . $stat['ino'] . '-' . $stat['size'] . '-' . $stat['mtime'] . '"';
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
2019-06-26 03:39:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-02 22:35:05 +01:00
|
|
|
* Include $filepath in the response, for viewing and downloading.
|
|
|
|
* If provided, $filesize is used to size the HTTP request,
|
|
|
|
* otherwise it's value is calculated
|
2019-10-19 01:57:36 +01:00
|
|
|
* @throws ServerException
|
2019-06-26 03:39:39 +01:00
|
|
|
*/
|
2019-10-19 01:57:36 +01:00
|
|
|
public function sendFile(): void
|
|
|
|
{
|
2019-07-02 22:35:05 +01:00
|
|
|
if (is_string(common_config('site', 'x-static-delivery'))) {
|
2019-10-19 01:57:36 +01:00
|
|
|
$tmp = explode(INSTALLDIR, $this->filepath);
|
2019-07-11 23:49:16 +01:00
|
|
|
$relative_path = end($tmp);
|
2019-07-02 22:35:05 +01:00
|
|
|
common_debug("Using Static Delivery with header: '" .
|
|
|
|
common_config('site', 'x-static-delivery') . ": {$relative_path}'");
|
|
|
|
header(common_config('site', 'x-static-delivery') . ": {$relative_path}");
|
2019-06-26 03:25:59 +01:00
|
|
|
} else {
|
2019-10-19 01:57:36 +01:00
|
|
|
if (empty($this->filesize)) {
|
|
|
|
$this->filesize = filesize($this->filepath);
|
2019-06-26 03:25:59 +01:00
|
|
|
}
|
2019-10-19 01:57:36 +01:00
|
|
|
header("Content-Length: {$this->filesize}");
|
2019-06-26 03:25:59 +01:00
|
|
|
// header('Cache-Control: private, no-transform, no-store, must-revalidate');
|
|
|
|
|
2019-10-19 01:57:36 +01:00
|
|
|
$ret = @readfile($this->filepath);
|
2019-06-26 03:25:59 +01:00
|
|
|
|
2019-06-29 20:10:20 +01:00
|
|
|
if ($ret === false) {
|
2019-10-19 01:57:36 +01:00
|
|
|
common_log(LOG_ERR, "Couldn't read file at {$this->filepath}.");
|
|
|
|
} elseif ($ret !== $this->filesize) {
|
2019-06-26 03:25:59 +01:00
|
|
|
common_log(LOG_ERR, "The lengths of the file as recorded on the DB (or on disk) for the file " .
|
2019-10-19 01:57:36 +01:00
|
|
|
"{$this->filepath} differ from what was sent to the user ({$this->filesize} vs {$ret}).");
|
2019-06-26 03:25:59 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-05-15 20:04:58 +01:00
|
|
|
}
|