Skip to content

Commit

Permalink
Clean and validate uploaded SVG files (#318)
Browse files Browse the repository at this point in the history
* Clean/validate svg uploads

* Apply fixes from StyleCI

* extend the singletons

Co-authored-by: StyleCI Bot <bot@styleci.io>
  • Loading branch information
imorland and StyleCIBot committed May 25, 2022
1 parent 837e86a commit d1f0e4b
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 3 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"ramsey/uuid": "^3.5.2 || ^4",
"softcreatr/php-mime-detector": "^3.0",
"guzzlehttp/guzzle": "^6.0 || ^7.0",
"ext-json": "*"
"ext-json": "*",
"enshrined/svg-sanitize": "^0.15.4"
},
"replace": {
"flagrow/upload": "*"
Expand Down
7 changes: 6 additions & 1 deletion extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Flarum\Extend;
use Flarum\Settings\Event\Deserializing;
use FoF\Upload\Events\File\WillBeUploaded;
use FoF\Upload\Extend\SvgSanitizer;

return [
(new Extend\Routes('api'))
Expand Down Expand Up @@ -56,7 +57,8 @@
(new Extend\ServiceProvider())
->register(Providers\UtilProvider::class)
->register(Providers\StorageServiceProvider::class)
->register(Providers\DownloadProvider::class),
->register(Providers\DownloadProvider::class)
->register(Providers\SanitizerProvider::class),

(new Extend\View())
->namespace('fof-upload.templates', __DIR__.'/resources/templates'),
Expand All @@ -69,4 +71,7 @@

(new Extend\Formatter())
->render(Formatter\TextPreview\FormatTextPreview::class),

(new SvgSanitizer())
->allowTag('animate'),
];
25 changes: 24 additions & 1 deletion src/Commands/UploadHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

namespace FoF\Upload\Commands;

use enshrined\svgSanitize\Sanitizer;
use Exception;
use Flarum\Foundation\Application;
use Flarum\Foundation\ValidationException;
Expand All @@ -24,6 +25,7 @@
use FoF\Upload\Repositories\FileRepository;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Psr\Http\Message\UploadedFileInterface;
use SoftCreatR\MimeDetector\MimeDetector;
use SoftCreatR\MimeDetector\MimeDetectorException;
Expand Down Expand Up @@ -59,20 +61,27 @@ class UploadHandler
*/
protected $translator;

/**
* @var Sanitizer
*/
protected $sanitizer;

public function __construct(
Application $app,
Dispatcher $events,
Util $util,
FileRepository $files,
MimeDetector $mimeDetector,
Translator $translator
Translator $translator,
Sanitizer $sanitizer
) {
$this->app = $app;
$this->util = $util;
$this->events = $events;
$this->files = $files;
$this->mimeDetector = $mimeDetector;
$this->translator = $translator;
$this->sanitizer = $sanitizer;
}

/**
Expand Down Expand Up @@ -106,6 +115,20 @@ public function handle(Upload $command)
}
}

// If an SVG has been uploaded, remove any unwanted tags & attrs, if possible, else throw a validation error
if (Str::startsWith($uploadFileData['mime'], 'image/svg')) {
// Will return false if sanitization fails, else will return the clean SVG contents.
$cleanSvg = $this->sanitizer->sanitize(file_get_contents($upload->getPathname()));

if (!$cleanSvg) {
//TODO maybe expose the error list via ValidationException?
//$issues = $this->sanitizer->getXmlIssues();
throw new ValidationException(['upload' => $this->translator->trans('fof-upload.api.upload_errors.svg_failure')]);
}

file_put_contents($upload->getPathname(), $cleanSvg, LOCK_EX);
}

$mimeConfiguration = $this->getMimeConfiguration($uploadFileData['mime']);
$adapter = $this->getAdapter(Arr::get($mimeConfiguration, 'adapter'));
$template = $this->getTemplate(Arr::get($mimeConfiguration, 'template', 'file'));
Expand Down
75 changes: 75 additions & 0 deletions src/Extend/SvgSanitizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of fof/upload.
*
* Copyright (c) FriendsOfFlarum.
* Copyright (c) Flagrow.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FoF\Upload\Extend;

use Flarum\Extend\ExtenderInterface;
use Flarum\Extension\Extension;
use Illuminate\Contracts\Container\Container;

class SvgSanitizer implements ExtenderInterface
{
protected $allowedAttrs = [];

protected $allowedTags = [];

protected $removeAttrs = [];

protected $removeTags = [];

public function allowAttr(string $attr): self
{
$this->allowedAttrs[] = $attr;

return $this;
}

public function allowTag(string $tag): self
{
$this->allowedTags[] = $tag;

return $this;
}

public function removeAttr($attr): self
{
$this->removeAttrs[] = $attr;

return $this;
}

public function removeTag($tag): self
{
$this->removeTags[] = $tag;

return $this;
}

public function extend(Container $container, Extension $extension = null)
{
$container->extend('fof.upload.sanitizer.svg_allowed_attrs', function ($items): array {
return array_merge($items, $this->allowedAttrs);
});

$container->extend('fof.upload.sanitizer.svg_disallowed_attrs', function ($items): array {
return array_merge($items, $this->removeAttrs);
});

$container->extend('fof.upload.sanitizer.svg_allowed_tags', function ($items): array {
return array_merge($items, $this->allowedTags);
});

$container->extend('fof.upload.sanitizer.svg_disallowed_tags', function ($items): array {
return array_merge($items, $this->removeTags);
});
}
}
49 changes: 49 additions & 0 deletions src/Providers/SanitizerProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of fof/upload.
*
* Copyright (c) FriendsOfFlarum.
* Copyright (c) Flagrow.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FoF\Upload\Providers;

use enshrined\svgSanitize\Sanitizer;
use Flarum\Foundation\AbstractServiceProvider;
use FoF\Upload\Sanitizer\SvgAllowedAttrs;
use FoF\Upload\Sanitizer\SvgAllowedTags;

class SanitizerProvider extends AbstractServiceProvider
{
public function register()
{
$this->container->singleton(Sanitizer::class, function (): Sanitizer {
$sanitizer = new Sanitizer();
$sanitizer->setAllowedAttrs(new SvgAllowedAttrs());
$sanitizer->setAllowedTags(new SvgAllowedTags());
$sanitizer->removeRemoteReferences(true);

return $sanitizer;
});

$this->container->singleton('fof.upload.sanitizer.svg_allowed_attrs', function (): array {
return [];
});

$this->container->singleton('fof.upload.sanitizer.svg_disallowed_attrs', function (): array {
return [];
});

$this->container->singleton('fof.upload.sanitizer.svg_allowed_tags', function (): array {
return [];
});

$this->container->singleton('fof.upload.sanitizer.svg_disallowed_tags', function (): array {
return [];
});
}
}
26 changes: 26 additions & 0 deletions src/Sanitizer/SvgAllowedAttrs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of fof/upload.
*
* Copyright (c) FriendsOfFlarum.
* Copyright (c) Flagrow.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FoF\Upload\Sanitizer;

use enshrined\svgSanitize\data\AllowedAttributes;

class SvgAllowedAttrs extends AllowedAttributes
{
public static function getAttributes(): array
{
return array_diff(
array_merge(parent::getAttributes(), resolve('fof.upload.sanitizer.svg_allowed_attrs')),
resolve('fof.upload.sanitizer.svg_disallowed_attrs')
);
}
}
26 changes: 26 additions & 0 deletions src/Sanitizer/SvgAllowedTags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of fof/upload.
*
* Copyright (c) FriendsOfFlarum.
* Copyright (c) Flagrow.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FoF\Upload\Sanitizer;

use enshrined\svgSanitize\data\AllowedTags;

class SvgAllowedTags extends AllowedTags
{
public static function getTags(): array
{
return array_diff(
array_merge(parent::getTags(), resolve('fof.upload.sanitizer.svg_allowed_tags')),
resolve('fof.upload.sanitizer.svg_disallowed_tags')
);
}
}

0 comments on commit d1f0e4b

Please sign in to comment.