<?   
{[copyright]}

ini_set('display_errors', 'Off');
ini_set('track_errors', 1);
ini_set('memory_limit','100M');
error_reporting(0);
set_time_limit(300);

register_shutdown_function('serval_shutdown_func');

/* Класс сканера */
Class Scanner{
	var $log = array(
		'scan'=>array(), /* Массив папок, которых просканировали */
		'dir'=>array(), /* Массив папок, которые в очереди */
		'file'=>array() /* Массив информации о файлах (Имя, Размер, Хеш, Вложенность, Папка) */
	);
	var $ext 		= false; 	/* Массив расширений файлов для сканирования */
	var $exc 		= false; 	/* Массив папок, которые нужно исключить */
	var $excf		= false; 	/* Массив файлов, которые нужно исключить */
	var $rootDir 	= '';   	/* Root папка */
	var $dirId 		= 1; 		/* Счетчик */
	var $version 	= 1.7; 		/* Версия сканера */
	var $maxDir		= 100; 		/* Максимальное кол-во папок за раз по умолчанию */
	var $isTest		= false;
	var $beginTime	= 0;
	var $limitTime	= 60;
	var $serval_tmp = "serval_tmp";
	
	function run($param = array()){
		if(!is_array($param) || empty($param['path'])){
			return array("status"=>'error','version'=>$this->version,'msg'=>"Ошибка передачи данных");
		}
		$param['path'] = $this->fixPath(($param['path'] === '.' ? $_SERVER['DOCUMENT_ROOT'] : $param['path']));
		$this->beginTime = time();
		if(isset($param['limitTime']) && (int)$param['limitTime']>=20){
			$this->limitTime = (int)$param['limitTime'];
		}
		
		/* Максимальное кол-во папок */
		if($param['dirMax']>0){
			$this->maxDir = $param['dirMax'];
		}
		/* Если это тест */
		if($param['is_test']=="yes"){
			$this->isTest = true;
		}
		
		if(is_dir($param['path'])){
			if(is_array($param['ext']) && count($param['ext'])>0){
				foreach($param['ext'] as $v){
					$v = trim($v);
					if(!empty($v)){
						$this->ext[] = $v;
					}
				}
			}
			$this->ext = (is_array($param['ext']) && count($param['ext'])>0) ? $param['ext'] : false;
			
			if(is_array($param['exc']) && count($param['exc'])>0){
				foreach($param['exc'] as $v){
					$v = trim($v);
					if(!empty($v)){
						$dir = $param['path'].'/'.$v.'/';
						$this->exc[] = $this->fixPath($dir,false);    
					}
				}
			}
			$this->excf = (is_array($param['excf']) && count($param['excf'])>0) ? $param['excf'] : false;
			if(is_array($param['excf']) && count($param['excf'])>0){
				foreach($param['excf'] as $v){
					$v = trim($v);
					if(!empty($v)){
						$this->excf[] = $this->fixPath($param['path'].'/'.$v,false);
					}
				}
			}
			
			
			$this->rootDir = $param['path'];
			/* Если передали папки, которые нужно просканировать (шаги) */
			if(is_array($param['listDir']) && count($param['listDir'])>0){
				foreach($param['listDir'] as $val){
					if(!empty($val)){
						$this->dirId++;
						$this->log['dir'][$this->dirId] = $val;
						$this->find($val['path'],$this->dirId,($val['level']+1));
					}
				}
			}else{
				$this->find($param['path'],$this->dirId);
			}
			
			return $this->_shuffle(array("status"=>'ok','serval_tmp'=>(file_exists($this->serval_tmp) ? 'yes' : 'no'),'dirMax'=>$this->maxDir,'version'=>$this->version,'log'=>$this->log));
		}else{
			return $this->_shuffle(array("status"=>'error','serval_tmp'=>(file_exists($this->serval_tmp) ? 'yes' : 'no'),'version'=>$this->version,'msg'=>"Ошибка! root папка не доступна!"));
		}
	}
	
	/*
		Метод сканирует папку
	*/
	function find($path,$dirId,$i=0){
		if(time()-$this->beginTime>=($this->limitTime-15)){
			return false; 
		}
		if(count($this->log['scan'])>=$this->maxDir) return false; /* Максимум, который можно просканировать */
		if($i>500) return false; /* Если большая вложенность */
		
		$path 			= $this->fixPath($path.'/');
		$fixPath 		= $this->fixPath($path.'/',false);
		$isFinished 	= true;
		$dirHashName 	= "tmp_serval_".md5($fixPath.'x'.__FILE__);
		$skeepFiles 	= false;
		$finished 		= true;
		if(isset($this->log['dir'][$dirId]['continue']) && file_exists($this->serval_tmp."/".$dirHashName)){
			$skeepFiles = explode("\n",file_get_contents($this->serval_tmp."/".$dirHashName));
		}
		
		/* Читаем папку */
		if($dir = opendir($path)){
			if(isset($this->log['dir'][$dirId])){
				$this->log['scan'][$dirId] = $this->log['dir'][$dirId];
			}
			unset($this->log['dir'][$dirId]);
			while (false !== ($val = readdir($dir))) {
				if (time()-$this->beginTime>=($this->limitTime-15)) {
					$isFinished = false;
					if(!$this->isTest){
						if(!isset($this->log['scan'][$dirId]['continue'])){
							if(file_exists($this->serval_tmp."/".$dirHashName)){
								unlink($this->serval_tmp."/".$dirHashName);
							}	
						}
						if(file_exists($this->serval_tmp)){
							$this->log['dir'][$dirId] = array('path'=>$fixPath,'continue'=>true,'level'=>$this->log['scan'][$dirId]['level']);
							$this->log['scan'][$dirId]['continue'] = true;
							file_put_contents($this->serval_tmp."/".$dirHashName,($skeepFiles ? "\n" : "").implode("\n",$listFile),FILE_APPEND);
						}
					}
					break;
				}
				$val = $path.$val;
				$pathInfo = pathinfo($val);
				if($pathInfo['basename'] === '.' || $pathInfo['basename'] === '..' || is_link($val)) continue;
				if($this->exc && in_array($val.'/',$this->exc) ) continue;
				
				if(is_dir($val)){
					$PathName = $this->fixPath($pathInfo['dirname'].'/'.$pathInfo['basename'].'/',false); 
					if(empty($PathName)) continue;
					if($this->skip($this->fixPath($pathInfo['dirname'].'/'.$pathInfo['basename'].'/'),$this->exc)) continue;  				
					$this->dirId++;
					$this->log['dir'][$this->dirId] = array('path'=>$PathName,'level'=>$i);
				}
				
				if(is_file($val)){
					if(!$this->ext || in_array(".".$pathInfo['extension'],$this->ext)){
						if($skeepFiles){
							if(in_array(md5($val),$skeepFiles)){
								continue;
							}
						}
						if($this->skip($val,$this->excf)) continue;		
						$listFile[] = md5($val);						
						$this->log['file'][] = array(
							'dirId'=>$dirId,
							'level'=>$i,
							'name'=>$pathInfo['basename'],
							'size'=>filesize($val),
							'hash'=>$this->hashFile($val),
						);
					}	
				}
				unset($pathInfo);
			}
			closedir($dir);
		}
		if($isFinished){
			if(isset($this->log['scan'][$dirId]['continue'])){
				unset($this->log['scan'][$dirId]['continue']);
			}
			if($skeepFiles){
				if(is_file($this->serval_tmp."/".$dirHashName)){
					unlink($this->serval_tmp."/".$dirHashName);
				}				
			}
		}

		if(isset($this->log['dir'][($dirId+1)])){
			if(!$this->isTest){
				$this->find($this->log['dir'][($dirId+1)],($dirId+1),($i+1));
			}
		}
	}
	
	/*
		FIX пути папки
	*/
	function fixPath($val,$root = true){
		$val = $this->rootDir.'/'.str_replace($this->rootDir,"/",$val);
		if(!$root){ /* Удаляем рутовую директорию */
			$val = '/'.str_replace($this->rootDir,"/",$val);
		}
		return preg_replace('~[\/]+~u','/',$val);
	}
	
	/* 
		Метод получает хеш сумму файла 
	*/
	function hashFile($file){
		$hash = false;
		if (function_exists('hash_file')) {
			$hash = @hash_file('md5', $file);
		} elseif(function_exists('md5_file')) {
		  $hash = @md5_file($file);
		} else {
			if($c = file_get_contents($file)){
				$hash = md5($c);
				unset($c);
			}
		}
		return $hash ? $hash : -1;
	}
	
	/* 
		Метод обработки исключений
	*/
	function skip($str_in,$exc){
		if(is_array($exc) && count($exc)>0){
			foreach($exc as $str){
				$str = preg_quote("[{$str}]","#");
				$str = str_replace("\*","(.*)",$str);	
				if(preg_match("#^({$str})#","[".$str_in."]",$matches)){
					return true;
				}
			}
		}
		return false;
	}
	
	/* Мешаем ключи массива в случайном порядке */
	function _shuffle($result){
		$keys = array_keys($result);
		shuffle($keys);
		$res = array();
		foreach($keys as $k){
			$res[$k] = $result[$k];
		}
		unset($result);
		unset($keys);
		return $res;
	}
	
}

