Completed
Push — master ( 6d34bd...c222a2 )
by Karol
03:16
created

Classes/Service/FileUploadService.php (2 issues)

Severity
1
<?php
2
declare(strict_types=1);
3
namespace SourceBroker\T3api\Service;
4
5
use InvalidArgumentException;
6
use SourceBroker\T3api\Domain\Model\OperationInterface;
7
use SourceBroker\T3api\Domain\Model\UploadSettings;
8
use Symfony\Component\HttpFoundation\File\UploadedFile;
9
use Symfony\Component\HttpFoundation\Request;
10
use TYPO3\CMS\Core\Resource\Exception;
11
use TYPO3\CMS\Core\Resource\Exception\ExistingTargetFolderException;
12
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
13
use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderWritePermissionsException;
14
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
15
use TYPO3\CMS\Core\Resource\File;
16
use TYPO3\CMS\Core\Resource\Folder;
17
use TYPO3\CMS\Core\Resource\ResourceFactory;
18
use TYPO3\CMS\Core\Resource\ResourceStorage;
19
use TYPO3\CMS\Core\SingletonInterface;
20
use TYPO3\CMS\Core\Utility\GeneralUtility;
21
use TYPO3\CMS\Core\Utility\PathUtility;
22
23
/**
24
 * Class FileUploadService
25
 */
