[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.
|
* Returns the avatar file's hash, mimetype, title and path.
|
||||||
* Ensures exactly one cached value exists
|
* 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}",
|
$res = Cache::get("avatar-file-info-{$actor_id}",
|
||||||
function () use ($actor_id) {
|
function () use ($actor_id) {
|
||||||
|
@ -101,7 +101,7 @@ class Avatar extends Controller
|
|||||||
$data_user = base64_decode($data_user);
|
$data_user = base64_decode($data_user);
|
||||||
$tempfile = new TemporaryFile(['prefix' => 'gs-avatar']);
|
$tempfile = new TemporaryFile(['prefix' => 'gs-avatar']);
|
||||||
$tempfile->write($data_user);
|
$tempfile->write($data_user);
|
||||||
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($tempfile);
|
$attachment = GSFile::storeFileAsAttachment($tempfile);
|
||||||
} else {
|
} else {
|
||||||
Log::info('Avatar upload got an invalid encoding, something\'s fishy and/or wrong');
|
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'])) {
|
} elseif (isset($data['avatar'])) {
|
||||||
// Cropping failed (e.g. disabled js), use file as uploaded
|
// Cropping failed (e.g. disabled js), use file as uploaded
|
||||||
$file = $data['avatar'];
|
$file = $data['avatar'];
|
||||||
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($file);
|
$attachment = GSFile::storeFileAsAttachment($file);
|
||||||
} else {
|
} else {
|
||||||
throw new ClientException('Invalid form');
|
throw new ClientException('Invalid form');
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ class Cover
|
|||||||
if (explode('/',$sfile->getMimeType())[0] != 'image') {
|
if (explode('/',$sfile->getMimeType())[0] != 'image') {
|
||||||
throw new ServerException('Invalid file type');
|
throw new ServerException('Invalid file type');
|
||||||
}
|
}
|
||||||
$file = GSFile::sanitizeAndStoreFileAsAttachment($sfile);
|
$file = GSFile::storeFileAsAttachment($sfile);
|
||||||
$old_file = null;
|
$old_file = null;
|
||||||
$cover = DB::find('cover', ['gctor_id' => $actor_id]);
|
$cover = DB::find('cover', ['gctor_id' => $actor_id]);
|
||||||
// Must get old id before inserting another one
|
// 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';
|
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 array $event_map
|
||||||
* @param string $mimetype
|
* @param string $mimetype
|
||||||
@ -83,8 +98,29 @@ class ImageEncoder extends Plugin
|
|||||||
return Event::next;
|
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.
|
* Also ensures that the image is not greater than the max width and height configured.
|
||||||
*
|
*
|
||||||
* @param SplFileInfo $file
|
* @param SplFileInfo $file
|
||||||
@ -103,7 +139,7 @@ class ImageEncoder extends Plugin
|
|||||||
public function fileSanitize(SplFileInfo &$file, ?string &$mimetype, ?int &$width, ?int &$height): bool
|
public function fileSanitize(SplFileInfo &$file, ?string &$mimetype, ?int &$width, ?int &$height): bool
|
||||||
{
|
{
|
||||||
$original_mimetype = $mimetype;
|
$original_mimetype = $mimetype;
|
||||||
if (GSFile::mimetypeMajor($original_mimetype) != 'image') {
|
if (GSFile::mimetypeMajor($original_mimetype) !== 'image') {
|
||||||
// Nothing concerning us
|
// Nothing concerning us
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,22 @@ class VideoEncoder extends Plugin
|
|||||||
return '1.0.0';
|
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 array $event_map
|
||||||
* @param string $mimetype
|
* @param string $mimetype
|
||||||
@ -66,8 +82,8 @@ class VideoEncoder extends Plugin
|
|||||||
if (GSFile::mimetypeMajor($mimetype) !== 'video' && $mimetype !== 'image/gif') {
|
if (GSFile::mimetypeMajor($mimetype) !== 'video' && $mimetype !== 'image/gif') {
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
$event_map['video'][] = [$this, 'fileSanitize'];
|
$event_map['video'][] = [$this, 'fileMeta'];
|
||||||
$event_map['image/gif'][] = [$this, 'fileSanitize'];
|
$event_map['image/gif'][] = [$this, 'fileMeta'];
|
||||||
return Event::next;
|
return Event::next;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +113,7 @@ class VideoEncoder extends Plugin
|
|||||||
*
|
*
|
||||||
* @return bool true if sanitized
|
* @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' &&
|
if (//GSFile::mimetypeMajor($mimetype) !== 'video' &&
|
||||||
$mimetype !== 'image/gif') {
|
$mimetype !== 'image/gif') {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<figure>
|
<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>
|
</video>
|
||||||
<figcaption><a
|
<figcaption><a
|
||||||
href="{{ path('attachment_show', {'id': attachment.getId()}) }}">{{ attachment.getBestTitle(note) }}</a>
|
href="{{ path('attachment_show', {'id': attachment.getId()}) }}">{{ attachment.getBestTitle(note) }}</a>
|
||||||
|
@ -59,7 +59,7 @@ class GSFile
|
|||||||
*
|
*
|
||||||
* @return Attachment
|
* @return Attachment
|
||||||
*/
|
*/
|
||||||
public static function sanitizeAndStoreFileAsAttachment(TemporaryFile|SymfonyFile $file): Attachment
|
public static function storeFileAsAttachment(TemporaryFile|SymfonyFile $file): Attachment
|
||||||
{
|
{
|
||||||
$hash = null;
|
$hash = null;
|
||||||
Event::handle('HashFile', [$file->getPathname(), &$hash]);
|
Event::handle('HashFile', [$file->getPathname(), &$hash]);
|
||||||
@ -67,21 +67,25 @@ class GSFile
|
|||||||
$attachment = DB::findOneBy('attachment', ['filehash' => $hash]);
|
$attachment = DB::findOneBy('attachment', ['filehash' => $hash]);
|
||||||
// Attachment Exists
|
// Attachment Exists
|
||||||
$attachment->livesIncrementAndGet();
|
$attachment->livesIncrementAndGet();
|
||||||
|
// We had this attachment, but not the file, thus no filename, update meta
|
||||||
if (is_null($attachment->getFilename())) {
|
if (is_null($attachment->getFilename())) {
|
||||||
$mimetype = $attachment->getMimetype();
|
$mimetype = $attachment->getMimetype();
|
||||||
$width = $attachment->getWidth();
|
$width = $attachment->getWidth();
|
||||||
$height = $attachment->getHeight();
|
$height = $attachment->getHeight();
|
||||||
if (Common::config('attachments', 'sanitize')) {
|
|
||||||
$event_map[$mimetype] = [];
|
$event_map[$mimetype] = [];
|
||||||
$major_mime = self::mimetypeMajor($mimetype);
|
$major_mime = self::mimetypeMajor($mimetype);
|
||||||
$event_map[$major_mime] = [];
|
$event_map[$major_mime] = [];
|
||||||
|
if (Common::config('attachments', 'sanitize')) {
|
||||||
Event::handle('FileSanitizerAvailable', [&$event_map, $mimetype]);
|
Event::handle('FileSanitizerAvailable', [&$event_map, $mimetype]);
|
||||||
|
} else {
|
||||||
|
Event::handle('FileMetaAvailable', [&$event_map, $mimetype]);
|
||||||
|
}
|
||||||
// Always prefer specific encoders
|
// Always prefer specific encoders
|
||||||
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
||||||
foreach ($encoders as $encoder) {
|
foreach ($encoders as $encoder) {
|
||||||
|
// These are all I/O params
|
||||||
if ($encoder($file, $mimetype, $width, $height)) {
|
if ($encoder($file, $mimetype, $width, $height)) {
|
||||||
break; // One successful sanitizer is enough
|
break; // One successful File type handler plugin is enough
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$attachment->setFilename($hash);
|
$attachment->setFilename($hash);
|
||||||
@ -97,11 +101,14 @@ class GSFile
|
|||||||
// available methods, so should be safe
|
// available methods, so should be safe
|
||||||
$mimetype = mb_substr($file->getMimeType(), 0, 64);
|
$mimetype = mb_substr($file->getMimeType(), 0, 64);
|
||||||
$width = $height = null;
|
$width = $height = null;
|
||||||
if (Common::config('attachments', 'sanitize')) {
|
|
||||||
$event_map[$mimetype] = [];
|
$event_map[$mimetype] = [];
|
||||||
$major_mime = self::mimetypeMajor($mimetype);
|
$major_mime = self::mimetypeMajor($mimetype);
|
||||||
$event_map[$major_mime] = [];
|
$event_map[$major_mime] = [];
|
||||||
|
if (Common::config('attachments', 'sanitize')) {
|
||||||
Event::handle('FileSanitizerAvailable', [&$event_map, $mimetype]);
|
Event::handle('FileSanitizerAvailable', [&$event_map, $mimetype]);
|
||||||
|
} else {
|
||||||
|
Event::handle('FileMetaAvailable', [&$event_map, $mimetype]);
|
||||||
|
}
|
||||||
// Always prefer specific encoders
|
// Always prefer specific encoders
|
||||||
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
$encoders = array_merge($event_map[$mimetype], $event_map[$major_mime]);
|
||||||
foreach ($encoders as $encoder) {
|
foreach ($encoders as $encoder) {
|
||||||
@ -109,7 +116,6 @@ class GSFile
|
|||||||
break; // One successful sanitizer is enough
|
break; // One successful sanitizer is enough
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$attachment = Attachment::create([
|
$attachment = Attachment::create([
|
||||||
'filehash' => $hash,
|
'filehash' => $hash,
|
||||||
'mimetype' => $mimetype,
|
'mimetype' => $mimetype,
|
||||||
|
@ -20,7 +20,7 @@ class MediaFixtures extends Fixture
|
|||||||
$file = new TemporaryFile();
|
$file = new TemporaryFile();
|
||||||
$file->write(file_get_contents($filepath));
|
$file->write(file_get_contents($filepath));
|
||||||
try {
|
try {
|
||||||
GSFile::sanitizeAndStoreFileAsAttachment($file);
|
GSFile::storeFileAsAttachment($file);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
echo "Could not save file {$filepath}, failed with {$e}\n";
|
echo "Could not save file {$filepath}, failed with {$e}\n";
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -39,7 +39,7 @@ class AttachmentTest extends GNUsocialTestCase
|
|||||||
|
|
||||||
// Setup first attachment
|
// Setup first attachment
|
||||||
$file = new TemporaryFile();
|
$file = new TemporaryFile();
|
||||||
$attachment = GSFile::sanitizeAndStoreFileAsAttachment($file);
|
$attachment = GSFile::storeFileAsAttachment($file);
|
||||||
$path = $attachment->getPath();
|
$path = $attachment->getPath();
|
||||||
$hash = $attachment->getFilehash();
|
$hash = $attachment->getFilehash();
|
||||||
static::assertTrue(file_exists($attachment->getPath()));
|
static::assertTrue(file_exists($attachment->getPath()));
|
||||||
@ -55,7 +55,7 @@ class AttachmentTest extends GNUsocialTestCase
|
|||||||
|
|
||||||
// Setup the second attachment, re-adding the backed store
|
// Setup the second attachment, re-adding the backed store
|
||||||
$file = new TemporaryFile();
|
$file = new TemporaryFile();
|
||||||
$repeated_attachment = GSFile::sanitizeAndStoreFileAsAttachment($file);
|
$repeated_attachment = GSFile::storeFileAsAttachment($file);
|
||||||
$path = $attachment->getPath();
|
$path = $attachment->getPath();
|
||||||
static::assertSame(2, $repeated_attachment->getLives());
|
static::assertSame(2, $repeated_attachment->getLives());
|
||||||
static::assertTrue(file_exists($path));
|
static::assertTrue(file_exists($path));
|
||||||
@ -84,7 +84,7 @@ class AttachmentTest extends GNUsocialTestCase
|
|||||||
DB::flush();
|
DB::flush();
|
||||||
|
|
||||||
$file = new File($temp_file->getRealPath());
|
$file = new File($temp_file->getRealPath());
|
||||||
GSFile::sanitizeAndStoreFileAsAttachment($file);
|
GSFile::storeFileAsAttachment($file);
|
||||||
static::assertNotNull($attachment->getFilename());
|
static::assertNotNull($attachment->getFilename());
|
||||||
static::assertTrue(file_exists($attachment->getPath()));
|
static::assertTrue(file_exists($attachment->getPath()));
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user