[GSFile] Fix sanitize configuration
This commit is contained in:
parent
c1e7d486a3
commit
7beb5c2995
@ -109,7 +109,7 @@ class Avatar extends Component
|
||||
* Returns the avatar file's hash, mimetype, title and path.
|
||||
* Ensures exactly one cached value exists
|
||||
*/
|
||||
public static function getAvatarFileInfo(int $actor_id): array
|
||||
public static function getAvatarFileInfo(int $actor_id, int $size = 0): array
|
||||
{
|
||||
$res = Cache::get("avatar-file-info-{$actor_id}",
|
||||
function () use ($actor_id) {
|
||||
|
@ -101,7 +101,7 @@ class Avatar extends Controller
|
||||
$data_user = base64_decode($data_user);
|
||||
$tempfile = new TemporaryFile(['prefix' => 'gs-avatar']);
|
||||
$tempfile->write($data_user);
|
||||
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($tempfile);
|
||||
$attachment = GSFile::storeFileAsAttachment($tempfile);
|
||||
} else {
|
||||
Log::info('Avatar upload got an invalid encoding, something\'s fishy and/or wrong');
|
||||
}
|
||||
@ -109,7 +109,7 @@ class Avatar extends Controller
|
||||
} elseif (isset($data['avatar'])) {
|
||||
// Cropping failed (e.g. disabled js), use file as uploaded
|
||||
$file = $data['avatar'];
|
||||
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($file);
|
||||
$attachment = GSFile::storeFileAsAttachment($file);
|
||||
} else {
|
||||
throw new ClientException('Invalid form');
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ class Cover
|
||||
if (explode('/',$sfile->getMimeType())[0] != 'image') {
|
||||
throw new ServerException('Invalid file type');
|
||||
}
|
||||
$file = GSFile::sanitizeAndStoreFileAsAttachment($sfile);
|
||||
$file = GSFile::storeFileAsAttachment($sfile);
|
||||
$old_file = null;
|
||||
$cover = DB::find('cover', ['gctor_id' => $actor_id]);
|
||||
// Must get old id before inserting another one
|
||||
|
@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
// {{{ License
|
||||
|
||||
// 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/>.
|
||||
|
||||
// }}}
|
||||
|
||||
namespace Plugin\ImageEncoder\Controller;
|
||||
|
||||
use App\Core\Controller;
|
||||
use App\Core\DB\DB;
|
||||
use App\Core\GSFile;
|
||||
use App\Entity\AttachmentThumbnail;
|
||||
use App\Util\Common;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class ImageThumbnail extends Controller
|
||||
{
|
||||
/**
|
||||
* Get a thumbnail for the attachment with id $id
|
||||
*/
|
||||
public function thumbnail(Request $request, int $id)
|
||||
{
|
||||
$attachment = DB::findOneBy('attachment', ['id' => $id]);
|
||||
if (!is_null($attachment->getScope())) {
|
||||
// && ($attachment->scope | VisibilityScope::PUBLIC) != 0
|
||||
// $user = Common::ensureLoggedIn();
|
||||
assert(false, 'Attachment scope not implemented');
|
||||
}
|
||||
|
||||
// TODO rate limit
|
||||
|
||||
$max_width = Common::config('thumbnail', 'width');
|
||||
$max_height = Common::config('thumbnail', 'height');
|
||||
$width = Common::clamp($this->int('w') ?: $max_width, min: 0, max: $max_width);
|
||||
$height = Common::clamp($this->int('h') ?: $max_height, min: 0, max: $max_height);
|
||||
$crop = $this->bool('c') ?: false;
|
||||
|
||||
$thumbnail = AttachmentThumbnail::getOrCreate(attachment: $attachment, width: $width, height: $height, crop: $crop);
|
||||
|
||||
$filename = $thumbnail->getFilename();
|
||||
$path = $thumbnail->getPath();
|
||||
|
||||
return GSFile::sendFile(filepath: $path, mimetype: $attachment->getMimetype(), output_filename: $filename, disposition: 'inline');
|
||||
}
|
||||
}
|
@ -53,6 +53,21 @@ class ImageEncoder extends Plugin
|
||||
return '3.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $event_map
|
||||
* @param string $mimetype
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onFileMetaAvailable(array &$event_map, string $mimetype): bool
|
||||
{
|
||||
if (GSFile::mimetypeMajor($mimetype) !== 'image') {
|
||||
return Event::next;
|
||||
}
|
||||
$event_map['image'][] = [$this, 'fileMeta'];
|
||||
return Event::next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $event_map
|
||||
* @param string $mimetype
|
||||
@ -83,8 +98,29 @@ class ImageEncoder extends Plugin
|
||||
return Event::next;
|
||||
}
|
||||
|
||||
public function fileMeta(SplFileInfo &$file, ?string &$mimetype, ?int &$width, ?int &$height): bool
|
||||
{
|
||||
$original_mimetype = $mimetype;
|
||||
if (GSFile::mimetypeMajor($original_mimetype) !== 'image') {
|
||||
// Nothing concerning us
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$image = Vips\Image::newFromFile($file->getRealPath(), ['access' => 'sequential']);
|
||||
} catch (Vips\Exception $e) {
|
||||
Log::debug("ImageEncoder's Vips couldn't handle the image file, failed with {$e}.");
|
||||
throw new UnsupportedFileTypeException(_m("Unsupported image file with {$mimetype}.", previous: $e));
|
||||
}
|
||||
$width = $image->width;
|
||||
$height = $image->height;
|
||||
|
||||
// Only one plugin can handle meta
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-encodes the image ensuring it's valid.
|
||||
* Re-encodes the image ensuring it is valid.
|
||||
* Also ensures that the image is not greater than the max width and height configured.
|
||||
*
|
||||
* @param SplFileInfo $file
|
||||
@ -103,7 +139,7 @@ class ImageEncoder extends Plugin
|
||||
public function fileSanitize(SplFileInfo &$file, ?string &$mimetype, ?int &$width, ?int &$height): bool
|
||||
{
|
||||
$original_mimetype = $mimetype;
|
||||
if (GSFile::mimetypeMajor($original_mimetype) != 'image') {
|
||||
if (GSFile::mimetypeMajor($original_mimetype) !== 'image') {
|
||||
// Nothing concerning us
|
||||
return false;
|
||||
}
|
||||
|
@ -55,6 +55,22 @@ class VideoEncoder extends Plugin
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $event_map
|
||||
* @param string $mimetype
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onFileMetaAvailable(array &$event_map, string $mimetype): bool
|
||||
{
|
||||
if (GSFile::mimetypeMajor($mimetype) !== 'video' && $mimetype !== 'image/gif') {
|
||||
return Event::next;
|
||||
}
|
||||
$event_map['video'][] = [$this, 'fileMeta'];
|
||||
$event_map['image/gif'][] = [$this, 'fileMeta'];
|
||||
return Event::next;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $event_map
|
||||
* @param string $mimetype
|
||||
@ -66,8 +82,8 @@ class VideoEncoder extends Plugin
|
||||
if (GSFile::mimetypeMajor($mimetype) !== 'video' && $mimetype !== 'image/gif') {
|
||||
return Event::next;
|
||||
}
|
||||
$event_map['video'][] = [$this, 'fileSanitize'];
|
||||
$event_map['image/gif'][] = [$this, 'fileSanitize'];
|
||||
$event_map['video'][] = [$this, 'fileMeta'];
|
||||
$event_map['image/gif'][] = [$this, 'fileMeta'];
|
||||
return Event::next;
|
||||
}
|
||||
|
||||
@ -97,7 +113,7 @@ class VideoEncoder extends Plugin
|
||||
*
|
||||
* @return bool true if sanitized
|
||||
*/
|
||||
public function fileSanitize(SplFileInfo &$file, ?string &$mimetype, ?int &$width, ?int &$height): bool
|
||||
public function fileMeta(SplFileInfo &$file, ?string &$mimetype, ?int &$width, ?int &$height): bool
|
||||
{
|
||||
if (//GSFile::mimetypeMajor($mimetype) !== 'video' &&
|
||||
$mimetype !== 'image/gif') {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div>
|
||||
<figure>
|
||||
<video class="u-video" src="{{ attachment.getUrl() }}" controls poster="{{ attachment.getThumbnailUrl()}}">
|
||||
<video class="u-video" src="{{ attachment.getUrl() }}" controls poster="{{ attachment.getThumbnailUrl('medium')}}">
|
||||
</video>
|
||||
<figcaption><a
|
||||
href="{{ path('attachment_show', {'id': attachment.getId()}) }}">{{ attachment.getBestTitle(note) }}</a>
|
||||
|
@ -59,7 +59,7 @@ class GSFile
|
||||
*
|
||||
* @return Attachment
|
||||
*/
|
||||
public static function sanitizeAndStoreFileAsAttachment(TemporaryFile|SymfonyFile $file): Attachment
|
||||
public static function storeFileAsAttachment(TemporaryFile|SymfonyFile $file): Attachment
|
||||
{
|
||||
$hash = null;
|
||||
Event::handle('HashFile', [$file->getPathname(), &$hash]);
|
||||
@ -67,21 +67,25 @@ class GSFile
|
||||
$attachment = DB::findOneBy('attachment', ['filehash' => $hash]);
|
||||
// Attachment Exists
|
||||
$attachment->livesIncrementAndGet();
|
||||
// We had this attachment, but not the file, thus no filename, update meta
|
||||
if (is_null($attachment->getFilename())) {
|
||||
$mimetype = $attachment->getMimetype();
|
||||
$width = $attachment->getWidth();
|
||||
$height = $attachment->getHeight();
|
||||
$mimetype = $attachment->getMimetype();
|
||||
$width = $attachment->getWidth();
|
||||
$height = $attachment->getHeight();
|
||||
$event_map[$mimetype] = [];
|
||||
$major_mime = self::mimetypeMajor($mimetype);
|
||||
$event_map[$major_mime] = [];
|
||||
if (Common::config('attachments', 'sanitize')) {
|
||||
$event_map[$mimetype] = [];
|
||||
$major_mime = self::mimetypeMajor($mimetype);
|
||||
$event_map[$major_mime] = [];
|
||||
Event::handle('FileSanitizerAvailable', [&$event_map, $mimetype]);
|
||||
// Always prefer specific encoders
|
||||
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
||||
foreach ($encoders as $encoder) {
|
||||
if ($encoder($file, $mimetype, $width, $height)) {
|
||||
break; // One successful sanitizer is enough
|
||||
}
|
||||
} else {
|
||||
Event::handle('FileMetaAvailable', [&$event_map, $mimetype]);
|
||||
}
|
||||
// Always prefer specific encoders
|
||||
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
||||
foreach ($encoders as $encoder) {
|
||||
// These are all I/O params
|
||||
if ($encoder($file, $mimetype, $width, $height)) {
|
||||
break; // One successful File type handler plugin is enough
|
||||
}
|
||||
}
|
||||
$attachment->setFilename($hash);
|
||||
@ -95,19 +99,21 @@ class GSFile
|
||||
// Create an Attachment
|
||||
// The following properly gets the mimetype with `file` or other
|
||||
// available methods, so should be safe
|
||||
$mimetype = mb_substr($file->getMimeType(), 0, 64);
|
||||
$width = $height = null;
|
||||
$mimetype = mb_substr($file->getMimeType(), 0, 64);
|
||||
$width = $height = null;
|
||||
$event_map[$mimetype] = [];
|
||||
$major_mime = self::mimetypeMajor($mimetype);
|
||||
$event_map[$major_mime] = [];
|
||||
if (Common::config('attachments', 'sanitize')) {
|
||||
$event_map[$mimetype] = [];
|
||||
$major_mime = self::mimetypeMajor($mimetype);
|
||||
$event_map[$major_mime] = [];
|
||||
Event::handle('FileSanitizerAvailable', [&$event_map, $mimetype]);
|
||||
// Always prefer specific encoders
|
||||
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
||||
foreach ($encoders as $encoder) {
|
||||
if ($encoder($file, $mimetype, $width, $height)) {
|
||||
break; // One successful sanitizer is enough
|
||||
}
|
||||
} else {
|
||||
Event::handle('FileMetaAvailable', [&$event_map, $mimetype]);
|
||||
}
|
||||
// Always prefer specific encoders
|
||||
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
||||
foreach ($encoders as $encoder) {
|
||||
if ($encoder($file, $mimetype, $width, $height)) {
|
||||
break; // One successful sanitizer is enough
|
||||
}
|
||||
}
|
||||
$attachment = Attachment::create([
|
||||
|
@ -20,7 +20,7 @@ class MediaFixtures extends Fixture
|
||||
$file = new TemporaryFile();
|
||||
$file->write(file_get_contents($filepath));
|
||||
try {
|
||||
GSFile::sanitizeAndStoreFileAsAttachment($file);
|
||||
GSFile::storeFileAsAttachment($file);
|
||||
} catch (Exception $e) {
|
||||
echo "Could not save file {$filepath}, failed with {$e}\n";
|
||||
} finally {
|
||||
|
@ -39,7 +39,7 @@ class AttachmentTest extends GNUsocialTestCase
|
||||
|
||||
// Setup first attachment
|
||||
$file = new TemporaryFile();
|
||||
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($file);
|
||||
$attachment = GSFile::storeFileAsAttachment($file);
|
||||
$path = $attachment->getPath();
|
||||
$hash = $attachment->getFilehash();
|
||||
static::assertTrue(file_exists($attachment->getPath()));
|
||||
@ -55,7 +55,7 @@ class AttachmentTest extends GNUsocialTestCase
|
||||
|
||||
// Setup the second attachment, re-adding the backed store
|
||||
$file = new TemporaryFile();
|
||||
$repeated_attachment = GSFile::sanitizeAndStoreFileAsAttachment($file);
|
||||
$repeated_attachment = GSFile::storeFileAsAttachment($file);
|
||||
$path = $attachment->getPath();
|
||||
static::assertSame(2, $repeated_attachment->getLives());
|
||||
static::assertTrue(file_exists($path));
|
||||
@ -84,7 +84,7 @@ class AttachmentTest extends GNUsocialTestCase
|
||||
DB::flush();
|
||||
|
||||
$file = new File($temp_file->getRealPath());
|
||||
GSFile::sanitizeAndStoreFileAsAttachment($file);
|
||||
GSFile::storeFileAsAttachment($file);
|
||||
static::assertNotNull($attachment->getFilename());
|
||||
static::assertTrue(file_exists($attachment->getPath()));
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user