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