26
class FileUploadService implements SingletonInterface
27
{
28
    /**
29
     * @var ResourceFactory
30
     */
31
    protected $resourceFactory;
32
33
    /**
34
     * @param ResourceFactory $resourceFactory
35
     */
36
    public function injectResourceFactory(ResourceFactory $resourceFactory): void
37
    {
38
        $this->resourceFactory = $resourceFactory;
39
    }
40
41
    /**
42
     * @param OperationInterface $operation
43
     * @param Request $request
44
     * @throws Exception
45
     * @return File
46
     */
47
    public function process(OperationInterface $operation, Request $request): File
48
    {
49
        /** @var UploadedFile $uploadedFile */
50
        $uploadedFile = $request->files->get('originalResource');
51
        $uploadSettings = $operation->getUploadSettings();
52
53
        $this->verifyFileExtension($uploadSettings, $uploadedFile);
54
55
        return $this->getUploadFolder($uploadSettings)
56
            ->addUploadedFile(
57
                [
58
                    'error' => $uploadedFile->getError(),
59
                    'name' => $this->getFilename($uploadSettings, $uploadedFile),
60
                    'size' => $uploadedFile->getSize(),
61
                    'tmp_name' => $uploadedFile->getPathname(),
62
                    'type' => $uploadedFile->getMimeType(),
63
                ],
64
                $operation->getUploadSettings()->getConflictMode()
65
            );
66
    }
67
68
    /**
69
     * @param UploadSettings $uploadSettings
70
     * @param UploadedFile $uploadedFile
71
     * @throws InvalidArgumentException
72
     * @return void
73
     */
74
    protected function verifyFileExtension(UploadSettings $uploadSettings, UploadedFile $uploadedFile): void
75
    {
76
        if (!GeneralUtility::verifyFilenameAgainstDenyPattern($uploadedFile->getClientOriginalName())) {
77
            throw new InvalidArgumentException(
78
                'Uploading files with PHP file extensions is not allowed!',
79
                1576999829435
80
            );
81
        }
82
83
        if (!empty($uploadSettings->getAllowedFileExtensions())) {
84
            $filePathInfo = PathUtility::pathinfo($uploadedFile->getClientOriginalName());
85
            if (!in_array(
86
                strtolower($filePathInfo['extension']),
87
                $uploadSettings->getAllowedFileExtensions(),
88
                true
89
            )) {
90
                throw new InvalidArgumentException(
91
                    sprintf(
92
                        'File extension `%s` is not allowed. Allowed file extensions are: `%s`',
93
                        strtolower($filePathInfo['extension']),
94
                        implode(', ', $uploadSettings->getAllowedFileExtensions())
95
                    ),
96
                    1577000112816
97
                );
98
            }
99
        }
100
    }
101
102
    /**
103
     * Creates upload folder if it not exists yet and returns it
104
     *
105
     * @param UploadSettings $uploadSettings
106
     *
107
     * @throws ExistingTargetFolderException
108
     * @throws InsufficientFolderAccessPermissionsException
109
     * @throws InsufficientFolderWritePermissionsException
110
     * @return Folder
111
     */
112
    protected function getUploadFolder(UploadSettings $uploadSettings): Folder
113
    {
114
        $uploadFolder = null;
115
116
        try {
117
            $uploadFolder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier(
118
                $uploadSettings->getFolder()
119
            );
120
        } catch (ResourceDoesNotExistException $exception) {
121
            $resource = $this->resourceFactory->getStorageObjectFromCombinedIdentifier(
122
                $uploadSettings->getFolder()
123
            );
124
125
            if (!$resource instanceof ResourceStorage) {
0 ignored issues
show
$resource is always a sub-type of TYPO3\CMS\Core\Resource\ResourceStorage.
Loading history...
126
                throw new InvalidArgumentException(
127
                    sprintf('Invalid upload path (`%s`). Storage does not exist?', $uploadSettings->getFolder()),
128
                    1577262016243
129
                );
130
            }
131
132
            $path = explode('/', $uploadSettings->getFolder());
133
134
            // removes storage identifier
135
            array_shift($path);
136
137
            do {
138
                $directoryName = array_shift($path);
139
140
                if ($uploadFolder && $resource->hasFolderInFolder($directoryName, $uploadFolder)) {
141
                    $uploadFolder = $resource->getFolderInFolder($directoryName, $uploadFolder);
142
                } elseif (!$uploadFolder && $resource->hasFolder($directoryName)) {
143
                    $uploadFolder = $resource->getFolder($directoryName);
144
                } else {
145
                    $uploadFolder = $resource->createFolder($directoryName, $uploadFolder);
146
                }
147
            } while (count($path));
148
        }
149
150
        if (!$uploadFolder instanceof Folder) {
0 ignored issues
show
$uploadFolder is always a sub-type of TYPO3\CMS\Core\Resource\Folder.
Loading history...
151
            throw new InvalidArgumentException(
152
                sprintf(
153
                    'Can not upload - `%s` is not a folder and could not create it.',
154
                    $uploadSettings->getFolder()
155
                ),
156
                1577001080960
157
            );
158
        }
159
160
        return $uploadFolder;
161
    }
162
163
    /**
164
     * @param UploadSettings $uploadSettings
165
     * @param UploadedFile $uploadedFile
166
     * @return string
167
     */
168
    public function getFilename(UploadSettings $uploadSettings, UploadedFile $uploadedFile): string
169
    {
170
        $replacements['filename'] = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
171
172
        $fileExtension = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_EXTENSION);
173
        $replacements['extension'] = $fileExtension ?? '';
174
        $replacements['extensionWithDot'] = $fileExtension ? '.' . $fileExtension : '';
175
176
        if (strpos($uploadSettings->getFilenameMask(), '[contentHash]') !== false) {
177
            $replacements['contentHash'] = hash_file(
178
                $uploadSettings->getContentHashAlgorithm(),
179
                $uploadedFile->getPathname()
180
            );
181
        }
182
        if (strpos($uploadSettings->getFilenameMask(), '[filenameHash]') !== false) {
183
            $replacements['filenameHash'] = hash(
184
                $uploadSettings->getFilenameHashAlgorithm(),
185
                $uploadedFile->getClientOriginalName()
186
            );
187
        }
188
        return preg_replace_callback(
189
            "/\\[([A-Za-z0-9_:]+)\\]/",
190
            static function ($match) use ($replacements) {
191
                return $replacements[$match[1]];
192
            },
193
            $uploadSettings->getFilenameMask()
194
        );
195
    }
196
}
197