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