diff --git a/Neos.Media.Browser/Classes/Controller/AssetController.php b/Neos.Media.Browser/Classes/Controller/AssetController.php index 7f78d961061..f15304dfa1b 100644 --- a/Neos.Media.Browser/Classes/Controller/AssetController.php +++ b/Neos.Media.Browser/Classes/Controller/AssetController.php @@ -14,6 +14,7 @@ use Doctrine\Common\Persistence\Proxy as DoctrineProxy; use Doctrine\ORM\EntityNotFoundException; +use enshrined\svgSanitize\Sanitizer; use Neos\Error\Messages\Error; use Neos\Error\Messages\Message; use Neos\Flow\Annotations as Flow; @@ -35,6 +36,7 @@ use Neos\Media\Domain\Model\AssetCollection; use Neos\Media\Domain\Model\AssetInterface; use Neos\Media\Domain\Model\AssetSource\AssetNotFoundExceptionInterface; +use Neos\Media\Domain\Model\AssetSource\AssetProxy\AssetProxyInterface; use Neos\Media\Domain\Model\AssetSource\AssetProxyRepositoryInterface; use Neos\Media\Domain\Model\AssetSource\AssetSourceConnectionExceptionInterface; use Neos\Media\Domain\Model\AssetSource\AssetSourceInterface; @@ -370,7 +372,8 @@ public function showAction(string $assetSourceIdentifier, string $assetProxyIden $this->view->assignMultiple([ 'assetProxy' => $assetProxy, - 'assetCollections' => $this->assetCollectionRepository->findAll() + 'assetCollections' => $this->assetCollectionRepository->findAll(), + 'assetContainsMaliciousContent' => $this->checkForMaliciousContent($assetProxy) ]); } catch (AssetNotFoundExceptionInterface | AssetSourceConnectionExceptionInterface $e) { $this->view->assign('connectionError', $e); @@ -423,6 +426,7 @@ public function editAction(string $assetSourceIdentifier, string $assetProxyIden 'assetCollections' => $this->assetCollectionRepository->findAll(), 'contentPreview' => $contentPreview, 'assetSource' => $assetSource, + 'assetContainsMaliciousContent' => $this->checkForMaliciousContent($assetProxy), 'canShowVariants' => ($assetProxy instanceof NeosAssetProxy) && ($assetProxy->getAsset() instanceof VariantSupportInterface) ]); } catch (AssetNotFoundExceptionInterface | AssetSourceConnectionExceptionInterface $e) { @@ -1022,4 +1026,25 @@ private function forwardWithConstraints(string $actionName, string $controllerNa } $this->forward($actionName, $controllerName, null, $arguments); } + + private function checkForMaliciousContent(AssetProxyInterface $assetProxy): bool + { + if ($assetProxy->getMediaType() == 'image/svg+xml') { + // @todo: Simplify again when https://github.com/darylldoyle/svg-sanitizer/pull/90 is merged and released. + $previousXmlErrorHandling = libxml_use_internal_errors(true); + $sanitizer = new Sanitizer(); + + $resource = stream_get_contents($assetProxy->getImportStream()); + + $sanitizer->sanitize($resource); + libxml_clear_errors(); + libxml_use_internal_errors($previousXmlErrorHandling); + $issues = $sanitizer->getXmlIssues(); + if ($issues && count($issues) > 0) { + return true; + } + } + + return false; + } } diff --git a/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html b/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html index f32baac89dd..3f0963cdf42 100644 --- a/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html +++ b/Neos.Media.Browser/Resources/Private/Partials/ContentDefaultPreview.html @@ -1,7 +1,14 @@ {namespace m=Neos\Media\ViewHelpers} {namespace neos=Neos\Neos\ViewHelpers}
- - {assetProxy.label} - + + + {assetProxy.label} + + + + {assetProxy.label} + + +
diff --git a/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html b/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html index f40c47e7df1..06ec12fa5d7 100644 --- a/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html +++ b/Neos.Media.Browser/Resources/Private/Templates/Asset/Edit.html @@ -78,7 +78,19 @@

{neos:backend.translate(id: 'connectionError', package: 'Neos.Media.Browser' {neos:backend.translate(id: 'metadata.filename', package: 'Neos.Media.Browser')} - {assetProxy.filename} + + + + {assetProxy.filename} +
+ {neos:backend.translate(id: 'message.assetContainsMaliciousContent', package: 'Neos.Media.Browser')} +
+
+ + {assetProxy.filename} + +
+ {neos:backend.translate(id: 'metadata.lastModified', package: 'Neos.Media.Browser')} diff --git a/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html b/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html index 0375e584638..c74719983bb 100644 --- a/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html +++ b/Neos.Media.Browser/Resources/Private/Templates/Asset/Show.html @@ -39,7 +39,19 @@ {neos:backend.translate(id: 'metadata.filename', package: 'Neos.Media.Browser')} - {assetProxy.filename} + + + + {assetProxy.filename} +
+ {neos:backend.translate(id: 'message.assetContainsMaliciousContent', package: 'Neos.Media.Browser')} +
+
+ + {assetProxy.filename} + +
+ {neos:backend.translate(id: 'metadata.lastModified', package: 'Neos.Media.Browser')} @@ -85,9 +97,16 @@
- - {assetProxy.label} - + + + {assetProxy.label} + + + + {assetProxy.label} + + +
diff --git a/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf b/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf index 07f3354ed02..1dd0ef007a1 100644 --- a/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf +++ b/Neos.Media.Browser/Resources/Private/Translations/en/Main.xlf @@ -101,6 +101,9 @@ This operation cannot be undone. + + This asset might contain malicious content! + Cancel diff --git a/Neos.Media.Browser/composer.json b/Neos.Media.Browser/composer.json index 85edd1fe87f..27ef651e188 100644 --- a/Neos.Media.Browser/composer.json +++ b/Neos.Media.Browser/composer.json @@ -12,6 +12,7 @@ ], "require": { "php": "^8.2", + "ext-libxml": "*", "neos/media": "self.version", "neos/contentrepository-core": "self.version", "neos/neos": "self.version", @@ -22,7 +23,8 @@ "neos/utility-mediatypes": "*", "neos/error-messages": "*", "doctrine/common": "^2.7 || ^3.0", - "doctrine/orm": "^2.6" + "doctrine/orm": "^2.6", + "enshrined/svg-sanitize": "^0.16.0" }, "autoload": { "psr-4": { diff --git a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php index ec474622f5c..2e0ce705dd3 100644 --- a/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php +++ b/Neos.Neos/Classes/Controller/Module/Management/WorkspacesController.php @@ -936,8 +936,8 @@ protected function renderContentChanges( 'diff' => $diffArray ]; } - // The && in belows condition is on purpose as creating a thumbnail for comparison only works - // if actually BOTH are ImageInterface (or NULL). + // The && in belows condition is on purpose as creating a thumbnail for comparison only works + // if actually BOTH are ImageInterface (or NULL). } elseif ( ($originalPropertyValue instanceof ImageInterface || $originalPropertyValue === null) && ($changedPropertyValue instanceof ImageInterface || $changedPropertyValue === null) diff --git a/Neos.Neos/Migrations/Postgresql/Version20230727164600.php b/Neos.Neos/Migrations/Postgresql/Version20230727164600.php new file mode 100644 index 00000000000..e584f6e34a3 --- /dev/null +++ b/Neos.Neos/Migrations/Postgresql/Version20230727164600.php @@ -0,0 +1,57 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on "postgresql".'); + + $this->addSql('ALTER TABLE neos_neos_eventlog_domain_model_event ADD dimensionshash VARCHAR(32) DEFAULT NULL'); + $this->addSql('CREATE INDEX dimensionshash ON neos_neos_eventlog_domain_model_event (dimensionshash)'); + } + + /** + * @param Schema $schema + * @throws \Doctrine\DBAL\Exception + */ + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on "postgresql".'); + + $this->addSql('DROP INDEX dimensionshash'); + $this->addSql('ALTER TABLE neos_neos_eventlog_domain_model_event DROP dimensionshash'); + } + + /** + * @param Schema $schema + * @throws \Doctrine\DBAL\Driver\Exception + * @throws \Doctrine\DBAL\Exception + */ + public function postUp(Schema $schema): void + { + $eventLogResult = $this->connection->executeQuery('SELECT dimension FROM neos_neos_eventlog_domain_model_event where dimensionshash IS NULL AND dimension IS NOT NULL LIMIT 1'); + + while ($eventLogInfo = $eventLogResult->fetchAssociative()) { + $dimensionsArray = unserialize($eventLogInfo['dimension'], ['allowed_classes' => false]); + $dimensionsHash = Utility::sortDimensionValueArrayAndReturnDimensionsHash($dimensionsArray); + $this->connection->executeStatement('UPDATE neos_neos_eventlog_domain_model_event SET dimensionshash = ? WHERE dimension = ?', [$dimensionsHash, $eventLogInfo['dimension']]); + $eventLogResult = $this->connection->executeQuery('SELECT dimension FROM neos_neos_eventlog_domain_model_event where dimensionshash IS NULL AND dimension IS NOT NULL LIMIT 1'); + } + } +} diff --git a/composer.json b/composer.json index 6b9142bcea2..8f8f059a8e5 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,8 @@ "neos/party": "~7.0.3", "neos/fusion-form": "^1.0 || ^2.0", "neos/form": "*", - "neos/kickstarter": "~9.0.0" + "neos/kickstarter": "~9.0.0", + "enshrined/svg-sanitize": "^0.16.0" }, "replace": { "packagefactory/atomicfusion-afx": "*",