Is there a best practice preventing BaseFileHelper::removeDirectory() from recursive deleting directory / ? Or a ‘too high’ directory in the domain structure?
When having bad luck a script that pulls this off manages to whipe the complete domain directory from the filesystem (I had bad luck a few times this year).
Besides, it’s almost undoable tracking the culprit (files removed by php are ‘legit’ actions), but found it in the end!
Some ideas of mine:
write a wrapper, and check:
$dir is not equal to ‘/’, or
$dir is minimum x levels away from DOCUMENT_ROOT, or
I’ve created a ‘whitelist’ of dirs that may be cleared from subdirs,
and wrapped created a removeDirectory() wrapper to solve it:
Config:
'params' => array(
/* A whitelist that's used to prevent functions as:
* - CFileHelper::removeDirectory()
* to completely whipe the domain folder, subdomain or just an important folder.
* The subdirectories of the following paths are considered 'save' for recursive removal
* Example: "/tmp" it's save to remove recursive "/tmp/test" (but not /tmp itself!)
* WARNING: Make sure you don't include a path like '/' or 'document root' ! ! !
*/
'directories_safe_to_empty' => array(
'/tmp',
$documentRoot.'/runtime/cache',
$documentRoot.'/httpdocs/assets',
)
)
Snippet from my FileHelper using above whitelist:
class FileHelper extends CFileHelper
{
/*
* Removes directory after confirming it's whitelisted as 'safe for recursive removal'.
* See whitelist in config['directories_safe_to_empty']
*/
public static function removeDirectory($directory, $options=array())
{
$realPath = realpath($directory);
if(!$realPath){
Yii::log("Invalid path passed to removerecursive according to realpath: '$directory'", "warning", 'shared.components.FileHelper');
// path doesnt exist so it doesnt have to be removed :)
return true;
}
if(FileHelper::isSafeForRecursiveRemoval($realPath)===false){
return false;
}
CFileHelper::removeDirectory($realPath, $options);
}
/*
* Checks the passed dir against a whitelist of dirs that are safe for recursvie removal.
* The passed dir must be a subdir of one of the whitelisted dirs !
* @param string $dir the path of a dir to be checked
* returns boolean
*/
public static function isSafeForRecursiveRemoval($dir)
{
$realPath = realpath($dir);
if(!$realPath)
return false;
// Yii::app()->params['paths']
$config = Yii::app()->params;
if(!isset($config['directories_safe_to_empty']) || !is_array($config['directories_safe_to_empty'])){
Yii::log("Config key params['directories_safe_to_empty'] not set or not an array", "error", 'shared.components.FileHelper');
return false;
}
$continueRemoval = false;
foreach($config['directories_safe_to_empty'] as $idx => $safeParent){
$realParent = realpath($safeParent);
if(!$realParent){
Yii::log("Invalid path according to realpath: params['directories_safe_to_empty'][\$idx] = '$safeParent'", "error", 'shared.components.FileHelper');
continue;
}
// dir to be removed is in parent's path,
// and path of dir to be removed is longer than parents
if(strpos($realPath, $realParent.'/') === 0 && (strlen($realPath) > strlen($realParent.'/'))){
return true;
}
}
// echo 'path is not safe for recursive removal';
Yii::log("Trying to recursive remove unsafe dir: '$realPath'", "warning", 'shared.components.FileHelper');
// Yii::trace("Trying to recursive remove unsafe dir: '$realPath'", 'shared.components.FileHelper');
return false;
}
}