1<?php
2namespace TYPO3\CMS\Core\Resource\Filter;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use TYPO3\CMS\Core\DataHandling\DataHandler;
18use TYPO3\CMS\Core\Resource\Driver\DriverInterface;
19use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException;
20use TYPO3\CMS\Core\Resource\ResourceFactory;
21use TYPO3\CMS\Core\Utility\GeneralUtility;
22use TYPO3\CMS\Core\Utility\PathUtility;
23
24/**
25 * Utility methods for filtering filenames
26 */
27class FileExtensionFilter
28{
29    /**
30     * Allowed file extensions. If NULL, all extensions are allowed.
31     *
32     * @var array
33     */
34    protected $allowedFileExtensions;
35
36    /**
37     * Disallowed file extensions. If NULL, no extension is disallowed (i.e. all are allowed).
38     *
39     * @var array
40     */
41    protected $disallowedFileExtensions;
42
43    /**
44     * Entry method for use as DataHandler "inline" field filter
45     *
46     * @param array $parameters
47     * @param DataHandler $dataHandler
48     * @return array
49     */
50    public function filterInlineChildren(array $parameters, DataHandler $dataHandler)
51    {
52        $values = $parameters['values'];
53        if ($parameters['allowedFileExtensions']) {
54            $this->setAllowedFileExtensions($parameters['allowedFileExtensions']);
55        }
56        if ($parameters['disallowedFileExtensions']) {
57            $this->setDisallowedFileExtensions($parameters['disallowedFileExtensions']);
58        }
59        $cleanValues = [];
60        if (is_array($values)) {
61            foreach ($values as $value) {
62                if (empty($value)) {
63                    continue;
64                }
65                $parts = GeneralUtility::revExplode('_', $value, 2);
66                $fileReferenceUid = $parts[count($parts) - 1];
67                try {
68                    $fileReference = ResourceFactory::getInstance()->getFileReferenceObject($fileReferenceUid);
69                    $file = $fileReference->getOriginalFile();
70                    if ($this->isAllowed($file->getExtension())) {
71                        $cleanValues[] = $value;
72                    } else {
73                        // Remove the erroneously created reference record again
74                        $dataHandler->deleteAction('sys_file_reference', $fileReferenceUid);
75                    }
76                } catch (FileDoesNotExistException $e) {
77                    // do nothing
78                }
79            }
80        }
81        return $cleanValues;
82    }
83
84    /**
85     * Entry method for use as filelist filter.
86     *
87     * We have to use -1 as the „don't include“ return value, as call_user_func() will return FALSE
88     * if calling the method failed and thus we can't use that as a return value.
89     *
90     * @param string $itemName
91     * @param string $itemIdentifier
92     * @param string $parentIdentifier
93     * @param array $additionalInformation Additional information about the inspected item
94     * @param DriverInterface $driver
95     * @return bool|int -1 if the file should not be included in a listing
96     */
97    public function filterFileList($itemName, $itemIdentifier, $parentIdentifier, array $additionalInformation, DriverInterface $driver)
98    {
99        $returnCode = true;
100        // Early return in case no file filters are set at all
101        if ($this->allowedFileExtensions === null && $this->disallowedFileExtensions === null) {
102            return $returnCode;
103        }
104        // Check that this is a file and not a folder
105        if ($driver->fileExists($itemIdentifier)) {
106            try {
107                $fileInfo = $driver->getFileInfoByIdentifier($itemIdentifier, ['extension']);
108            } catch (\InvalidArgumentException $e) {
109                $fileInfo = [];
110            }
111            if (!isset($fileInfo['extension'])) {
112                trigger_error('Guessing FAL file extensions will be removed in TYPO3 v10.0. The FAL (' . get_class($driver) . ') driver method getFileInfoByIdentifier() should return the file extension.', E_USER_DEPRECATED);
113                $fileInfo['extension'] = PathUtility::pathinfo($itemIdentifier, PATHINFO_EXTENSION);
114            }
115            if (!$this->isAllowed($fileInfo['extension'])) {
116                $returnCode = -1;
117            }
118        }
119        return $returnCode;
120    }
121
122    /**
123     * Checks whether a file is allowed according to the criteria defined in the class variables ($this->allowedFileExtensions etc.)
124     *
125     * @param string $fileExt
126     * @return bool
127     */
128    protected function isAllowed($fileExt)
129    {
130        $fileExt = strtolower($fileExt);
131        $result = true;
132        // Check allowed file extensions
133        if ($this->allowedFileExtensions !== null && !empty($this->allowedFileExtensions) && !in_array($fileExt, $this->allowedFileExtensions)) {
134            $result = false;
135        }
136        // Check disallowed file extensions
137        if ($this->disallowedFileExtensions !== null && !empty($this->disallowedFileExtensions) && in_array($fileExt, $this->disallowedFileExtensions)) {
138            $result = false;
139        }
140        return $result;
141    }
142
143    /**
144     * Set allowed file extensions
145     *
146     * @param mixed $allowedFileExtensions  Comma-separated list or array, of allowed file extensions
147     */
148    public function setAllowedFileExtensions($allowedFileExtensions)
149    {
150        $this->allowedFileExtensions = $this->convertToLowercaseArray($allowedFileExtensions);
151    }
152
153    /**
154     * Set disallowed file extensions
155     *
156     * @param mixed $disallowedFileExtensions  Comma-separated list or array, of allowed file extensions
157     */
158    public function setDisallowedFileExtensions($disallowedFileExtensions)
159    {
160        $this->disallowedFileExtensions = $this->convertToLowercaseArray($disallowedFileExtensions);
161    }
162
163    /**
164     * Converts mixed (string or array) input arguments into an array, NULL if empty.
165     *
166     * All array values will be converted to lower case.
167     *
168     * @param mixed $inputArgument Comma-separated list or array.
169     * @return array
170     */
171    protected function convertToLowercaseArray($inputArgument)
172    {
173        $returnValue = null;
174        if (is_array($inputArgument)) {
175            $returnValue = $inputArgument;
176        } elseif ((string)$inputArgument !== '') {
177            $returnValue = GeneralUtility::trimExplode(',', $inputArgument);
178        }
179
180        if (is_array($returnValue)) {
181            $returnValue = array_map('strtolower', $returnValue);
182        }
183
184        return $returnValue;
185    }
186}
187