Initial revision
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_basicfilefunc.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2003 Kasper Skårhøj (kasper@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 * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
31 *
32 * @author Kasper Skårhøj <kasper@typo3.com>
33 * @package TYPO3
34 * @subpackage t3lib
35 */
36 /**
37 * [CLASS/FUNCTION INDEX of SCRIPT]
38 *
39 *
40 *
41 * 79: class t3lib_basicFileFunctions
42 *
43 * SECTION: Checking functions
44 * 131: function init($mounts, $f_ext)
45 * 148: function getTotalFileInfo($wholePath)
46 * 168: function is_allowed($iconkey,$type)
47 * 193: function checkIfFullAccess($theDest)
48 * 207: function is_webpath($path)
49 * 227: function checkIfAllowed($ext, $theDest, $filename='')
50 * 237: function checkFileNameLen($fileName)
51 * 247: function is_directory($theDir)
52 * 264: function isPathValid($theFile)
53 * 279: function getUniqueName($theFile, $theDest, $dontCheckForUnique=0)
54 * 322: function checkPathAgainstMounts($thePath)
55 * 340: function blindPath($thePath)
56 * 356: function findTempFolder()
57 *
58 * SECTION: Cleaning functions
59 * 385: function cleanDirectoryName($theDir)
60 * 395: function rmDoubleSlash($string)
61 * 405: function slashPath($path)
62 * 418: function cleanFileName($fileName)
63 * 429: function formatSize($sizeInBytes)
64 *
65 * TOTAL FUNCTIONS: 18
66 * (This index is automatically created/updated by the extension "extdeveval")
67 *
68 */
69
70
71
72
73 /**
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
75 *
76 * @author Kasper Skårhøj <kasper@typo3.com>
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 = 20; // 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 = 30; // 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 start();
91
92
93
94 /**********************************
95 *
96 * Checking functions
97 *
98 **********************************/
99
100 /**
101 * Constructor
102 * This function should be called to initialise the internal arrays $this->mounts and $this->f_ext
103 *
104 * A typical example of the array $mounts is this:
105 * $mounts[xx][path] = (..a mounted path..)
106 * 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.
107 * $this->mounts is traversed in the function checkPathAgainstMounts($thePath), and it is checked that $thePath is actually below one of the mount-paths
108 * The mountpaths are with a trailing '/'. $thePath must be with a trailing '/' also!
109 * As you can see, $this->mounts is very critical! This is the array that decides where the user will be allowed to copy files!!
110 * Typically the global var $WEBMOUNTS would be passed along as $mounts
111 *
112 * A typical example of the array $f_ext is this:
113 * $f_ext['webspace']['allow']='';
114 * $f_ext['webspace']['deny']='php3,php';
115 * $f_ext['ftpspace']['allow']='*';
116 * $f_ext['ftpspace']['deny']='';
117 * 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.
118 * 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.
119 * You list extensions comma-separated. If the value is a '*' every extension is allowed
120 * The list is case-insensitive when used in this class (see init())
121 * Typically TYPO3_CONF_VARS['BE']['fileExtensions'] would be passed along as $f_ext.
122 *
123 * Example:
124 * $basicff->init($GLOBALS['FILEMOUNTS'],$TYPO3_CONF_VARS['BE']['fileExtensions']);
125 *
126 * @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)
127 * @param array Array with information about allowed and denied file extensions. Typically passed: $TYPO3_CONF_VARS['BE']['fileExtensions']
128 * @return void
129 * @see typo3/init.php, t3lib_userAuthGroup::returnFilemounts()
130 */
131 function init($mounts, $f_ext) {
132 $this->f_ext['webspace']['allow'] = t3lib_div::uniqueList(strtolower($f_ext['webspace']['allow']));
133 $this->f_ext['webspace']['deny'] = t3lib_div::uniqueList(strtolower($f_ext['webspace']['deny']));
134 $this->f_ext['ftpspace']['allow'] = t3lib_div::uniqueList(strtolower($f_ext['ftpspace']['allow']));
135 $this->f_ext['ftpspace']['deny'] = t3lib_div::uniqueList(strtolower($f_ext['ftpspace']['deny']));
136
137 $this->mounts = $mounts;
138 $this->webPath = t3lib_div::getIndpEnv('TYPO3_DOCUMENT_ROOT');
139 $this->isInit=1;
140 }
141
142 /**
143 * Returns an array with a whole lot of fileinformation.
144 *
145 * @param string Filepath to existing file. Should probably be absolute. Filefunctions are performed on this value.
146 * @return array Information about the file in the filepath
147 */
148 function getTotalFileInfo($wholePath) {
149 $theuser = getmyuid();
150 $info = t3lib_div::split_fileref($wholePath);
151 $info['tstamp']=@filectime($wholePath);
152 $info['size']=@filesize($wholePath);
153 $info['type']=@filetype($wholePath);
154 $info['owner']=@fileowner($wholePath);
155 $info['perms']=@fileperms($wholePath);
156 $info['writeable'] = ($info['perms']&2 || ($theuser==$info['owner'] && $info['perms']&128));
157 $info['readable'] = ($info['perms']&4 || ($theuser==$info['owner'] && $info['perms']&256));
158 return $info;
159 }
160
161 /**
162 * Checks if a $iconkey (fileextension) is allowed according to $this->f_ext.
163 *
164 * @param string The extension to check, eg. "php" or "html" etc.
165 * @param string Either "webspage" or "ftpspace" - points to a key in $this->f_ext
166 * @return boolean True if file extension is allowed.
167 */
168 function is_allowed($iconkey,$type) {
169 if (isset($this->f_ext[$type])) {
170 $ik = strtolower($iconkey);
171 if ($ik) {
172 // If the extension is found amongst the allowed types, we return true immediately
173 if ($this->f_ext[$type]['allow']=='*' || t3lib_div::inList($this->f_ext[$type]['allow'],$ik)) return true;
174 // If the extension is found amongst the denied types, we return false immediately
175 if ($this->f_ext[$type]['deny']=='*' || t3lib_div::inList($this->f_ext[$type]['deny'],$ik)) return false;
176 // If no match we return true
177 return true;
178 } else { // If no extension:
179 if ($this->f_ext[$type]['allow']=='*') return true;
180 if ($this->f_ext[$type]['deny']=='*') return false;
181 return true;
182 }
183 }
184 return false;
185 }
186
187 /**
188 * Returns true if you can operate of ANY file ('*') in the space $theDest is in ('webspace' / 'ftpspace')
189 *
190 * @param string Absolute path
191 * @return boolean
192 */
193 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']=='*') return true;
197 }
198 }
199
200 /**
201 * Checks if $this->webPath (should be TYPO3_DOCUMENT_ROOT) is in the first part of $path
202 * Returns true also if $this->init is not set or if $path is empty...
203 *
204 * @param string Absolute path to check
205 * @return boolean
206 */
207 function is_webpath($path) {
208 if ($this->isInit) {
209 $testPath = $this->slashPath($path);
210 $testPathWeb = $this->slashPath($this->webPath);
211 if ($testPathWeb && $testPath) {
212 return t3lib_div::isFirstPartOfStr($testPath,$testPathWeb);
213 }
214 }
215 return true; // Its more safe to return true (as the webpath is more restricted) if something went wrong...
216 }
217
218 /**
219 * If the filename is given, check it against the TYPO3_CONF_VARS[BE][fileDenyPattern] +
220 * Checks if the $ext fileextension is allowed in the path $theDest (this is based on whether $theDest is below the $this->webPath)
221 *
222 * @param string File extension, eg. "php" or "html"
223 * @param string Absolute path for which to test
224 * @param string Filename to check against TYPO3_CONF_VARS[BE][fileDenyPattern]
225 * @return boolean True if extension/filename is allowed
226 */
227 function checkIfAllowed($ext, $theDest, $filename='') {
228 return t3lib_div::verifyFilenameAgainstDenyPattern($filename) && $this->is_allowed($ext,($this->is_webpath($theDest)?'webspace':'ftpspace'));
229 }
230
231 /**
232 * Returns true if the input filename string is shorter than $this->maxInputNameLen.
233 *
234 * @param string Filename, eg "somefile.html"
235 * @return boolean
236 */
237 function checkFileNameLen($fileName) {
238 return strlen($fileName) <= $this->maxInputNameLen;
239 }
240
241 /**
242 * Cleans $theDir for slashes in the end of the string and returns the new path, if it exists on the server.
243 *
244 * @param string Directory path to check
245 * @return string Returns the cleaned up directory name if OK, otherwise false.
246 */
247 function is_directory($theDir) {
248 if ($this->isPathValid($theDir)) {
249 $theDir=$this->cleanDirectoryName($theDir);
250 if (@is_dir($theDir)) {
251 return $theDir;
252 }
253 }
254 return false;
255 }
256
257 /**
258 * Wrapper for t3lib_div::validPathStr()
259 *
260 * @param string Filepath to evaluate
261 * @return boolean True, if no '//', '..' or '\' is in the $theFile
262 * @see t3lib_div::validPathStr()
263 */
264 function isPathValid($theFile) {
265 return t3lib_div::validPathStr($theFile);
266 }
267
268 /**
269 * Returns the destination path/filename of a unique filename/foldername in that path.
270 * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
271 * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
272 *
273 * @param string The input filename to check
274 * @param string The directory for which to return a unique filename for $theFile. $theDest MUST be a valid directory. Should be absolute.
275 * @param boolean If set the filename is returned with the path prepended without checking whether it already existed!
276 * @return string The destination absolute filepath (not just the name!) of a unique filename/foldername in that path.
277 * @see t3lib_TCEmain::checkValue()
278 */
279 function getUniqueName($theFile, $theDest, $dontCheckForUnique=0) {
280 $theDest = $this->is_directory($theDest); // $theDest is cleaned up
281 $origFileInfo = t3lib_div::split_fileref($theFile); // Fetches info about path, name, extention of $theFile
282 if ($theDest) {
283 if ($this->getUniqueNamePrefix) { // Adds prefix
284 $origFileInfo['file'] = $this->getUniqueNamePrefix.$origFileInfo['file'];
285 $origFileInfo['filebody'] = $this->getUniqueNamePrefix.$origFileInfo['filebody'];
286 }
287
288 // Check if the file exists and if not - return the filename...
289 $fileInfo = $origFileInfo;
290 $theDestFile = $theDest.'/'.$fileInfo['file']; // The destinations file
291 if (!@file_exists($theDestFile) || $dontCheckForUnique) { // If the file does NOT exist we return this filename
292 return $theDestFile;
293 }
294
295 // 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...
296 $theTempFileBody = ereg_replace('_[0-9][0-9]$','',$origFileInfo['filebody']); // This removes _xx if appended to the file
297 $theOrigExt = $origFileInfo['realFileext'] ? '.'.$origFileInfo['realFileext'] : '';
298
299 for ($a=1; $a<=($this->maxNumber+1); $a++) {
300 if ($a<=$this->maxNumber) { // First we try to append numbers
301 $insert = '_'.sprintf('%02d', $a);
302 } else { // .. then we try unique-strings...
303 $insert = '_'.substr(md5(uniqId('')),0,$this->uniquePrecision);
304 }
305 $theTestFile = $theTempFileBody.$insert.$theOrigExt;
306 $theDestFile = $theDest.'/'.$theTestFile; // The destinations file
307 if (!@file_exists($theDestFile)) { // If the file does NOT exist we return this filename
308 return $theDestFile;
309 }
310 }
311 }
312 }
313
314 /**
315 * Checks if $thePath is a path under one of the paths in $this->mounts
316 * See comment in the header of this class.
317 *
318 * @param string $thePath MUST HAVE a trailing '/' in order to match correctly with the mounts
319 * @return string The key to the first mount found, otherwise nothing is returned.
320 * @see init()
321 */
322 function checkPathAgainstMounts($thePath) {
323 if ($thePath && $this->isPathValid($thePath) && is_array($this->mounts)) {
324 reset ($this->mounts);
325 while(list($k,$val)=each($this->mounts)) {
326 if (t3lib_div::isFirstPartOfStr($thePath,$val['path'])) {
327 return $k;
328 }
329 }
330 }
331 }
332
333 /**
334 * Removes filemount part of a path, thus blinding the position.
335 * Takes a path, $thePath, and removes the part of the path which equals the filemount.
336 *
337 * @param string $thePath is a path which MUST be found within one of the internally set filemounts, $this->mounts
338 * @return string The processed input path
339 */
340 function blindPath($thePath) {
341 $k=$this->checkPathAgainstMounts($thePath);
342 if ($k) {
343 $name='';
344 $name.='['.$this->mounts[$k]['name'].']: ';
345 $name.=substr($thePath,strlen($this->mounts[$k]['path']));
346 return $name;
347 }
348 }
349
350 /**
351 * Find temporary folder
352 * Finds the first $this->tempFN ('_temp_' usually) -folder in the internal array of filemounts, $this->mounts
353 *
354 * @return string Returns the path if found, otherwise nothing if error.
355 */
356 function findTempFolder() {
357 if ($this->tempFN && is_array($this->mounts)) {
358 reset ($this->mounts);
359 while(list($k,$val)=each($this->mounts)) {
360 $tDir = $val['path'].$this->tempFN;
361 if (@is_dir($tDir)) {
362 return $tDir;
363 }
364 }
365 }
366 }
367
368
369
370
371
372
373 /*********************
374 *
375 * Cleaning functions
376 *
377 *********************/
378
379 /**
380 * Removes all dots, slashes and spaces after a path...
381 *
382 * @param string Input string
383 * @return string Output string
384 */
385 function cleanDirectoryName($theDir) {
386 return ereg_replace('[\/\. ]*$','',$this->rmDoubleSlash($theDir));
387 }
388
389 /**
390 * Converts any double slashes (//) to a single slash (/)
391 *
392 * @param string Input value
393 * @return string Returns the converted string
394 */
395 function rmDoubleSlash($string) {
396 return str_replace('//','/',$string);
397 }
398
399 /**
400 * Returns a string which has a slash '/' appended if it doesn't already have that slash
401 *
402 * @param string Input string
403 * @return string Output string with a slash in the end (if not already there)
404 */
405 function slashPath($path) {
406 if (substr($path,-1)!='/') {
407 return $path.'/';
408 }
409 return $path;
410 }
411
412 /**
413 * Returns a string where any character not matching [.a-zA-Z0-9_-] is substituted by '_'
414 *
415 * @param string Input string, typically the body of a filename
416 * @return string Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_'
417 */
418 function cleanFileName($fileName) {
419 $theNewName = ereg_replace('[^\.[:alnum:]_-]','_',trim($fileName));
420 return $theNewName;
421 }
422
423 /**
424 * Formats an integer, $sizeInBytes, to Mb or Kb or just bytes
425 *
426 * @param integer Bytes to be formated
427 * @return string Formatted with M,K or &nbsp;&nbsp; appended.
428 */
429 function formatSize($sizeInBytes) {
430 if ($sizeInBytes>900) {
431 if ($sizeInBytes>900000) { // MB
432 $val = $sizeInBytes/(1024*1024);
433 return number_format($val, (($val<20)?1:0), '.', '').' M';
434 } else { // KB
435 $val = $sizeInBytes/(1024);
436 return number_format($val, (($val<20)?1:0), '.', '').' K';
437 }
438 } else { // Bytes
439 return $sizeInBytes.'&nbsp;&nbsp;';
440 }
441 }
442 }
443
444
445
446 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_basicfilefunc.php']) {
447 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_basicfilefunc.php']);
448 }
449 ?>