/* Класс кодирования */
Class BritCrypt{
	var $key 					= null;
	var $encodingIn 			= 'UTF-8';
	var $encodingOut 			= 'CP866';
	var $stb					= 63;
	var $type					= 'default';
	
	/* Кодирование */	
	function encrypt($string=''){
		if(empty($this->key)) return false;
		
		switch($this->type){
			case 'lite':
				return $this->crypt_lite($string);
			break;
			case 'reinforced':
		    $seed = '';
		    for ($i = 0; $i < strlen($this->key); $i++) {
		        $seed .= chr(mt_rand(0, 255));
		    }
		    $string ^= $this->gamma($seed, strlen($string));
		    return ($seed ^ $this->key) . $string;
			break;
			default:
				$keyw = $this->calculateKeyw($string);
				$Len = mb_strlen($string, $this->encodingIn);
				
				$result = '';
		    for ($i = 0; $i < $Len; $i++) {
			    $char = mb_substr($string, $i, 1, $this->encodingIn);
			    $ord = ord(iconv($this->encodingIn, $this->encodingOut, $char));
			    $char = chr(($ord - $this->stb) ^ $keyw[$i]);   
			    $result.= iconv($this->encodingOut, $this->encodingIn, $char);
		    }
		    unset($string);
				return $result;
			break;
		}
	}
	
	function gamma($seed, $len) {
	    $t = '';
	    $count = ceil($len / 20);
	    for ($i = 0; $i < $count; $i++) {
	        $t.= sha1($seed . pack('N', $i), true);
	    }
	    return substr($t, 0, $len);
	}

  function calculateKeyw($pass){
	    $keyw = array();
	    $lenPass = mb_strlen($pass, $this->encodingIn);
	    $lenKey = mb_strlen($this->key, $this->encodingIn);
	    for ($i = 1; $i <= $lenPass; $i++) {
	        if ($lenKey < $lenPass) {
	            $j = $i % $lenKey;
	            if ($j === 0) {
	                $j = $lenKey;
	            }
	        } else {
	            $j = $i;
	        }
	
	        $keyw[] = ord(mb_substr($this->key, $j - 1, 1, $this->encodingIn));
	    }
	    return $keyw;
  }
  /* Декодирование */
	function decrypt($string=''){   
		if(empty($this->key)) return false;
		switch($this->type){
			case 'lite':
				return $this->crypt_lite($string);
			break;
			case 'reinforced':
		    $keyLen = strlen($this->key);
		    $seed = substr($string, 0, $keyLen) ^ $this->key;
		    $string = substr($string, $keyLen);
		    $string ^= $this->gamma($seed, strlen($string));
		    return $string;
			break;
			default:
				$keyw = $this->calculateKeyw($string);
				$Len = mb_strlen($string, $this->encodingIn);
				$result = '';
				for ($i = 0; $i < $Len; $i++) {
					$char = mb_substr($string, $i, 1, $this->encodingIn);
					$ord = ord(iconv($this->encodingIn, $this->encodingOut, $char));
					$ord = $ord ^ $keyw[$i];
					$result .=iconv($this->encodingOut, $this->encodingIn, chr($ord + $this->stb));
				}
				unset($string);      
			return $result;
		}
	}
	/* 
		Легкое кодирование
	*/
	function crypt_lite($string){
		if(empty($this->key)) return false;
		for($i=0;$i<mb_strlen($string,'utf-8');){
			for($j=0;$j<mb_strlen($this->key,'utf-8');$j++, $i++){
				$outText .= $string{$i} ^ $this->key{$j};
			}
		}
		unset($string);
		return $outText;
	}	
}

$_POST['_scan_isOk'] = false;
if(isset($_POST['run']) && $_POST['run']=="DoCheckThis"){
	$sCode  			= "{[sCODE]}";
	$_crypt 			= new BritCrypt;
	$_crypt->key 		= $sCode;  
	$_crypt->type 		= $_POST['crypt_type'];
	$data				= unserialize($_crypt->decrypt(base64_decode($_POST['data'])));
	unset($_POST['data']);
	$scan 		= new Scanner();
	$result 	= $scan->run($data);
	unset($data);
	echo $_crypt->encrypt(base64_encode(serialize($result)));
	$_POST['_scan_isOk'] = true;
}

/* Для отлова ошибок */
function serval_shutdown_func(){
	if(!$_POST['_scan_isOk']){
		$error  			= error_get_last();
		$sCode  			= "{[sCODE]}";
		$_crypt 			= new BritCrypt($sCode);
		$_crypt->type 		= $_POST['crypt_type'];
		$result 			= array('status'=>'error','msg'=>"Error: ".$error['message']);
		echo $_crypt->encrypt(serialize($result));
		die();
	}
}
?>