TYPO3 API  SVNRelease
class.t3lib_basicfilefunc.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003  *  Copyright notice
00004  *
00005  *  (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
00006  *  All rights reserved
00007  *
00008  *  This script is part of the TYPO3 project. The TYPO3 project is
00009  *  free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  The GNU General Public License can be found at
00015  *  http://www.gnu.org/copyleft/gpl.html.
00016  *  A copy is found in the textfile GPL.txt and important notices to the license
00017  *  from the author is found in LICENSE.txt distributed with these scripts.
00018  *
00019  *
00020  *  This script is distributed in the hope that it will be useful,
00021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023  *  GNU General Public License for more details.
00024  *
00025  *  This copyright notice MUST APPEAR in all copies of the script!
00026  ***************************************************************/
00027 /**
00028  * Contains class with basic file management functions
00029  *
00030  * $Id: class.t3lib_basicfilefunc.php 10493 2011-02-19 10:46:02Z lolli $
00031  * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
00032  *
00033  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00034  */
00035 /**
00036  * [CLASS/FUNCTION INDEX of SCRIPT]
00037  *
00038  *
00039  *
00040  *   81: class t3lib_basicFileFunctions
00041  *
00042  *            SECTION: Checking functions
00043  *  133:     function init($mounts, $f_ext)
00044  *  152:     function getTotalFileInfo($wholePath)
00045  *  172:     function is_allowed($iconkey,$type)
00046  *  197:     function checkIfFullAccess($theDest)
00047  *  211:     function is_webpath($path)
00048  *  231:     function checkIfAllowed($ext, $theDest, $filename='')
00049  *  241:     function checkFileNameLen($fileName)
00050  *  251:     function is_directory($theDir)
00051  *  268:     function isPathValid($theFile)
00052  *  283:     function getUniqueName($theFile, $theDest, $dontCheckForUnique=0)
00053  *  326:     function checkPathAgainstMounts($thePath)
00054  *  342:     function findFirstWebFolder()
00055  *  362:     function blindPath($thePath)
00056  *  378:     function findTempFolder()
00057  *
00058  *            SECTION: Cleaning functions
00059  *  412:     function cleanDirectoryName($theDir)
00060  *  422:     function rmDoubleSlash($string)
00061  *  432:     function slashPath($path)
00062  *  446:     function cleanFileName($fileName,$charset='')
00063  *  480:     function formatSize($sizeInBytes)
00064  *
00065  * TOTAL FUNCTIONS: 19
00066  * (This index is automatically created/updated by the extension "extdeveval")
00067  *
00068  */
00069 
00070 
00071 /**
00072  * 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
00073  *
00074  * @author  Kasper Skårhøj <kasperYYYY@typo3.com>
00075  * @package TYPO3
00076  * @subpackage t3lib
00077  * @see t3lib_basicFileFunctions::init()
00078  */
00079 class t3lib_basicFileFunctions {
00080     var $getUniqueNamePrefix = ''; // Prefix which will be prepended the file when using the getUniqueName-function
00081     var $maxNumber = 99; // This number decides the highest allowed appended number used on a filename before we use naming with unique strings
00082     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.
00083     var $maxInputNameLen = 60; // This is the maximum length of names treated by cleanFileName()
00084     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)
00085 
00086     // internal
00087     var $f_ext = Array(); // See comment in header
00088     var $mounts = Array(); // See comment in header
00089     var $webPath = ''; // Set to DOCUMENT_ROOT.
00090     var $isInit = 0; // Set to true after init()/start();
00091 
00092 
00093     /**********************************
00094      *
00095      * Checking functions
00096      *
00097      **********************************/
00098 
00099     /**
00100      * Constructor
00101      * This function should be called to initialise the internal arrays $this->mounts and $this->f_ext
00102      *
00103      *  A typical example of the array $mounts is this:
00104      *       $mounts[xx][path] = (..a mounted path..)
00105      *   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.
00106      *   $this->mounts is traversed in the function checkPathAgainstMounts($thePath), and it is checked that $thePath is actually below one of the mount-paths
00107      *   The mountpaths are with a trailing '/'. $thePath must be with a trailing '/' also!
00108      *   As you can see, $this->mounts is very critical! This is the array that decides where the user will be allowed to copy files!!
00109      *  Typically the global var $WEBMOUNTS would be passed along as $mounts
00110      *
00111      *   A typical example of the array $f_ext is this:
00112      *       $f_ext['webspace']['allow']='';
00113      *       $f_ext['webspace']['deny']= PHP_EXTENSIONS_DEFAULT;
00114      *       $f_ext['ftpspace']['allow']='*';
00115      *       $f_ext['ftpspace']['deny']='';
00116      *   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.
00117      *   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.
00118      *   You list extensions comma-separated. If the value is a '*' every extension is allowed
00119      *   The list is case-insensitive when used in this class (see init())
00120      *  Typically TYPO3_CONF_VARS['BE']['fileExtensions'] would be passed along as $f_ext.
00121      *
00122      *  Example:
00123      *   $basicff->init($GLOBALS['FILEMOUNTS'],$TYPO3_CONF_VARS['BE']['fileExtensions']);
00124      *
00125      * @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)
00126      * @param   array       Array with information about allowed and denied file extensions. Typically passed: $TYPO3_CONF_VARS['BE']['fileExtensions']
00127      * @return  void
00128      * @see typo3/init.php, t3lib_userAuthGroup::returnFilemounts()
00129      */
00130     function init($mounts, $f_ext) {
00131         $this->f_ext['webspace']['allow'] = t3lib_div::uniqueList(strtolower($f_ext['webspace']['allow']));
00132         $this->f_ext['webspace']['deny'] = t3lib_div::uniqueList(strtolower($f_ext['webspace']['deny']));
00133         $this->f_ext['ftpspace']['allow'] = t3lib_div::uniqueList(strtolower($f_ext['ftpspace']['allow']));
00134         $this->f_ext['ftpspace']['deny'] = t3lib_div::uniqueList(strtolower($f_ext['ftpspace']['deny']));
00135 
00136         $this->mounts = $mounts;
00137         $this->webPath = t3lib_div::getIndpEnv('TYPO3_DOCUMENT_ROOT');
00138         $this->isInit = 1;
00139 
00140         $this->maxInputNameLen = $GLOBALS['TYPO3_CONF_VARS']['SYS']['maxFileNameLength'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['maxFileNameLength'] : $this->maxInputNameLen;
00141     }
00142 
00143     /**
00144      * Returns an array with a whole lot of fileinformation.
00145      * Information includes:
00146      * - path           : path part of give file
00147      * - file           : filename
00148      * - filebody       : filename without extension
00149      * - fileext        : lowercase extension
00150      * - realFileext    : extension
00151      * - tstamp         : timestamp of modification
00152      * - size           : file size
00153      * - type           : file type (block/char/dir/fifo/file/link)
00154      * - owner          : user ID of owner of file
00155      * - perms          : numerical representation of file permissions
00156      * - writable       : is file writeable by web user (FALSE = yes; TRUE = no) *)
00157      * - readable       : is file readable by web user (FALSE = yes; TRUE = no) *)
00158      *
00159      * *) logic is reversed because of handling by functions in class.file_list.inc
00160      *
00161      * @param   string      Filepath to existing file. Should probably be absolute. Filefunctions are performed on this value.
00162      * @return  array       Information about the file in the filepath
00163      */
00164     function getTotalFileInfo($wholePath) {
00165         $theuser = getmyuid();
00166         $info = t3lib_div::split_fileref($wholePath);
00167         $info['tstamp'] = @filemtime($wholePath);
00168         $info['size'] = @filesize($wholePath);
00169         $info['type'] = @filetype($wholePath);
00170         $info['owner'] = @fileowner($wholePath);
00171         $info['perms'] = @fileperms($wholePath);
00172         $info['writable'] = !@is_writable($wholePath);
00173         $info['readable'] = !@is_readable($wholePath);
00174         return $info;
00175     }
00176 
00177     /**
00178      * Checks if a $iconkey (fileextension) is allowed according to $this->f_ext.
00179      *
00180      * @param   string      The extension to check, eg. "php" or "html" etc.
00181      * @param   string      Either "webspage" or "ftpspace" - points to a key in $this->f_ext
00182      * @return  boolean     True if file extension is allowed.
00183      */
00184     function is_allowed($iconkey, $type) {
00185         if (isset($this->f_ext[$type])) {
00186             $ik = strtolower($iconkey);
00187             if ($ik) {
00188                     // If the extension is found amongst the allowed types, we return true immediately
00189                 if ($this->f_ext[$type]['allow'] == '*' || t3lib_div::inList($this->f_ext[$type]['allow'], $ik)) {
00190                     return TRUE;
00191                 }
00192                     // If the extension is found amongst the denied types, we return false immediately
00193                 if ($this->f_ext[$type]['deny'] == '*' || t3lib_div::inList($this->f_ext[$type]['deny'], $ik)) {
00194                     return FALSE;
00195                 }
00196                     // If no match we return true
00197                 return TRUE;
00198             } else { // If no extension:
00199                 if ($this->f_ext[$type]['allow'] == '*') {
00200                     return TRUE;
00201                 }
00202                 if ($this->f_ext[$type]['deny'] == '*') {
00203                     return FALSE;
00204                 }
00205                 return TRUE;
00206             }
00207         }
00208         return FALSE;
00209     }
00210 
00211     /**
00212      * Returns true if you can operate of ANY file ('*') in the space $theDest is in ('webspace' / 'ftpspace')
00213      *
00214      * @param   string      Absolute path
00215      * @return  boolean
00216      */
00217     function checkIfFullAccess($theDest) {
00218         $type = $this->is_webpath($theDest) ? 'webspace' : 'ftpspace';
00219         if (isset($this->f_ext[$type])) {
00220             if ((string) $this->f_ext[$type]['deny'] == '' || $this->f_ext[$type]['allow'] == '*') {
00221                 return TRUE;
00222             }
00223         }
00224     }
00225 
00226     /**
00227      * Checks if $this->webPath (should be TYPO3_DOCUMENT_ROOT) is in the first part of $path
00228      * Returns true also if $this->init is not set or if $path is empty...
00229      *
00230      * @param   string      Absolute path to check
00231      * @return  boolean
00232      */
00233     function is_webpath($path) {
00234         if ($this->isInit) {
00235             $testPath = $this->slashPath($path);
00236             $testPathWeb = $this->slashPath($this->webPath);
00237             if ($testPathWeb && $testPath) {
00238                 return t3lib_div::isFirstPartOfStr($testPath, $testPathWeb);
00239             }
00240         }
00241         return TRUE; // Its more safe to return true (as the webpath is more restricted) if something went wrong...
00242     }
00243 
00244     /**
00245      * If the filename is given, check it against the TYPO3_CONF_VARS[BE][fileDenyPattern] +
00246      * Checks if the $ext fileextension is allowed in the path $theDest (this is based on whether $theDest is below the $this->webPath)
00247      *
00248      * @param   string      File extension, eg. "php" or "html"
00249      * @param   string      Absolute path for which to test
00250      * @param   string      Filename to check against TYPO3_CONF_VARS[BE][fileDenyPattern]
00251      * @return  boolean     True if extension/filename is allowed
00252      */
00253     function checkIfAllowed($ext, $theDest, $filename = '') {
00254         return t3lib_div::verifyFilenameAgainstDenyPattern($filename) && $this->is_allowed($ext, ($this->is_webpath($theDest) ? 'webspace' : 'ftpspace'));
00255     }
00256 
00257     /**
00258      * Returns true if the input filename string is shorter than $this->maxInputNameLen.
00259      *
00260      * @param   string      Filename, eg "somefile.html"
00261      * @return  boolean
00262      */
00263     function checkFileNameLen($fileName) {
00264         return strlen($fileName) <= $this->maxInputNameLen;
00265     }
00266 
00267     /**
00268      * Cleans $theDir for slashes in the end of the string and returns the new path, if it exists on the server.
00269      *
00270      * @param   string      Directory path to check
00271      * @return  string      Returns the cleaned up directory name if OK, otherwise false.
00272      */
00273     function is_directory($theDir) {
00274         if ($this->isPathValid($theDir)) {
00275             $theDir = $this->cleanDirectoryName($theDir);
00276             if (@is_dir($theDir)) {
00277                 return $theDir;
00278             }
00279         }
00280         return FALSE;
00281     }
00282 
00283     /**
00284      * Wrapper for t3lib_div::validPathStr()
00285      *
00286      * @param   string      Filepath to evaluate
00287      * @return  boolean     True, if no '//', '..' or '\' is in the $theFile
00288      * @see t3lib_div::validPathStr()
00289      */
00290     function isPathValid($theFile) {
00291         return t3lib_div::validPathStr($theFile);
00292     }
00293 
00294     /**
00295      * Returns the destination path/filename of a unique filename/foldername in that path.
00296      * If $theFile exists in $theDest (directory) the file have numbers appended up to $this->maxNumber. Hereafter a unique string will be appended.
00297      * This function is used by fx. TCEmain when files are attached to records and needs to be uniquely named in the uploads/* folders
00298      *
00299      * @param   string      The input filename to check
00300      * @param   string      The directory for which to return a unique filename for $theFile. $theDest MUST be a valid directory. Should be absolute.
00301      * @param   boolean     If set the filename is returned with the path prepended without checking whether it already existed!
00302      * @return  string      The destination absolute filepath (not just the name!) of a unique filename/foldername in that path.
00303      * @see t3lib_TCEmain::checkValue()
00304      */
00305     function getUniqueName($theFile, $theDest, $dontCheckForUnique = 0) {
00306         $theDest = $this->is_directory($theDest); // $theDest is cleaned up
00307         $origFileInfo = t3lib_div::split_fileref($theFile); // Fetches info about path, name, extention of $theFile
00308         if ($theDest) {
00309             if ($this->getUniqueNamePrefix) { // Adds prefix
00310                 $origFileInfo['file'] = $this->getUniqueNamePrefix . $origFileInfo['file'];
00311                 $origFileInfo['filebody'] = $this->getUniqueNamePrefix . $origFileInfo['filebody'];
00312             }
00313 
00314                 // Check if the file exists and if not - return the filename...
00315             $fileInfo = $origFileInfo;
00316             $theDestFile = $theDest . '/' . $fileInfo['file']; // The destinations file
00317             if (!file_exists($theDestFile) || $dontCheckForUnique) { // If the file does NOT exist we return this filename
00318                 return $theDestFile;
00319             }
00320 
00321                 // 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...
00322             $theTempFileBody = preg_replace('/_[0-9][0-9]$/', '', $origFileInfo['filebody']); // This removes _xx if appended to the file
00323             $theOrigExt = $origFileInfo['realFileext'] ? '.' . $origFileInfo['realFileext'] : '';
00324 
00325             for ($a = 1; $a <= ($this->maxNumber + 1); $a++) {
00326                 if ($a <= $this->maxNumber) { // First we try to append numbers
00327                     $insert = '_' . sprintf('%02d', $a);
00328                 } else { // .. then we try unique-strings...
00329                     $insert = '_' . substr(md5(uniqId('')), 0, $this->uniquePrecision);
00330                 }
00331                 $theTestFile = $theTempFileBody . $insert . $theOrigExt;
00332                 $theDestFile = $theDest . '/' . $theTestFile; // The destinations file
00333                 if (!file_exists($theDestFile)) { // If the file does NOT exist we return this filename
00334                     return $theDestFile;
00335                 }
00336             }
00337         }
00338     }
00339 
00340     /**
00341      * Checks if $thePath is a path under one of the paths in $this->mounts
00342      * See comment in the header of this class.
00343      *
00344      * @param   string      $thePath MUST HAVE a trailing '/' in order to match correctly with the mounts
00345      * @return  string      The key to the first mount found, otherwise nothing is returned.
00346      * @see init()
00347      */
00348     function checkPathAgainstMounts($thePath) {
00349         if ($thePath && $this->isPathValid($thePath) && is_array($this->mounts)) {
00350             foreach ($this->mounts as $k => $val) {
00351                 if (t3lib_div::isFirstPartOfStr($thePath, $val['path'])) {
00352                     return $k;
00353                 }
00354             }
00355         }
00356     }
00357 
00358     /**
00359      * Find first web folder (relative to PATH_site.'fileadmin') in filemounts array
00360      *
00361      * @return  string      The key to the first mount inside PATH_site."fileadmin" found, otherwise nothing is returned.
00362      */
00363     function findFirstWebFolder() {
00364         global $TYPO3_CONF_VARS;
00365 
00366         if (is_array($this->mounts)) {
00367             foreach ($this->mounts as $k => $val) {
00368                 if (t3lib_div::isFirstPartOfStr($val['path'], PATH_site . $TYPO3_CONF_VARS['BE']['fileadminDir'])) {
00369                     return $k;
00370                 }
00371             }
00372         }
00373     }
00374 
00375     /**
00376      * Removes filemount part of a path, thus blinding the position.
00377      * Takes a path, $thePath, and removes the part of the path which equals the filemount.
00378      *
00379      * @param   string      $thePath is a path which MUST be found within one of the internally set filemounts, $this->mounts
00380      * @return  string      The processed input path
00381      */
00382     function blindPath($thePath) {
00383         $k = $this->checkPathAgainstMounts($thePath);
00384         if ($k) {
00385             $name = '';
00386             $name .= '[' . $this->mounts[$k]['name'] . ']: ';
00387             $name .= substr($thePath, strlen($this->mounts[$k]['path']));
00388             return $name;
00389         }
00390     }
00391 
00392     /**
00393      * Find temporary folder
00394      * Finds the first $this->tempFN ('_temp_' usually) -folder in the internal array of filemounts, $this->mounts
00395      *
00396      * @return  string      Returns the path if found, otherwise nothing if error.
00397      */
00398     function findTempFolder() {
00399         if ($this->tempFN && is_array($this->mounts)) {
00400             foreach ($this->mounts as $k => $val) {
00401                 $tDir = $val['path'] . $this->tempFN;
00402                 if (@is_dir($tDir)) {
00403                     return $tDir;
00404                 }
00405             }
00406         }
00407     }
00408 
00409 
00410     /*********************
00411      *
00412      * Cleaning functions
00413      *
00414      *********************/
00415 
00416     /**
00417      * Removes all dots, slashes and spaces after a path...
00418      *
00419      * @param   string      Input string
00420      * @return  string      Output string
00421      */
00422     function cleanDirectoryName($theDir) {
00423         return preg_replace('/[\/\. ]*$/', '', $this->rmDoubleSlash($theDir));
00424     }
00425 
00426     /**
00427      * Converts any double slashes (//) to a single slash (/)
00428      *
00429      * @param   string      Input value
00430      * @return  string      Returns the converted string
00431      */
00432     function rmDoubleSlash($string) {
00433         return str_replace('//', '/', $string);
00434     }
00435 
00436     /**
00437      * Returns a string which has a slash '/' appended if it doesn't already have that slash
00438      *
00439      * @param   string      Input string
00440      * @return  string      Output string with a slash in the end (if not already there)
00441      */
00442     function slashPath($path) {
00443         if (substr($path, -1) != '/') {
00444             return $path . '/';
00445         }
00446         return $path;
00447     }
00448 
00449     /**
00450      * Returns a string where any character not matching [.a-zA-Z0-9_-] is substituted by '_'
00451      * Trailing dots are removed
00452      *
00453      * @param   string      Input string, typically the body of a filename
00454      * @param   string      Charset of the a filename (defaults to current charset; depending on context)
00455      * @return  string      Output string with any characters not matching [.a-zA-Z0-9_-] is substituted by '_' and trailing dots removed
00456      */
00457     function cleanFileName($fileName, $charset = '') {
00458             // Handle UTF-8 characters
00459         if ($GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'] == 'utf-8' && $GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
00460                 // allow ".", "-", 0-9, a-z, A-Z and everything beyond U+C0 (latin capital letter a with grave)
00461             $cleanFileName = preg_replace('/[\x00-\x2C\/\x3A-\x3F\x5B-\x60\x7B-\xBF]/u', '_', trim($fileName));
00462 
00463             // Handle other character sets
00464         } else {
00465                 // Get conversion object or initialize if needed
00466             if (!is_object($this->csConvObj)) {
00467                 if (TYPO3_MODE == 'FE') {
00468                     $this->csConvObj = $GLOBALS['TSFE']->csConvObj;
00469                 } elseif (is_object($GLOBALS['LANG'])) { // BE assumed:
00470                     $this->csConvObj = $GLOBALS['LANG']->csConvObj;
00471                 } else { // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
00472                     $this->csConvObj = t3lib_div::makeInstance('t3lib_cs');
00473                 }
00474             }
00475 
00476                 // Define character set
00477             if (!$charset) {
00478                 if (TYPO3_MODE == 'FE') {
00479                     $charset = $GLOBALS['TSFE']->renderCharset;
00480                 } elseif (is_object($GLOBALS['LANG'])) { // BE assumed:
00481                     $charset = $GLOBALS['LANG']->charSet;
00482                 } else { // best guess
00483                     $charset = $GLOBALS['TYPO3_CONF_VARS']['BE']['forceCharset'];
00484                 }
00485             }
00486 
00487                 // If a charset was found, convert filename
00488             if ($charset) {
00489                 $fileName = $this->csConvObj->specCharsToASCII($charset, $fileName);
00490             }
00491 
00492                 // Replace unwanted characters by underscores
00493             $cleanFileName = preg_replace('/[^.[:alnum:]_-]/', '_', trim($fileName));
00494         }
00495             // Strip trailing dots and return
00496         return preg_replace('/\.*$/', '', $cleanFileName);
00497     }
00498 }
00499 
00500 
00501 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_basicfilefunc.php'])) {
00502     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['t3lib/class.t3lib_basicfilefunc.php']);
00503 }
00504 
00505 ?>