1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Install\Updates;
19
20use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
21use TYPO3\CMS\Core\Resource\File;
22use TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter;
23use TYPO3\CMS\Core\Resource\ResourceStorage;
24use TYPO3\CMS\Core\Resource\Security\SvgSanitizer;
25use TYPO3\CMS\Core\Resource\StorageRepository;
26use TYPO3\CMS\Core\Utility\GeneralUtility;
27
28class SvgFilesSanitization implements UpgradeWizardInterface, ConfirmableInterface
29{
30    /**
31     * @var StorageRepository
32     */
33    protected $storageRepository;
34
35    /**
36     * @var Confirmation
37     */
38    protected $confirmation;
39
40    public function __construct()
41    {
42        $this->storageRepository = GeneralUtility::makeInstance(StorageRepository::class);
43        $this->confirmation = new Confirmation(
44            'Continue sanitizing SVG files?',
45            $this->getDescription(),
46            false,
47            'sanitize, backup available',
48            'cancel',
49            false
50        );
51    }
52
53    /**
54     * Return the identifier for this wizard
55     * This should be the same string as used in the ext_localconf class registration
56     *
57     * @return string
58     */
59    public function getIdentifier(): string
60    {
61        return 'svgFilesSanitization';
62    }
63
64    /**
65     * Return the speaking name of this wizard
66     *
67     * @return string
68     */
69    public function getTitle(): string
70    {
71        return 'Sanitize existing SVG files in fileadmin folder';
72    }
73
74    /**
75     * Return the description for this wizard
76     *
77     * @return string
78     */
79    public function getDescription(): string
80    {
81        return 'This upgrade wizard will sanitize all SVG files located in local file storages. '
82            . 'It is very likely that file contents will be changed.' . "\n"
83            . 'Before continuing, please ensure a proper backup of *.svg and *.svgz files is in place before continuing.';
84    }
85
86    /**
87     * To avoid timeout issues, no check is performed in advance
88     *
89     * @return bool
90     */
91    public function updateNecessary(): bool
92    {
93        return true;
94    }
95
96    /**
97     * Execute the update
98     *
99     * Called when a wizard reports that an update is necessary
100     *
101     * @return bool
102     */
103    public function executeUpdate(): bool
104    {
105        return $this->processSvgFiles();
106    }
107
108    /**
109     * Returns an array of class names of Prerequisite classes
110     *
111     * This way a wizard can define dependencies like "database up-to-date" or
112     * "reference index updated"
113     *
114     * @return string[]
115     */
116    public function getPrerequisites(): array
117    {
118        return [];
119    }
120
121    /**
122     * Return a confirmation message instance
123     *
124     * @return Confirmation
125     */
126    public function getConfirmation(): Confirmation
127    {
128        return $this->confirmation;
129    }
130
131    /**
132     * @return ResourceStorage[]
133     */
134    protected function resolveLocalStorages(): array
135    {
136        return array_filter(
137            $this->storageRepository->findByStorageType('Local'),
138            function (ResourceStorage $storage) {
139                return $storage->isWritable();
140            }
141        );
142    }
143
144    /**
145     * @param ResourceStorage $storage
146     * @return File[]
147     * @throws InsufficientFolderAccessPermissionsException
148     */
149    protected function resolveSvgFiles(ResourceStorage $storage): array
150    {
151        $filter = GeneralUtility::makeInstance(FileExtensionFilter::class);
152        $filter->setAllowedFileExtensions(['svg', 'svgz']);
153        $files = $storage
154            ->setFileAndFolderNameFilters([
155                [$filter, 'filterFileList']
156            ])
157            ->getFilesInFolder(
158                $storage->getRootLevelFolder(),
159                0,
160                0,
161                true,
162                true
163            );
164        $storage->resetFileAndFolderNameFiltersToDefault();
165        return $files;
166    }
167
168    protected function processSvgFiles(): bool
169    {
170        $successful = true;
171        $sanitizer = GeneralUtility::makeInstance(SvgSanitizer::class);
172        foreach ($this->resolveLocalStorages() as $storage) {
173            try {
174                $svgFiles = $this->resolveSvgFiles($storage);
175            } catch (InsufficientFolderAccessPermissionsException $exception) {
176                // @todo Add notice/warning for this upgrade process
177                $successful = false;
178                continue;
179            }
180            foreach ($svgFiles as $svgFile) {
181                $oldFileContent = $svgFile->getContents();
182                $newFileContent = $sanitizer->sanitizeContent($oldFileContent);
183                if ($oldFileContent !== $newFileContent) {
184                    $svgFile->setContents($newFileContent);
185                }
186            }
187        }
188        return $successful;
189    }
190}
191