gnu-social/actions/attachment.php
Diogo Peralta Cordeiro 6028175bfc [Media] Fix issues with database file storage
Fixed file quota as well.

There can be more than one file for the same filehash IF the url are different.

Possible states:
  - A file with no url and with filename is a local file.
  - A file with an url but no filename is a remote file that wasn't fetched,
    not even the thumbnail.
  - A file with an url and filename is a fetched remote file (maybe just a
    thumbnail of it).
  - A file with no filename nor url is a redirect.

Routes:
  Given these states, updated routes so that an attachment can only be
  retrieved by id and a file by filehash.

Major API changes:
  File::getByHash now returns a yield of files

Major UI changes:
  - Now remote non stored files are presented.
  - /view became preferred
  - Redirects to remote originals are preferred.

Many other minor bug fixes...
2021-02-21 16:03:46 +00:00

221 lines
5.9 KiB
PHP

<?php
// 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();
/**
* Show notice attachments
*
* @category Personal
* @package GNUsocial
* @author Evan Prodromou <evan@status.net>
* @copyright 2008-2009 StatusNet, Inc.
* @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
*/
class AttachmentAction extends ManagedAction
{
/**
* Attachment File object to show
*/
public $attachment = null;
public $filehash = null;
public $filepath = null;
public $filesize = null;
public $mimetype = null;
public $filename = null;
/**
* Load attributes based on database arguments
*
* Loads all the DB stuff
*
* @param array $args $_REQUEST array
*
* @return bool flag
* @throws ClientException
* @throws FileNotFoundException
* @throws FileNotStoredLocallyException
* @throws InvalidFilenameException
* @throws ServerException
*/
protected function prepare(array $args = [])
{
parent::prepare($args);
try {
if (!empty($id = $this->trimmed('attachment'))) {
$this->attachment = File::getByID((int) $id);
} elseif (!empty($this->filehash = $this->trimmed('filehash'))) {
$file = File::getByHash($this->filehash);
$file->fetch();
$this->attachment = $file;
}
} catch (Exception $e) {
// Not found
}
if (!$this->attachment instanceof File) {
// TRANS: Client error displayed trying to get a non-existing attachment.
$this->clientError(_m('No such attachment.'), 404);
}
$this->filesize = $this->attachment->size;
$this->mimetype = $this->attachment->mimetype;
$this->filename = $this->attachment->filename;
if ($this->attachment->isLocal()) {
$this->filepath = $this->attachment->getFileOrThumbnailPath();
if (empty($this->filepath)) {
$this->clientError(
_m('Requested local URL for a file that is not stored locally.'),
404
);
}
$this->filesize = $this->attachment->getFileOrThumbnailSize();
$this->mimetype = $this->attachment->getFileOrThumbnailMimetype();
$this->filename = MediaFile::getDisplayName($this->attachment);
}
return true;
}
/**
* Is this action read-only?
*
* @return bool true
*/
public function isReadOnly($args): bool
{
return true;
}
/**
* Title of the page
*
* @return string title of the page
*/
public function title(): string
{
$a = new Attachment($this->attachment);
return $a->title();
}
public function showPage(): void
{
if (
!$this->attachment->isLocal()
|| empty($this->filepath)
|| !file_exists($this->filepath)
) {
// If it's not a locally stored file, get lost
common_redirect($this->attachment->getUrl(), 303);
}
parent::showPage();
}
/**
* Fill the content area of the page
*
* Shows a single notice list item.
*
* @return void
*/
public function showContent(): void
{
$ali = new Attachment($this->attachment, $this);
$ali->show();
}
/**
* Don't show page notice
*
* @return void
*/
public function showPageNoticeBlock(): void
{
}
/**
* Show aside: this attachments appears in what notices
*
* @return void
*/
public function showSections(): void
{
$ns = new AttachmentNoticeSection($this);
$ns->show();
}
/**
* Last-modified date for file
*
* @return int last-modified date as unix timestamp
* @throws ServerException
*/
public function lastModified(): ?int
{
if (common_config('site', 'use_x_sendfile')) {
return null;
}
$path = $this->filepath;
if (!empty($path)) {
return filemtime($path);
} else {
return null;
}
}
/**
* 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
* @throws ServerException
*/
public function etag(): ?string
{
if (common_config('site', 'use_x_sendfile')) {
return null;
}
$path = $this->filepath;
$cache = Cache::instance();
if ($cache) {
if (empty($path)) {
return null;
}
$key = Cache::key('attachments:etag:' . $path);
$etag = $cache->get($key);
if ($etag === false) {
$etag = crc32(file_get_contents($path));
$cache->set($key, $etag);
}
return $etag;
}
if (!empty($path)) {
$stat = stat($path);
return '"' . $stat['ino'] . '-' . $stat['size'] . '-' . $stat['mtime'] . '"';
} else {
return null;
}
}
}