652acc9657bfcd97458ce69d7a69947eac6991b3
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / File / BasicFileUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Utility\File;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the text file GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Utility\PathUtility;
32
33 /**
34 * Contains class with basic file management functions
35 *
36 * Contains functions for management, validation etc of files in TYPO3,
37 * using the concepts of web- and ftp-space. Please see the comment for the
38 * init() function
39 *
40 * Note: All methods in this class should not be used anymore since TYPO3 6.0.
41 * Please use corresponding TYPO3\\CMS\\Core\\Resource\\ResourceStorage
42 * (fetched via BE_USERS->getFileStorages()), as all functions should be
43 * found there (in a cleaner manner).
44 *
45 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
46 */
47 class BasicFileUtility {
48 /**
49 * @var string
50 */
51 const UNSAFE_FILENAME_CHARACTER_EXPRESSION = '\\x00-\\x2C\\/\\x3A-\\x3F\\x5B-\\x60\\x7B-\\xBF';
52
53 /**
54 * @todo Define visibility
55 */
56 public $getUniqueNamePrefix = '';
57
58 // Prefix which will be prepended the file when using the getUniqueName-function
59 /**
60 * @todo Define visibility
61 */
62 public $maxNumber = 99;
63
64 // This number decides the highest allowed appended number used on a filename before we use naming with unique strings
65 /**
66 * @todo Define visibility
67 */
68 public $uniquePrecision = 6;
69
70 // 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.
71 /**
72 * @todo Define visibility
73 */
74 public $maxInputNameLen = 60;
75
76 // This is the maximum length of names treated by cleanFileName()
77 /**
78 * @todo Define visibility
79 */
80 public $tempFN = '_temp_';
81
82 // 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)
83 // internal
84 /**
85 * @todo Define visibility
86 */
87 public $f_ext = array();
88
89 // See comment in header
90 /**
91 * @todo Define visibility
92 */
93 public $mounts = array();
94
95 // See comment in header
96 /**
97 * @todo Define visibility
98 */
99 public $webPath = '';
100
101 // Set to DOCUMENT_ROOT.
102 /**
103 * @todo Define visibility
104 */
105 public $isInit = 0;
106
107 /**
108 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
109 */
110 public $csConvObj;
111
112 // Set to TRUE after init()/start();
113 /**********************************
114 *
115 * Checking functions
116 *
117 **********************************/
118 /**
119 * Constructor
120 * This function should be called to initialise the internal arrays $this->mounts and $this->f_ext
121 *
122 * A typical example of the array $mounts is this:
123 * $mounts[xx][path] = (..a mounted path..)
124 * 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.
125 * $this->mounts is traversed in the function checkPathAgainstMounts($thePath), and it is checked that $thePath is actually below one of the mount-paths
126 * The mountpaths are with a trailing '/'. $thePath must be with a trailing '/' also!
127 * As you can see, $this->mounts is very critical! This is the array that decides where the user will be allowed to copy files!!
128 * Typically the global var $WEBMOUNTS would be passed along as $mounts
129 *
130 * A typical example of the array $f_ext is this:
131 * $f_ext['webspace']['allow']='';
132 * $f_ext['webspace']['deny']= PHP_EXTENSIONS_DEFAULT;
133 * $f_ext['ftpspace']['allow']='*';
134 * $f_ext['ftpspace']['deny']='';
135 * 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.
136 * 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.
137 * You list extensions comma-separated. If the value is a '*' every extension is allowed
138 * The list is case-insensitive when used in this class (see init())
139 * Typically TYPO3_CONF_VARS['BE']['fileExtensions'] would be passed along as $f_ext.
140 *
141 * Example:
142 * $basicff->init(array(), $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']);
143 *
144 * @param array Not in use anymore
145 * @param array Array with information about allowed and denied file extensions. Typically passed: $GLOBALS['TYPO3_CONF_VARS']['BE']['fileExtensions']
146 * @return void
147 * @see typo3/init.php, \TYPO3\CMS\Core\Authentication\BackendUserAuthentication::returnFilemounts()
148 */
149 public function init($mounts, $f_ext) {
150 $this->f_ext['webspace']['allow'] = GeneralUtility::uniqueList(strtolower($f_ext['webspace']['allow']));
151 $this->f_ext['webspace']['deny'] = GeneralUtility::uniqueList(strtolower($f_ext['webspace']['deny']));
152 $this->f_ext['ftpspace']['allow'] = GeneralUtility::uniqueList(strtolower($f_ext['ftpspace']['allow']));
153 $this->f_ext['ftpspace']['deny'] = GeneralUtility::uniqueList(strtolower($f_ext['ftpspace']['deny']));
154
155 $this->mounts = (!empty($mounts) ? $mounts : array());
156 $this->webPath = GeneralUtility::getIndpEnv('TYPO3_DOCUMENT_ROOT');
157 $this->isInit = 1;
158 $this->maxInputNameLen = $GLOBALS['TYPO3_CONF_VARS']['SYS']['maxFileNameLength'] ?: $this->maxInputNameLen;
159 }
160
161 /**
162 * Returns an array with a whole lot of fileinformation.
163 * Information includes:
164 * - path : path part of give file
165 * - file : filename
166 * - filebody : filename without extension
167 * - fileext : lowercase extension
168 * - realFileext : extension
169 * - tstamp : timestamp of modification
170 * - size : file size
171 * - type : file type (block/char/dir/fifo/file/link)
172 * - owner : user ID of owner of file
173 * - perms : numerical representation of file permissions
174 * - writable : is file writeable by web user (FALSE = yes; TRUE = no) *)
175 * - readable : is file readable by web user (FALSE = yes; TRUE = no) *)
176 *
177 * ) logic is reversed because of handling by functions in TYPO3\CMS\Filelist\FileList
178 *
179 * @param string Filepath to existing file. Should probably be absolute. Filefunctions are performed on this value.
180 * @return array Information about the file in the filepath
181 * @deprecated since TYPO3 6.0. Please use corresponding TYPO3\\CMS\\Core\\Resource\\ResourceStorage (fetched via BE_USERS->getFileStorages())
182 */
183 public function getTotalFileInfo($wholePath) {
184 GeneralUtility::logDeprecatedFunction();
185 $theuser = getmyuid();
186 $info = GeneralUtility::split_fileref($wholePath);
187 $info['tstamp'] = @filemtime($wholePath);
188 $info['size'] = @filesize($wholePath);
189 $info['type'] = @filetype($wholePath);
190 $info['owner'] = @fileowner($wholePath);
191 $info['perms'] = @fileperms($wholePath);
192 $info['writable'] = !@is_writable($wholePath);
193 $info['readable'] = !@is_readable($wholePath);
194 return $info;
195 }
196
197 /**
198 * Checks if a $iconkey (fileextension) is allowed according to $this->f_ext.
199 *
200 * @param string The extension to check, eg. "php" or "html" etc.
201 * @param string Either "webspage" or "ftpspace" - points to a key in $this->f_ext
202 * @return boolean TRUE if file extension is allowed.
203 * @todo Deprecate, but still in use by checkIfAllowed()
204 * @deprecated but still in use in the Core. Don't use in your extensions!
205 */
206 public function is_allowed($iconkey, $type) {
207 if (isset($this->f_ext[$type])) {
208 $ik = strtolower($iconkey);
209 if ($ik) {
210 // If the extension is found amongst the allowed types, we return TRUE immediately
211 if ($this->f_ext[$type]['allow'] == '*' || GeneralUtility::inList($this->f_ext[$type]['allow'], $ik)) {
212 return TRUE;
213 }
214 // If the extension is found amongst the denied types, we return FALSE immediately
215 if ($this->f_ext[$type]['deny'] == '*' || GeneralUtility::inList($this->f_ext[$type]['deny'], $ik)) {
216 return FALSE;
217 }
218 // If no match we return TRUE
219 return TRUE;
220 } else {
221 // If no extension:
222 if ($this->f_ext[$type]['allow'] == '*') {
223 return TRUE;
224 }
225 if ($this->f_ext[$type]['deny'] == '*') {
226 return FALSE;
227 }
228 return TRUE;
229 }
230 }
231 return FALSE;
232 }
233
234 /**
235 * Returns TRUE if you can operate of ANY file ('*') in the space $theDest is in ('webspace' / 'ftpspace')
236 *
237 * @param string Absolute path
238 * @return boolean
239 * @todo Deprecate: but still in use by through func_unzip in ExtendedFileUtility
240 * @deprecated but still in use in the Core. Don't use in your extensions!
241 */
242 public function checkIfFullAccess($theDest) {
243 $type = $this->is_webpath($theDest) ? 'webspace' : 'ftpspace';
244 if (isset($this->f_ext[$type])) {
245 if ((string) $this->f_ext[$type]['deny'] == '' || $this->f_ext[$type]['allow'] == '*') {
246 return TRUE;
247 }
248 }
249 }
250
251 /**
252 * Checks if $this->webPath (should be TYPO3_DOCUMENT_ROOT) is in the first part of $path
253 * Returns TRUE also if $this->init is not set or if $path is empty...
254 *
255 * @param string Absolute path to check
256 * @return boolean
257 * @todo Deprecate, but still in use by DataHandler
258 * @deprecated but still in use in the Core. Don't use in your extensions!
259 */
260 public function is_webpath($path) {
261 if ($this->isInit) {
262 $testPath = $this->slashPath($path);
263 $testPathWeb = $this->slashPath($this->webPath);
264 if ($testPathWeb && $testPath) {
265 return GeneralUtility::isFirstPartOfStr($testPath, $testPathWeb);
266 }
267 }
268 return TRUE;
269 }
270
271 /**
272 * If the filename is given, check it against the TYPO3_CONF_VARS[BE][fileDenyPattern] +
273 * Checks if the $ext fileextension is allowed in the path $theDest (this is based on whether $theDest is below the $this->webPath)
274 *
275 * @param string File extension, eg. "php" or "html
276 * @param string Absolute path for which to test
277 * @param string Filename to check against TYPO3_CONF_VARS[BE][fileDenyPattern]
278 * @return boolean TRUE if extension/filename is allowed
279 * @todo Deprecate, but still in use by DataHandler
280 * @deprecated but still in use in the Core. Don't use in your extensions!
281 */
282 public function checkIfAllowed($ext, $theDest, $filename = '') {
283 return GeneralUtility::verifyFilenameAgainstDenyPattern($filename) && $this->is_allowed($ext, ($this->is_webpath($theDest) ? 'webspace' : 'ftpspace'));
284 }
285
286 /**
287 * Returns TRUE if the input filename string is shorter than $this->maxInputNameLen.
288 *
289 * @param string Filename, eg "somefile.html
290 * @return boolean
291 * @todo Define visibility
292 * @deprecated since TYPO3 6.0. Please use corresponding TYPO3\\CMS\\Core\\Resource\\ResourceStorage (fetched via BE_USERS->getFileStorages())
293 */
294 public function checkFileNameLen($fileName) {
295 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
296 GeneralUtility::logDeprecatedFunction();
297 return strlen($fileName) <= $this->maxInputNameLen;
298 }
299
300 /**
301 * Cleans $theDir for slashes in the end of the string and returns the new path, if it exists on the server.
302 *
303 * @param string Directory path to check
304 * @return string Returns the cleaned up directory name if OK, otherwise FALSE.
305 * @todo Deprecate: but still in use by getUniqueName (used by DataHandler)
306 * @deprecated but still in use in the Core. Don't use in your extensions!
307 */
308 public function is_directory($theDir) {
309 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
310 if (GeneralUtility::validPathStr($theDir)) {
311 $theDir = PathUtility::getCanonicalPath($theDir);
312 if (@is_dir($theDir)) {
313 return $theDir;
314 }
315 }
316 return FALSE;
317 }
318
319 /**
320 * Wrapper for \TYPO3\CMS\Core\Utility\GeneralUtility::validPathStr()
321 *
322 * @param string Filepath to evaluate
323 * @return boolean TRUE, if no '//', '..' or '\' is in the $theFile
324 * @see \TYPO3\CMS\Core\Utility\GeneralUtility::validPathStr()
325 * @deprecated since TYPO3 6.0. Use GeneralUtility::validPathStr() instead
326 */
327 public function isPathValid($theFile) {
328 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
329 GeneralUtility::logDeprecatedFunction();
330 return GeneralUtility::validPathStr($theFile);
331 }
332
333 /**
334 * Returns the destination path/filename of a unique filename/foldername in that path.
335 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
336 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
337 *
338 * @param string The input filename to check
339 * @param string The directory for which to return a unique filename for $theFile. $theDest MUST be a valid directory. Should be absolute.
340 * @param boolean If set the filename is returned with the path prepended without checking whether it already existed!
341 * @return string The destination absolute filepath (not just the name!) of a unique filename/foldername in that path.
342 * @see \TYPO3\CMS\Core\DataHandling\DataHandler::checkValue()
343 * @todo Deprecate, but still in use by the Core (DataHandler...)
344 * @deprecated but still in use in the Core. Don't use in your extensions!
345 */
346 public function getUniqueName($theFile, $theDest, $dontCheckForUnique = 0) {
347 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
348 $theDest = $this->is_directory($theDest);
349 // $theDest is cleaned up
350 $origFileInfo = GeneralUtility::split_fileref($theFile);
351 // Fetches info about path, name, extension of $theFile
352 if ($theDest) {
353 if ($this->getUniqueNamePrefix) {
354 // Adds prefix
355 $origFileInfo['file'] = $this->getUniqueNamePrefix . $origFileInfo['file'];
356 $origFileInfo['filebody'] = $this->getUniqueNamePrefix . $origFileInfo['filebody'];
357 }
358 // Check if the file exists and if not - return the filename...
359 $fileInfo = $origFileInfo;
360 $theDestFile = $theDest . '/' . $fileInfo['file'];
361 // The destinations file
362 if (!file_exists($theDestFile) || $dontCheckForUnique) {
363 // If the file does NOT exist we return this filename
364 return $theDestFile;
365 }
366 // 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...
367 $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filebody']);
368 // This removes _xx if appended to the file
369 $theOrigExt = $origFileInfo['realFileext'] ? '.' . $origFileInfo['realFileext'] : '';
370 for ($a = 1; $a <= $this->maxNumber + 1; $a++) {
371 if ($a <= $this->maxNumber) {
372 // First we try to append numbers
373 $insert = '_' . sprintf('%02d', $a);
374 } else {
375 // .. then we try unique-strings...
376 $insert = '_' . substr(md5(uniqId('')), 0, $this->uniquePrecision);
377 }
378 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
379 $theDestFile = $theDest . '/' . $theTestFile;
380 // The destinations file
381 if (!file_exists($theDestFile)) {
382 // If the file does NOT exist we return this filename
383 return $theDestFile;
384 }
385 }
386 }
387 }
388
389 /**
390 * Checks if $thePath is a path under one of the paths in $this->mounts
391 * See comment in the header of this class.
392 *
393 * @param string $thePath MUST HAVE a trailing '/' in order to match correctly with the mounts
394 * @return string The key to the first mount found, otherwise nothing is returned.
395 * @see init()
396 * @todo: deprecate this function, now done in the Storage object. But still in use by impexp and ElementBrowser
397 * @deprecated but still in use in the Core. Don't use in your extensions!
398 */
399 public function checkPathAgainstMounts($thePath) {
400 if ($thePath && GeneralUtility::validPathStr($thePath) && is_array($this->mounts)) {
401 foreach ($this->mounts as $k => $val) {
402 if (GeneralUtility::isFirstPartOfStr($thePath, $val['path'])) {
403 return $k;
404 }
405 }
406 }
407 }
408
409 /**
410 * Find first web folder (relative to PATH_site.'fileadmin') in filemounts array
411 *
412 * @return string The key to the first mount inside PATH_site."fileadmin" found, otherwise nothing is returned.
413 * @todo: deprecate this function. But still in use by impexp
414 * @deprecated but still in use in the Core. Don't use in your extensions!
415 */
416 public function findFirstWebFolder() {
417 // @todo: where and when to use this function?
418 if (is_array($this->mounts)) {
419 foreach ($this->mounts as $k => $val) {
420 if (GeneralUtility::isFirstPartOfStr($val['path'], PATH_site . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileadminDir'])) {
421 return $k;
422 }
423 }
424 }
425 }
426
427 /**
428 * Removes filemount part of a path, thus blinding the position.
429 * Takes a path, $thePath, and removes the part of the path which equals the filemount.
430 *
431 * @param string $thePath is a path which MUST be found within one of the internally set filemounts, $this->mounts
432 * @return string The processed input path
433 * @deprecated since TYPO3 6.0. No replacement
434 */
435 public function blindPath($thePath) {
436 // @todo: where and when to use this function?
437 GeneralUtility::logDeprecatedFunction();
438 $k = $this->checkPathAgainstMounts($thePath);
439 if ($k) {
440 $name = '';
441 $name .= '[' . $this->mounts[$k]['name'] . ']: ';
442 $name .= substr($thePath, strlen($this->mounts[$k]['path']));
443 return $name;
444 }
445 }
446
447 /**
448 * Find temporary folder
449 * Finds the first $this->tempFN ('_temp_' usually) -folder in the internal array of filemounts, $this->mounts
450 *
451 * @return string Returns the path if found, otherwise nothing if error.
452 * @deprecated since TYPO3 6.0. No replacement
453 */
454 public function findTempFolder() {
455 // @todo: where and when to use this function?
456 GeneralUtility::logDeprecatedFunction();
457 if ($this->tempFN && is_array($this->mounts)) {
458 foreach ($this->mounts as $k => $val) {
459 $tDir = $val['path'] . $this->tempFN;
460 if (@is_dir($tDir)) {
461 return $tDir;
462 }
463 }
464 }
465 }
466
467 /*********************
468 *
469 * Cleaning functions
470 *
471 *********************/
472 /**
473 * Removes all dots, slashes and spaces after a path
474 *
475 * @param string $theDir Input string
476 * @return string Output string
477 * @deprecated since TYPO3 6.1, will be removed in two versions, use \TYPO3\CMS\Core\Utility\PathUtility::getCanonicalPath() instead
478 */
479 public function cleanDirectoryName($theDir) {
480 GeneralUtility::logDeprecatedFunction();
481 return PathUtility::getCanonicalPath($theDir);
482 }
483
484 /**
485 * Converts any double slashes (//) to a single slash (/)
486 *
487 * @param string Input value
488 * @return string Returns the converted string
489 * @deprecated since TYPO3 6.0, no replacement
490 */
491 public function rmDoubleSlash($string) {
492 GeneralUtility::logDeprecatedFunction();
493 return str_replace('//', '/', $string);
494 }
495
496 /**
497 * Returns a string which has a slash '/' appended if it doesn't already have that slash
498 *
499 * @param string Input string
500 * @return string Output string with a slash in the end (if not already there)
501 * @todo Deprecate, but still in use by is_webpath, used by DataHandler
502 * @deprecated but still in use in the Core. Don't use in your extensions!
503 */
504 public function slashPath($path) {
505 // @todo: should go into the LocalDriver in a protected way (not important to the outside world)
506 // @todo: should be done with rtrim($path, '/') . '/';
507 if (substr($path, -1) != '/') {
508 return $path . '/';
509 }
510 return $path;
511 }
512
513 /**
514 * Returns a string where any character not matching [.a-zA-Z0-9_-] is substituted by '_'
515 * Trailing dots are removed
516 *
517 * @param string $fileName Input string, typically the body of a filename
518 * @param string $charset Charset of the a filename (defaults to current charset; depending on context)
519 * @return string Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_' and trailing dots removed
520 * @todo Deprecate, but still in use by the core
521 * @deprecated but still in use in the Core. Don't use in your extensions!
522 */
523 public function cleanFileName($fileName, $charset = '') {
524 // Handle UTF-8 characters
525 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
526 // allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
527 $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . ']/u', '_', trim($fileName));
528 } else {
529 // Get conversion object or initialize if needed
530 if (!is_object($this->csConvObj)) {
531 if (TYPO3_MODE == 'FE') {
532 $this->csConvObj = $GLOBALS['TSFE']->csConvObj;
533 } elseif (is_object($GLOBALS['LANG'])) {
534 // BE assumed:
535 $this->csConvObj = $GLOBALS['LANG']->csConvObj;
536 } else {
537 // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
538 $this->csConvObj = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
539 }
540 }
541 // Define character set
542 if (!$charset) {
543 if (TYPO3_MODE == 'FE') {
544 $charset = $GLOBALS['TSFE']->renderCharset;
545 } else {
546 // Backend
547 $charset = 'utf-8';
548 }
549 }
550 // If a charset was found, convert filename
551 if ($charset) {
552 $fileName = $this->csConvObj->specCharsToASCII($charset, $fileName);
553 }
554 // Replace unwanted characters by underscores
555 $cleanFileName = preg_replace('/[' . self::UNSAFE_FILENAME_CHARACTER_EXPRESSION . '\\xC0-\\xFF]/', '_', trim($fileName));
556 }
557 // Strip trailing dots and return
558 return preg_replace('/\\.*$/', '', $cleanFileName);
559 }
560
561 }