2 /***************************************************************
5 * (c) 1999-2008 Kasper Skaarhoj (kasperYYYY@typo3.com)
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.
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.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * Contains class with basic file management functions
31 * Revised for TYPO3 3.6 July/2003 by Kasper Skaarhoj
33 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
36 * [CLASS/FUNCTION INDEX of SCRIPT]
40 * 81: class t3lib_basicFileFunctions
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()
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)
66 * (This index is automatically created/updated by the extension "extdeveval")
70 require_once(PATH_t3lib
.'class.t3lib_cs.php');
74 * 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
76 * @author Kasper Skaarhoj <kasperYYYY@typo3.com>
79 * @see t3lib_basicFileFunctions::init()
81 class t3lib_basicFileFunctions
{
82 var $getUniqueNamePrefix = ''; // Prefix which will be prepended the file when using the getUniqueName-function
83 var $maxNumber = 99; // This number decides the highest allowed appended number used on a filename before we use naming with unique strings
84 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.
85 var $maxInputNameLen = 60; // This is the maximum length of names treated by cleanFileName()
86 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)
89 var $f_ext = Array(); // See comment in header
90 var $mounts = Array(); // See comment in header
91 var $webPath =''; // Set to DOCUMENT_ROOT.
92 var $isInit = 0; // Set to true after init()/start();
96 /**********************************
100 **********************************/
104 * This function should be called to initialise the internal arrays $this->mounts and $this->f_ext
106 * A typical example of the array $mounts is this:
107 * $mounts[xx][path] = (..a mounted path..)
108 * 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.
109 * $this->mounts is traversed in the function checkPathAgainstMounts($thePath), and it is checked that $thePath is actually below one of the mount-paths
110 * The mountpaths are with a trailing '/'. $thePath must be with a trailing '/' also!
111 * As you can see, $this->mounts is very critical! This is the array that decides where the user will be allowed to copy files!!
112 * Typically the global var $WEBMOUNTS would be passed along as $mounts
114 * A typical example of the array $f_ext is this:
115 * $f_ext['webspace']['allow']='';
116 * $f_ext['webspace']['deny']= PHP_EXTENSIONS_DEFAULT;
117 * $f_ext['ftpspace']['allow']='*';
118 * $f_ext['ftpspace']['deny']='';
119 * 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.
120 * 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.
121 * You list extensions comma-separated. If the value is a '*' every extension is allowed
122 * The list is case-insensitive when used in this class (see init())
123 * Typically TYPO3_CONF_VARS['BE']['fileExtensions'] would be passed along as $f_ext.
126 * $basicff->init($GLOBALS['FILEMOUNTS'],$TYPO3_CONF_VARS['BE']['fileExtensions']);
128 * @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)
129 * @param array Array with information about allowed and denied file extensions. Typically passed: $TYPO3_CONF_VARS['BE']['fileExtensions']
131 * @see typo3/init.php, t3lib_userAuthGroup::returnFilemounts()
133 function init($mounts, $f_ext) {
134 $this->f_ext
['webspace']['allow'] = t3lib_div
::uniqueList(strtolower($f_ext['webspace']['allow']));
135 $this->f_ext
['webspace']['deny'] = t3lib_div
::uniqueList(strtolower($f_ext['webspace']['deny']));
136 $this->f_ext
['ftpspace']['allow'] = t3lib_div
::uniqueList(strtolower($f_ext['ftpspace']['allow']));
137 $this->f_ext
['ftpspace']['deny'] = t3lib_div
::uniqueList(strtolower($f_ext['ftpspace']['deny']));
139 $this->mounts
= $mounts;
140 $this->webPath
= t3lib_div
::getIndpEnv('TYPO3_DOCUMENT_ROOT');
143 $this->maxInputNameLen
= $GLOBALS['TYPO3_CONF_VARS']['SYS']['maxFileNameLength'] ?
$GLOBALS['TYPO3_CONF_VARS']['SYS']['maxFileNameLength'] : $this->maxInputNameLen
;
147 * Returns an array with a whole lot of fileinformation.
149 * @param string Filepath to existing file. Should probably be absolute. Filefunctions are performed on this value.
150 * @return array Information about the file in the filepath
152 function getTotalFileInfo($wholePath) {
153 $theuser = getmyuid();
154 $info = t3lib_div
::split_fileref($wholePath);
155 $info['tstamp'] = @filemtime
($wholePath);
156 $info['size'] = @filesize
($wholePath);
157 $info['type'] = @filetype
($wholePath);
158 $info['owner'] = @fileowner
($wholePath);
159 $info['perms'] = @fileperms
($wholePath);
160 $info['writable'] = !@is_writable
($wholePath);
161 $info['readable'] = !@is_readable
($wholePath);
166 * Checks if a $iconkey (fileextension) is allowed according to $this->f_ext.
168 * @param string The extension to check, eg. "php" or "html" etc.
169 * @param string Either "webspage" or "ftpspace" - points to a key in $this->f_ext
170 * @return boolean True if file extension is allowed.
172 function is_allowed($iconkey,$type) {
173 if (isset($this->f_ext
[$type])) {
174 $ik = strtolower($iconkey);
176 // If the extension is found amongst the allowed types, we return true immediately
177 if ($this->f_ext
[$type]['allow']=='*' || t3lib_div
::inList($this->f_ext
[$type]['allow'],$ik)) return true
;
178 // If the extension is found amongst the denied types, we return false immediately
179 if ($this->f_ext
[$type]['deny']=='*' || t3lib_div
::inList($this->f_ext
[$type]['deny'],$ik)) return false
;
180 // If no match we return true
182 } else { // If no extension:
183 if ($this->f_ext
[$type]['allow']=='*') return true
;
184 if ($this->f_ext
[$type]['deny']=='*') return false
;
192 * Returns true if you can operate of ANY file ('*') in the space $theDest is in ('webspace' / 'ftpspace')
194 * @param string Absolute path
197 function checkIfFullAccess($theDest) {
198 $type = $this->is_webpath($theDest)?
'webspace':'ftpspace';
199 if (isset($this->f_ext
[$type])) {
200 if ((string)$this->f_ext
[$type]['deny']=='' ||
$this->f_ext
[$type]['allow']=='*') return true
;
205 * Checks if $this->webPath (should be TYPO3_DOCUMENT_ROOT) is in the first part of $path
206 * Returns true also if $this->init is not set or if $path is empty...
208 * @param string Absolute path to check
211 function is_webpath($path) {
213 $testPath = $this->slashPath($path);
214 $testPathWeb = $this->slashPath($this->webPath
);
215 if ($testPathWeb && $testPath) {
216 return t3lib_div
::isFirstPartOfStr($testPath,$testPathWeb);
219 return true
; // Its more safe to return true (as the webpath is more restricted) if something went wrong...
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)
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 boolean True if extension/filename is allowed
231 function checkIfAllowed($ext, $theDest, $filename='') {
232 return t3lib_div
::verifyFilenameAgainstDenyPattern($filename) && $this->is_allowed($ext,($this->is_webpath($theDest)?
'webspace':'ftpspace'));
236 * Returns true if the input filename string is shorter than $this->maxInputNameLen.
238 * @param string Filename, eg "somefile.html"
241 function checkFileNameLen($fileName) {
242 return strlen($fileName) <= $this->maxInputNameLen
;
246 * Cleans $theDir for slashes in the end of the string and returns the new path, if it exists on the server.
248 * @param string Directory path to check
249 * @return string Returns the cleaned up directory name if OK, otherwise false.
251 function is_directory($theDir) {
252 if ($this->isPathValid($theDir)) {
253 $theDir=$this->cleanDirectoryName($theDir);
254 if (@is_dir
($theDir)) {
262 * Wrapper for t3lib_div::validPathStr()
264 * @param string Filepath to evaluate
265 * @return boolean True, if no '//', '..' or '\' is in the $theFile
266 * @see t3lib_div::validPathStr()
268 function isPathValid($theFile) {
269 return t3lib_div
::validPathStr($theFile);
273 * Returns the destination path/filename of a unique filename/foldername in that path.
274 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
275 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
277 * @param string The input filename to check
278 * @param string The directory for which to return a unique filename for $theFile. $theDest MUST be a valid directory. Should be absolute.
279 * @param boolean If set the filename is returned with the path prepended without checking whether it already existed!
280 * @return string The destination absolute filepath (not just the name!) of a unique filename/foldername in that path.
281 * @see t3lib_TCEmain::checkValue()
283 function getUniqueName($theFile, $theDest, $dontCheckForUnique=0) {
284 $theDest = $this->is_directory($theDest); // $theDest is cleaned up
285 $origFileInfo = t3lib_div
::split_fileref($theFile); // Fetches info about path, name, extention of $theFile
287 if ($this->getUniqueNamePrefix
) { // Adds prefix
288 $origFileInfo['file'] = $this->getUniqueNamePrefix
.$origFileInfo['file'];
289 $origFileInfo['filebody'] = $this->getUniqueNamePrefix
.$origFileInfo['filebody'];
292 // Check if the file exists and if not - return the filename...
293 $fileInfo = $origFileInfo;
294 $theDestFile = $theDest.'/'.$fileInfo['file']; // The destinations file
295 if (!file_exists($theDestFile) ||
$dontCheckForUnique) { // If the file does NOT exist we return this filename
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']); // This removes _xx if appended to the file
301 $theOrigExt = $origFileInfo['realFileext'] ?
'.'.$origFileInfo['realFileext'] : '';
303 for ($a=1; $a<=($this->maxNumber+
1); $a++
) {
304 if ($a<=$this->maxNumber
) { // First we try to append numbers
305 $insert = '_'.sprintf('%02d', $a);
306 } else { // .. then we try unique-strings...
307 $insert = '_'.substr(md5(uniqId('')),0,$this->uniquePrecision
);
309 $theTestFile = $theTempFileBody.$insert.$theOrigExt;
310 $theDestFile = $theDest.'/'.$theTestFile; // The destinations file
311 if (!file_exists($theDestFile)) { // If the file does NOT exist we return this filename
319 * Checks if $thePath is a path under one of the paths in $this->mounts
320 * See comment in the header of this class.
322 * @param string $thePath MUST HAVE a trailing '/' in order to match correctly with the mounts
323 * @return string The key to the first mount found, otherwise nothing is returned.
326 function checkPathAgainstMounts($thePath) {
327 if ($thePath && $this->isPathValid($thePath) && is_array($this->mounts
)) {
328 reset ($this->mounts
);
329 while(list($k,$val)=each($this->mounts
)) {
330 if (t3lib_div
::isFirstPartOfStr($thePath,$val['path'])) {
338 * Find first web folder (relative to PATH_site.'fileadmin') in filemounts array
340 * @return string The key to the first mount inside PATH_site."fileadmin" found, otherwise nothing is returned.
342 function findFirstWebFolder() {
343 global $TYPO3_CONF_VARS;
345 if (is_array($this->mounts
)) {
346 reset ($this->mounts
);
347 while(list($k,$val)=each($this->mounts
)) {
348 if (t3lib_div
::isFirstPartOfStr($val['path'], PATH_site
.$TYPO3_CONF_VARS['BE']['fileadminDir'])) {
356 * Removes filemount part of a path, thus blinding the position.
357 * Takes a path, $thePath, and removes the part of the path which equals the filemount.
359 * @param string $thePath is a path which MUST be found within one of the internally set filemounts, $this->mounts
360 * @return string The processed input path
362 function blindPath($thePath) {
363 $k=$this->checkPathAgainstMounts($thePath);
366 $name.='['.$this->mounts
[$k]['name'].']: ';
367 $name.=substr($thePath,strlen($this->mounts
[$k]['path']));
373 * Find temporary folder
374 * Finds the first $this->tempFN ('_temp_' usually) -folder in the internal array of filemounts, $this->mounts
376 * @return string Returns the path if found, otherwise nothing if error.
378 function findTempFolder() {
379 if ($this->tempFN
&& is_array($this->mounts
)) {
380 reset ($this->mounts
);
381 while(list($k,$val)=each($this->mounts
)) {
382 $tDir = $val['path'].$this->tempFN
;
383 if (@is_dir
($tDir)) {
400 /*********************
404 *********************/
407 * Removes all dots, slashes and spaces after a path...
409 * @param string Input string
410 * @return string Output string
412 function cleanDirectoryName($theDir) {
413 return preg_replace('/[\/\. ]*$/','',$this->rmDoubleSlash($theDir));
417 * Converts any double slashes (//) to a single slash (/)
419 * @param string Input value
420 * @return string Returns the converted string
422 function rmDoubleSlash($string) {
423 return str_replace('//','/',$string);
427 * Returns a string which has a slash '/' appended if it doesn't already have that slash
429 * @param string Input string
430 * @return string Output string with a slash in the end (if not already there)
432 function slashPath($path) {
433 if (substr($path,-1)!='/') {
440 * Returns a string where any character not matching [.a-zA-Z0-9_-] is substituted by '_'
442 * @param string Input string, typically the body of a filename
443 * @param string Charset of the a filename (defaults to current charset; depending on context)
444 * @return string Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_'
446 function cleanFileName($fileName,$charset='') {
447 // handle UTF-8 characters
448 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'] == 'utf-8' && $GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
449 // allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
450 return preg_replace('/[\x00-\x2C\/\x3A-\x3F\x5B-\x60\x7B-\xBF]/u','_',trim($fileName));
453 if (!is_object($this->csConvObj
)) {
454 if (TYPO3_MODE
=='FE') {
455 $this->csConvObj
= &$GLOBALS['TSFE']->csConvObj
;
456 } elseif (is_object($GLOBALS['LANG'])) { // BE assumed:
457 $this->csConvObj
= &$GLOBALS['LANG']->csConvObj
;
458 } else { // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
459 $this->csConvObj
= &t3lib_div
::makeInstance('t3lib_cs');
464 if (TYPO3_MODE
=='FE') {
465 $charset = $GLOBALS['TSFE']->renderCharset
;
466 } elseif (is_object($GLOBALS['LANG'])) { // BE assumed:
467 $charset = $GLOBALS['LANG']->charSet
;
468 } else { // best guess
469 $charset = $GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'];
474 $fileName = $this->csConvObj
->specCharsToASCII($charset,$fileName);
477 $fileName = preg_replace('/[^.[:alnum:]_-]/','_',trim($fileName));
478 return preg_replace('/\.*$/','',$fileName);
482 * Formats an integer, $sizeInBytes, to Mb or Kb or just bytes
484 * @param integer Bytes to be formated
485 * @return string Formatted with M,K or appended.
487 function formatSize($sizeInBytes) {
488 if ($sizeInBytes>900) {
489 if ($sizeInBytes>900000) { // MB
490 $val = $sizeInBytes/(1024*1024);
491 return number_format($val, (($val<20)?
1:0), '.', '').' M';
493 $val = $sizeInBytes/(1024);
494 return number_format($val, (($val<20)?
1:0), '.', '').' K';
497 return $sizeInBytes.' ';
504 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/class.t3lib_basicfilefunc.php']) {
505 include_once($TYPO3_CONF_VARS[TYPO3_MODE
]['XCLASS']['t3lib/class.t3lib_basicfilefunc.php']);