[BUGFIX] Remove unused references to former ElementBrowser
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / File / BasicFileUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility\File;
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
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Core\Utility\PathUtility;
19
20 /**
21 * Contains class with basic file management functions
22 *
23 * Contains functions for management, validation etc of files in TYPO3,
24 * using the concepts of web- and ftp-space. Please see the comment for the
25 * init() function
26 *
27 * Note: All methods in this class should not be used anymore since TYPO3 6.0.
28 * Please use corresponding TYPO3\\CMS\\Core\\Resource\\ResourceStorage
29 * (fetched via BE_USERS->getFileStorages()), as all functions should be
30 * found there (in a cleaner manner).
31 */
32 class BasicFileUtility
33 {
34 /**
35 * @var string
36 */
37 const UNSAFE_FILENAME_CHARACTER_EXPRESSION = '\\x00-\\x2C\\/\\x3A-\\x3F\\x5B-\\x60\\x7B-\\xBF';
38
39 /**
40 * Prefix which will be prepended the file when using the getUniqueName-function
41 *
42 * @var string
43 */
44 public $getUniqueNamePrefix = '';
45
46 /**
47 * This number decides the highest allowed appended number used on a filename before we use naming with unique strings
48 *
49 * @var int
50 */
51 public $maxNumber = 99;
52
53 /**
54 * This number decides how many characters out of a unique MD5-hash that is appended to a filename if getUniqueName is asked to find an available filename.
55 *
56 * @var int
57 */
58 public $uniquePrecision = 6;
59
60 /**
61 * This is the maximum length of names treated by cleanFileName()
62 *
63 * @var int
64 */
65 public $maxInputNameLen = 60;
66
67 /**
68 * Temp-foldername. A folder in the root of one of the mounts with this name is regarded a TEMP-folder (used for upload from clipboard)
69 *
70 * @var string
71 */
72 public $tempFN = '_temp_';
73
74 /**
75 * @var array
76 */
77 public $f_ext = array();
78
79 /**
80 * See comment in header
81 *
82 * @var array
83 */
84 public $mounts = array();
85
86 /**
87 * See comment in header
88 *
89 * @var string
90 */
91 public $webPath = '';
92
93 /**
94 * Set to DOCUMENT_ROOT.
95 *
96 * @var bool
97 */
98 public $isInit = 0;
99
100 /**
101 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
102 */
103 public $csConvObj;
104
105 /**********************************
106 *
107 * Checking functions
108 *
109 **********************************/
110 /**
111 * Constructor
112 * This function should be called to initialise the internal arrays $this->mounts and $this->f_ext
113 *
114 * A typical example of the array $mounts is this:
115 * $mounts[xx][path] = (..a mounted path..)
116 * the 'xx'-keys is just numerical from zero. There are also a [name] and [type] value that just denotes the mountname and type. Not used for athentication here.
117 * $this->mounts is traversed in the function checkPathAgainstMounts($thePath), and it is checked that $thePath is actually below one of the mount-paths
118 * The mountpaths are with a trailing '/'. $thePath must be with a trailing '/' also!
119 * As you can see, $this->mounts is very critical! This is the array that decides where the user will be allowed to copy files!!
120 *
121 * A typical example of the array $f_ext is this:
122 * $f_ext['webspace']['allow']='';
123 * $f_ext['webspace']['deny']= PHP_EXTENSIONS_DEFAULT;
124 * $f_ext['ftpspace']['allow']='*';
125 * $f_ext['ftpspace']['deny']='';
126 * The control of fileextensions goes in two catagories. Webspace and Ftpspace. Webspace is folders accessible from a webbrowser (below TYPO3_DOCUMENT_ROOT) and ftpspace is everything else.
127 * The control is done like this: If an extension matches 'allow' then the check returns TRUE. If not and an extension matches 'deny' then the check return FALSE. If no match at all, returns TRUE.
128 * You list extensions comma-separated. If the value is a '*' every extension is allowed
129 * The list is case-insensitive when used in this class (see init())
130 * Typically TYPO3_CONF_VARS['BE']['fileExtensions'] would be passed along as $f_ext.
131 *
132 * Example:
133 * $basicff->init(array(), $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']);
134 *
135 * @param array Not in use anymore
136 * @param array Array with information about allowed and denied file extensions. Typically passed: $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']
137 * @return void
138 */
139 public function init($mounts, $f_ext)
140 {
141 $this->f_ext['webspace']['allow'] = GeneralUtility::uniqueList(strtolower($f_ext['webspace']['allow']));
142 $this->f_ext['webspace']['deny'] = GeneralUtility::uniqueList(strtolower($f_ext['webspace']['deny']));
143 $this->f_ext['ftpspace']['allow'] = GeneralUtility::uniqueList(strtolower($f_ext['ftpspace']['allow']));
144 $this->f_ext['ftpspace']['deny'] = GeneralUtility::uniqueList(strtolower($f_ext['ftpspace']['deny']));
145
146 $this->mounts = (!empty($mounts) ? $mounts : array());
147 $this->webPath = GeneralUtility::getIndpEnv('TYPO3_DOCUMENT_ROOT');
148 $this->isInit = 1;
149 $this->maxInputNameLen = $GLOBALS['TYPO3_CONF_VARS']['SYS']['maxFileNameLength'] ?: $this->maxInputNameLen;
150 }
151
152 /**
153 * Checks if a $iconkey (fileextension) is allowed according to $this->f_ext.
154 *
155 * @param string The extension to check, eg. "php" or "html" etc.
156 * @param string Either "webspage" or "ftpspace" - points to a key in $this->f_ext
157 * @return bool TRUE if file extension is allowed.
158 * @todo Deprecate, but still in use by checkIfAllowed()
159 * @deprecated but still in use in the Core. Don't use in your extensions!
160 */
161 public function is_allowed($iconkey, $type)
162 {
163 if (isset($this->f_ext[$type])) {
164 $ik = strtolower($iconkey);
165 if ($ik) {
166 // If the extension is found amongst the allowed types, we return TRUE immediately
167 if ($this->f_ext[$type]['allow'] == '*' || GeneralUtility::inList($this->f_ext[$type]['allow'], $ik)) {
168 return true;
169 }
170 // If the extension is found amongst the denied types, we return FALSE immediately
171 if ($this->f_ext[$type]['deny'] == '*' || GeneralUtility::inList($this->f_ext[$type]['deny'], $ik)) {
172 return false;
173 }
174 // If no match we return TRUE
175 return true;
176 } else {
177 // If no extension:
178 if ($this->f_ext[$type]['allow'] == '*') {
179 return true;
180 }
181 if ($this->f_ext[$type]['deny'] == '*') {
182 return false;
183 }
184 return true;
185 }
186 }
187 return false;
188 }
189
190 /**
191 * Returns TRUE if you can operate of ANY file ('*') in the space $theDest is in ('webspace' / 'ftpspace')
192 *
193 * @param string Absolute path
194 * @return bool
195 * @todo Deprecate: but still in use by through func_unzip in ExtendedFileUtility
196 * @deprecated but still in use in the Core. Don't use in your extensions!
197 */
198 public function checkIfFullAccess($theDest)
199 {
200 $type = $this->is_webpath($theDest) ? 'webspace' : 'ftpspace';
201 if (isset($this->f_ext[$type])) {
202 if ((string)$this->f_ext[$type]['deny'] == '' || $this->f_ext[$type]['allow'] == '*') {
203 return true;
204 }
205 }
206 }
207
208 /**
209 * Checks if $this->webPath (should be TYPO3_DOCUMENT_ROOT) is in the first part of $path
210 * Returns TRUE also if $this->init is not set or if $path is empty...
211 *
212 * @param string Absolute path to check
213 * @return bool
214 * @todo Deprecate, but still in use by DataHandler
215 * @deprecated but still in use in the Core. Don't use in your extensions!
216 */
217 public function is_webpath($path)
218 {
219 if ($this->isInit) {
220 $testPath = $this->slashPath($path);
221 $testPathWeb = $this->slashPath($this->webPath);
222 if ($testPathWeb && $testPath) {
223 return GeneralUtility::isFirstPartOfStr($testPath, $testPathWeb);
224 }
225 }
226 return true;
227 }
228
229 /**
230 * If the filename is given, check it against the TYPO3_CONF_VARS[BE][fileDenyPattern] +
231 * Checks if the $ext fileextension is allowed in the path $theDest (this is based on whether $theDest is below the $this->webPath)
232 *
233 * @param string File extension, eg. "php" or "html
234 * @param string Absolute path for which to test
235 * @param string Filename to check against TYPO3_CONF_VARS[BE][fileDenyPattern]
236 * @return bool TRUE if extension/filename is allowed
237 * @todo Deprecate, but still in use by DataHandler
238 * @deprecated but still in use in the Core. Don't use in your extensions!
239 */
240 public function checkIfAllowed($ext, $theDest, $filename = '')
241 {
242 return GeneralUtility::verifyFilenameAgainstDenyPattern($filename) && $this->is_allowed($ext, ($this->is_webpath($theDest) ? 'webspace' : 'ftpspace'));
243 }
244
245 /**
246 * Cleans $theDir for slashes in the end of the string and returns the new path, if it exists on the server.
247 *
248 * @param string Directory path to check
249 * @return string Returns the cleaned up directory name if OK, otherwise FALSE.
250 * @todo Deprecate: but still in use by getUniqueName (used by DataHandler)
251 * @deprecated but still in use in the Core. Don't use in your extensions!
252 */
253 public function is_directory($theDir)
254 {
255 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
256 if (GeneralUtility::validPathStr($theDir)) {
257 $theDir = PathUtility::getCanonicalPath($theDir);
258 if (@is_dir($theDir)) {
259 return $theDir;
260 }
261 }
262 return false;
263 }
264
265 /**
266 * Returns the destination path/filename of a unique filename/foldername in that path.
267 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
268 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
269 *
270 * @param string The input filename to check
271 * @param string The directory for which to return a unique filename for $theFile. $theDest MUST be a valid directory. Should be absolute.
272 * @param bool If set the filename is returned with the path prepended without checking whether it already existed!
273 * @return string The destination absolute filepath (not just the name!) of a unique filename/foldername in that path.
274 * @see \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue()
275 * @todo Deprecate, but still in use by the Core (DataHandler...)
276 * @deprecated but still in use in the Core. Don't use in your extensions!
277 */
278 public function getUniqueName($theFile, $theDest, $dontCheckForUnique = 0)
279 {
280 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
281 $theDest = $this->is_directory($theDest);
282 // $theDest is cleaned up
283 $origFileInfo = GeneralUtility::split_fileref($theFile);
284 // Fetches info about path, name, extension of $theFile
285 if ($theDest) {
286 if ($this->getUniqueNamePrefix) {
287 // Adds prefix
288 $origFileInfo['file'] = $this->getUniqueNamePrefix . $origFileInfo['file'];
289 $origFileInfo['filebody'] = $this->getUniqueNamePrefix . $origFileInfo['filebody'];
290 }
291 // Check if the file exists and if not - return the filename...
292 $fileInfo = $origFileInfo;
293 $theDestFile = $theDest . '/' . $fileInfo['file'];
294 // The destinations file
295 if (!file_exists($theDestFile) || $dontCheckForUnique) {
296 // If the file does NOT exist we return this filename
297 return $theDestFile;
298 }
299 // Well the filename in its pure form existed. Now we try to append numbers / unique-strings and see if we can find an available filename...
300 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filebody']);
301 // This removes _xx if appended to the file
302 $theOrigExt = $origFileInfo['realFileext'] ? '.' . $origFileInfo['realFileext'] : '';
303 for ($a = 1; $a <= $this->maxNumber + 1; $a++) {
304 if ($a <= $this->maxNumber) {
305 // First we try to append numbers
306 $insert = '_' . sprintf('%02d', $a);
307 } else {
308 // .. then we try unique-strings...
309 $insert = '_' . substr(md5(uniqid('', true)), 0, $this->uniquePrecision);
310 }
311 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
312 $theDestFile = $theDest . '/' . $theTestFile;
313 // The destinations file
314 if (!file_exists($theDestFile)) {
315 // If the file does NOT exist we return this filename
316 return $theDestFile;
317 }
318 }
319 }
320 }
321
322 /**
323 * Checks if $thePath is a path under one of the paths in $this->mounts
324 * See comment in the header of this class.
325 *
326 * @param string $thePath MUST HAVE a trailing '/' in order to match correctly with the mounts
327 * @return string The key to the first mount found, otherwise nothing is returned.
328 * @see init()
329 * @todo: deprecate this function, now done in the Storage object. But still in use by impexp and ExtendedFileUtility
330 * @deprecated but still in use in the Core. Don't use in your extensions!
331 */
332 public function checkPathAgainstMounts($thePath)
333 {
334 if ($thePath && GeneralUtility::validPathStr($thePath) && is_array($this->mounts)) {
335 foreach ($this->mounts as $k => $val) {
336 if (GeneralUtility::isFirstPartOfStr($thePath, $val['path'])) {
337 return $k;
338 }
339 }
340 }
341 }
342
343 /**
344 * Find first web folder (relative to PATH_site.'fileadmin') in filemounts array
345 *
346 * @return string The key to the first mount inside PATH_site."fileadmin" found, otherwise nothing is returned.
347 * @todo: deprecate this function. But still in use by impexp
348 * @deprecated but still in use in the Core. Don't use in your extensions!
349 */
350 public function findFirstWebFolder()
351 {
352 // @todo: where and when to use this function?
353 if (is_array($this->mounts)) {
354 foreach ($this->mounts as $k => $val) {
355 if (GeneralUtility::isFirstPartOfStr($val['path'], PATH_site . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'])) {
356 return $k;
357 }
358 }
359 }
360 }
361
362 /*********************
363 *
364 * Cleaning functions
365 *
366 *********************/
367 /**
368 * Returns a string which has a slash '/' appended if it doesn't already have that slash
369 *
370 * @param string Input string
371 * @return string Output string with a slash in the end (if not already there)
372 * @todo Deprecate, but still in use by is_webpath, used by DataHandler
373 * @deprecated but still in use in the Core. Don't use in your extensions!
374 */
375 public function slashPath($path)
376 {
377 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
378 // @todo: should be done with rtrim($path, '/') . '/';
379 if (substr($path, -1) != '/') {
380 return $path . '/';
381 }
382 return $path;
383 }
384
385 /**
386 * Returns a string where any character not matching [.a-zA-Z0-9_-] is substituted by '_'
387 * Trailing dots are removed
388 *
389 * @param string $fileName Input string, typically the body of a filename
390 * @param string $charset Charset of the a filename (defaults to current charset; depending on context)
391 * @return string Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_' and trailing dots removed
392 * @todo Deprecate, but still in use by the core
393 * @deprecated but still in use in the Core. Don't use in your extensions!
394 */
395 public function cleanFileName($fileName, $charset = '')
396 {
397 // Handle UTF-8 characters
398 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
399 // allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
400 $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . ']/u', '_', trim($fileName));
401 } else {
402 // Get conversion object or initialize if needed
403 if (!is_object($this->csConvObj)) {
404 if (TYPO3_MODE == 'FE') {
405 $this->csConvObj = $GLOBALS['TSFE']->csConvObj;
406 } elseif (is_object($GLOBALS['LANG'])) {
407 // BE assumed:
408 $this->csConvObj = $GLOBALS['LANG']->csConvObj;
409 } else {
410 // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
411 $this->csConvObj = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Charset\CharsetConverter::class);
412 }
413 }
414 // Define character set
415 if (!$charset) {
416 if (TYPO3_MODE == 'FE') {
417 $charset = $GLOBALS['TSFE']->renderCharset;
418 } else {
419 // Backend
420 $charset = 'utf-8';
421 }
422 }
423 // If a charset was found, convert filename
424 if ($charset) {
425 $fileName = $this->csConvObj->specCharsToASCII($charset, $fileName);
426 }
427 // Replace unwanted characters by underscores
428 $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . '\\xC0-\\xFF]/', '_', trim($fileName));
429 }
430 // Strip trailing dots and return
431 return rtrim($cleanFileName, '.');
432 }
433 }