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