null,
		'log' => null,
		'edit' => null,
		'move' => null,
		'delete' => null,
		'list' => null,
		'properties' => null,
		'copy' => null
	);
	public $do = array(
		'edit' => null,
		'move' => null,
		'delete' => null,
		'list' => null,
		'copy' => null
	);
	public static function logPath() {
		return __DIR__.SLASH.'logs';
	}
	function start(){
		$server = $_SERVER;
		$method = strtoupper($server['REQUEST_METHOD']);
		$headers = getallheaders();
		$body = file_get_contents("php://input");
		$requestUri = explode('/',$server['REQUEST_URI']);
		$requestUri = array_filter($requestUri);
		$requestUri = '/'.implode('/',$requestUri);
		$server['REQUEST_URI'] =  $requestUri;
		$log = PHP_EOL.PHP_EOL.'---------- '.$method.' --------------';
		$log .= PHP_EOL.'Fichier : '.$requestUri;
		foreach($headers as $key => $header){
			$log .=  PHP_EOL."\t - ".$key.' : '.$header;
		}
		$log .= PHP_EOL."\t === ".'Requête : '.PHP_EOL. $body;
		$this->logit($log);
		if(!file_exists(self::logPath()))
			mkdir(self::logPath(),0755,true);
		if($method!='OPTIONS' && isset($this->do['login']) ){
			try{
				$function = $this->do['login'];
				$user = isset($server['PHP_AUTH_USER']) ? $server['PHP_AUTH_USER']: '';
				$password = isset($server['PHP_AUTH_PW']) ? $server['PHP_AUTH_PW']: '';
				$header = apache_request_headers();
				if(isset($header['Authorization']) && $header['Authorization']!=''){
					$authorization = explode(' ',$header['Authorization']);
					if(count($authorization)==2){
						$credentials = explode(':',base64_decode($authorization[1]));
						if(isset($credentials[0]))  $user = $credentials[0];
						if(isset($credentials[1]))  $password = $credentials[1];
					}
				}
				$user = $user =='' ? 'anonymous': $user;
				$password = $password =='' ? 'anonymous': $password;
				$this->user = $function($user,$password);
			}catch(Exception $e){
		    	$this->logit('REQUEST URI : '.$requestUri.PHP_EOL.'REQUEST HEADERS : '.json_encode($headers));
				$this->logit('Error '.self::headerFromCode(401).'  for -'. $user.' :'.$e->getCode().' '.$e->getMessage() );
				$this->logit(json_encode($_SERVER,JSON_PRETTY_PRINT));
				$header = apache_request_headers();
				$header = $this->logit(json_encode($header,JSON_PRETTY_PRINT));
				$header = $this->logit(json_encode($_REQUEST,JSON_PRETTY_PRINT));
				self::emit_header($e->getCode());
	    		if($e->getCode()==401)
					header('WWW-Authenticate: Basic realm="Software Webdav gate"');
		    	echo $e->getMessage();
		    	exit();
		    }
		}
		try{
			switch($method) {
				//Returns a list of properties of a file, a directory of a directory and its files.
				case 'PROPFIND':
					$log= "\t === ".'Réponse : '.PHP_EOL;
					try{
						$depth = isset($headers['Depth']) ? $headers['Depth'] : 0;
						if(in_array(mt_basename($server['REQUEST_URI']), $this->ignoreFiles)){
							$this->logit('File in blacklist ignored'.PHP_EOL);
							return self::emit_header(404);
						}
						$scanned = $this->folder.$this->get_path($server);
						$files = array();
						if(isset($this->do['list']) ){
							$function = $this->do['list'];
							$files = $function($scanned,$depth);
						}
						$locks = $this->lock();
						$xml = '';
						foreach ($files as $i=>$file) {
							$url = $requestUri.'/';
							//Si on est pas sur le premier dossier/fichier d'origine, c'est qu'on est sur un enfant du dossier, on concatene son url au dossier
							if($i!=0) $url .= rawurlencode($file['label']);
							$xml .= self::xml_file($url,$file,$locks);
						}
						$stream = ''.$xml.'';
						self::emit_header(207);
						header('DAV: '.$this->version);
						header('Content-Type: text/xml');
						echo $stream;
						$log.= "\t".'DAV: '.$this->version.PHP_EOL;
						$log.= "\t\t".'Content-Type: text/xml'.PHP_EOL;
						$log.= "\t\t".$stream.PHP_EOL;
						$this->logit($log);
					}catch(Exception $e){
						$log.= "\t".$e->getCode().' : '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
			    	}
		      	break;
		      	//Récuperation d'un fichier (ou dossier ?)
		      	case 'GET':
		      		$log= "\t === ".'Réponse : '.PHP_EOL;
			      	try{
				        $file = $this->folder.$this->get_path($server);
				      	$this->logit('Download '.$file);
						if(!isset($this->do['download']) ) throw new Exception("Not implemented", 501);
						$function = $this->do['download'];
						$stream = $function($file);
						self::emit_header(200);
						header('Content-Length: '.strlen($stream));
						echo $stream;
						$log.= "\t".'- 200 OK'.PHP_EOL;
						$log.= "\t".'- Content-Length: '.strlen($stream);
						$log.= "\t\t".$stream.PHP_EOL;
						$this->logit($log);
			        }catch(Exception $e){
			        	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    	header('Content-Type: text/xml');
				    }
			    break;
			    //Envoi d'un fichier
			    case 'PUT':
			    	$log= "\t === ".'Réponse : '.PHP_EOL;
			      	try{
				        $target = $this->folder.$this->get_path($server);
						$this->logit('PUT '.$target);
						if(!isset($this->do['edit']) ) throw new Exception("Not implemented", 501);
						$function = $this->do['edit'];
						$function($target, $body,'file');
						self::emit_header(201);
						$log.= "\t".'- 201 Created'.PHP_EOL;
						$this->logit($log);
				    }catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
			    break;
			    //Suppression dossier/fichier
			    case 'DELETE':
			    	$log= "\t === ".'Réponse : '.PHP_EOL;
			      	try{
				        $path = $this->folder.$this->get_path($server);
				        if(!isset($this->do['delete']) ) throw new Exception("Error Processing Request", 501);
						$function = $this->do['delete'];
						$function($path);
						self::emit_header(200);
					}catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
			    break;
			    //Déplacement/renommage dossier/fichier
			    case 'MOVE':
			    	$log= "\t === ".'Réponse : '.PHP_EOL;
			      	try{
				        $target = $this->folder.$this->get_path($server);
				        $toUrl = rawurldecode($server['HTTP_DESTINATION']);
				        $toUrl = parse_url($toUrl, PHP_URL_PATH);
				        $this->logit('Move to '.$toUrl);
				        $to = $this->folder.str_replace('/',SLASH,utf8_decode(str_replace($this->root,'',$toUrl)));
				        $to = substr($to, -1,1) == SLASH ? substr($to, 0,strlen($to)-1): $to;
				        $this->logit('Move '.$target.' to '.$to.' (url :'.$toUrl.')');
				        if(!isset($this->do['move']) ) throw new Exception("Not implemented", 501);
						$function = $this->do['move'];
						$function($target,$to);
						self::emit_header(200);
			        }catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
			    break;
		        //Creation de dossier
		        case 'MKCOL':
		        	$log= "\t === ".'Réponse : '.PHP_EOL;
			      	try{
				        $path = $this->folder.$this->get_path($server);
				        $this->logit('Create folder '.$path);
						if(isset($this->do['edit']) ){
							$function = $this->do['edit'];
							$function($path, '','create','directory');
							self::emit_header(200);
							break;
						}
						self::emit_header(200);
				        mkdir($path,0755,true);
			        }catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
		    	break;
		    	//The OPTIONS method allows an http client to find out what HTTP methods are supported on a specific url.
		    	case 'OPTIONS':
		    		$log= "\t === ".'Réponse : '.PHP_EOL;
		        	if (ob_get_length()) ob_end_clean();
		        	header('Allows: options get head delete propfind copy mkcol put lock unlock proppatch');
		        	$log.= "\t".'- Allows: options get head delete propfind copy mkcol put lock unlock proppatch'.PHP_EOL;
					$this->logit($log);
		      	break;
		      	case 'HEAD':
		      		$log= "\t === ".'Réponse : '.PHP_EOL;
		      		self::emit_header(200);
		      		$log.= "\t".'- 200 Ok'.PHP_EOL;
					$this->logit($log);
		      	break;
		      	case 'POST':
		      		$log= "\t === ".'Réponse : '.PHP_EOL;
		        	self::emit_header(501);
		        	$log.= "\t".'- 501 Not implemented'.PHP_EOL;
					$this->logit($log);
		      	break;
		      	case 'TRACE':
		      		$log= "\t === ".'Réponse : '.PHP_EOL;
		        	self::emit_header(501);
		        	$log.= "\t".'- 501 Not implemented'.PHP_EOL;
					$this->logit($log);
		      	break;
		      	//Updates properties of a resource or collection.
		      	case 'PROPPATCH':
		      		$log= "\t === ".'Réponse : '.PHP_EOL;
					$properties = array();
					$namespaces = array();
				   	$ns = '';
				   	preg_match_all('|xmlns:([^\=]*)="([^"]*)"|iUs', $body, $matches,PREG_SET_ORDER);
				   	foreach ($matches as $match) {
				   		if(count($match)!=3) continue;
				   		$namespaces[$match[1]] = $match[2];
				   		$ns.=' xmlns:'.$match[1].' = "'.$match[2].'" ';
				   	}
				   	preg_match('|(.*)<\/D:prop>|iUs', $body, $matches);
				   	if(isset($matches[1])){
					   	preg_match_all('|<([^:]*):([^\>]*)>([^\<]*)<\/\1:\2>|iUs', $matches[1], $matches,PREG_SET_ORDER);
					   	foreach ($matches as $match) {
					   		if(count($match)==4)
					   			$properties[$match[2]] = array('ns'=>$match[1],'value'=>$match[3],'key'=>$match[2]);
					   	}
				   	}
				   	$this->logit('Set properties '.json_encode($properties,JSON_PRETTY_PRINT));
					if(isset($this->on['properties']) ){
						self::emit_header(200);
						$function = $this->do['properties'];
						$function($properties,$namespaces);
						break;
					}
				   	$response = '
					
					  
					    /
					    
					      HTTP/1.1 200 OK
					      
					      ';
					        foreach ($properties as $key => $value) {
					        	$response .= '<'.$value['ns'].':'.$value['key'].' />';
					        }
					$response .= '
					      
					    
					  
					';
				   	$this->logit('[RESPONSE]'.PHP_EOL.$response);
			       	self::emit_header(207);
			       	header('Content-Type: text/xml');
			       	echo $response;
			       	$log.= "\t".'- 207 Multi status'.PHP_EOL;
			       	$log.= "\t".'- Content-Type: text/xml'.PHP_EOL;
			       	$log.= "\t\t".$response.PHP_EOL;
					$this->logit($log);
			    break;
		    	//Copie d'un élement vers un nouvel emplacement
		    	case 'COPY':
		    		$log= "\t === ".'Réponse : '.PHP_EOL;
			      	try{
				        $target = $this->folder.$this->get_path($server);
				        $toUrl = rawurldecode($server['HTTP_DESTINATION']);
				        $toUrl = parse_url($toUrl, PHP_URL_PATH);
				        $to = $this->folder.str_replace('/',SLASH,utf8_decode(str_replace($this->root,'',$toUrl)));
				        $this->logit('Copy '.$target.' to '.$to.' (url :'.$toUrl.')');
				        if(!isset($this->do['copy'])) throw new Exception("Not implemented", 501);
						$function = $this->do['copy'];
						$function($target,$to);
						self::emit_header(200);
						$log.= "\t".'- 200 OK'.PHP_EOL;
						$this->logit($log);
			        }catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
		    	break;
		    	//Verouillage d'un élement
		    	case 'LOCK':
		    		$log= "\t === ".'Réponse : '.PHP_EOL;
		    		try{
			    		$target = $this->folder.$this->get_path($server);
						$response = 200;
						$locks = $this->lock();
						//Renouvellement de lock ou demande par un autre user
						if(isset($locks[base64_encode($requestUri)])){
							$lock = $locks[base64_encode($requestUri)];
							if($lock['owner']!=$this->user->login) {
								$response = 423;
							}else{
								$properties = array(
									'owner'=>$this->user->login,
									'created'=>time(),
									'depth'=>isset($headers['Depth']) ? $headers['Depth'] : 'Infinity',
									'timeout'=>isset($headers['Timeout']) ? $headers['Timeout'] : 'Second-3600',
								);
								$lock = $this->lock($requestUri,$properties);
							}
						//Création d'un nouveau lock
						}else{
							$properties = array(
								'owner'=>$this->user->login,
								'created'=>time(),
								'depth'=>isset($headers['Depth']) ? $headers['Depth'] : 'Infinity',
								'timeout'=>isset($headers['Timeout']) ? $headers['Timeout'] : 'Second-3600',
							);
							$lock = $this->lock($requestUri,$properties);
						}
				        self::emit_header($response);
				        header('Lock-Token: <'.$lock['token'].'>');
				        $requestUri = str_replace('&','%26',$requestUri);
				        $xml = '
						   
						     
						       
						         
						         
						         '.$lock['depth'].'
						         
						           '.$lock['owner'].'
						         
						         '.$lock['timeout'].'
						         
						           '.$lock['token'].'
						         
						         
						           '.$requestUri.'
						         
						       
						     
						   ';
				        echo $xml;
				        $log.= "\t".'- '.$response.' '.self::headerFromCode($response).PHP_EOL;
				        $log.= "\t".'-  Lock-Token: <'.$lock['token'].'>'.PHP_EOL;
				        $log.= "\t\t".$xml.PHP_EOL;
						$this->logit($log);
				    }catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
		    	break;
		    	//Déverouillage d'un élement
		    	case 'UNLOCK':
		    		$log= "\t === ".'Réponse : '.PHP_EOL;
		    		try{
			    		$target = $this->folder.$this->get_path($server);
			    		$token = isset($headers['Lock-Token']) ? $headers['Lock-Token'] : 'no-token';
			    		$token = preg_replace('|[\<\>]|is','',$token);
			    		$locks = $this->lock();
			    		if(isset($locks[base64_encode($requestUri)])){
			    			if($locks[base64_encode($requestUri)]['token'] == $token){
					    		$this->lock($requestUri,false);
			    			}
			    		}
			    		self::emit_header(204);
			    		$log.= "\t".'- 204 No Content'.PHP_EOL;
						$this->logit($log);
		    		}catch(Exception $e){
				    	$log.= "\t".'- '.$e->getCode().' '.$e->getMessage().PHP_EOL;
						$this->logit($log);
				    	self::emit_header($e->getCode());
				    }
		    	break;
		    }
		}catch(Exception $e){
			self::emit_header(500);
		}
	    exit();
	}
	//Méthode de gestion du manifest des locks fichiers (ajout, supression, listing)
	// Si properties est a false : supression d'un verrou pour 'file'
	// Si file + properties est renseigné : Création d'un verrou pour 'file' avec ces propriétés
	// Si file et properties sont a vide : on retourne juste la liste des verrou non expirés
	public function lock($file=null,$properties = array()){
		if(!file_exists($this->lockfile)) file_put_contents($this->lockfile,json_encode(array()));
		$locks = json_decode(file_get_contents($this->lockfile),true);
		foreach($locks as $key=>$lock){
			$timeoutInfos = explode('-',$lock['timeout']);
      		//Calcul du délais d'expiration d'un verrou
      		$secondNumber = 60;
      		if(count($timeoutInfos)==2){
      			list($unity,$number) = $timeoutInfos;
      			$secondNumber = $number;
      			if($unity == 'Minut') $secondNumber *= 60;
      			if($unity == 'Hour') $secondNumber *= 3600;
      			if($unity == 'Day') $secondNumber *= 86400;
      		}
      		//Supression du verrou fichier si expiré
  			if(time() <= ($secondNumber + $lock['created'])) continue;
			unset($locks[$key]);
		}
		//Si aucun parametre n'est spécifié, on retourne la liste des verrous
		if($file==null) return $locks;
		//Si les properties sont a false, on supprime le verrou
		if($properties===false){
			unset($locks[base64_encode($file)]);
		}else{
			//Si les properties du verrou sont renseigné, on le save
			$properties['token'] = isset($properties['token']) && $properties['token'] != '' ? $properties['token'] : 'fr.core.'.sha1($file);
			$locks[base64_encode($file)] = $properties;
		}
		file_put_contents($this->lockfile, json_encode($locks));
		return !$properties ? '': $locks[base64_encode($file)];
	}
	/*
		create_time : timestamp de création
		update_time : timestamp dernière modification
		length : taille du dossier ou du fichier
		type : directory/file
	*/
	public static function xml_file($url,$fileInfos,$locks){
		if(!isset($fileInfos['create_time'])) $fileInfos['create_time'] =  time();
		if(!isset($fileInfos['update_time'])) $fileInfos['update_time']  =  time();
		if(!isset($fileInfos['length'])) $fileInfos['length'] = 0;
		$fileInfos['create_time'] = $fileInfos['create_time'] - (3600*2);
		$fileInfos['update_time'] = $fileInfos['update_time'] - (3600*2);
		$fileInfos['update_time'] = date('D, j M Y H:i:s',  $fileInfos['update_time']).' GMT';
		$fileInfos['create_time'] = date('D, j M Y H:i:s',  $fileInfos['create_time']).' GMT';
		$url = str_replace('&','%26',$url);
		$xml = '
					'.$url.'';
	    $xml .= '
	              ';
	            if($fileInfos['type']=='directory'){
	            $xml .='
	                  
	                ';
	            }else{
	            	$xml .='';
	            	$xml .='
                    
			            
			            
			          
                  ';
	            }
	            if(isset($locks[base64_encode($url)])){
	              		$lock = $locks[base64_encode($url)];
	      				//Envoi du verrou au client si un verrou est en place
	              		$xml .='
                        	
                        	    
                        	    
                        	    '.$lock['depth'].'
                        	    '.$lock['owner'].'
                        	    '.$lock['timeout'].'
                        	    
                        	        '.$lock['token'].'
                        	    
                        	
	                    ';
	            }
              	if(isset($fileInfos['length']))
	              	 $xml.= ''.$fileInfos['length'].'';
	           $xml .='
	                "'.sha1($fileInfos['update_time']).'"
	                '.$fileInfos['update_time'].'
	                '.$fileInfos['create_time'].'
	              
	              HTTP/1.1 200 OK
	           ';
	           if(!isset($fileInfos['length'])){
	           		$xml .=  '
	              
	                
	              
	              HTTP/1.1 404 Not Found
	           		';
	           }
	    $xml .= '';
	    return $xml;
	}
	function get_path($server){
		$path = $this->get_parameter($server['REQUEST_URI']);
		$path = rawurldecode(utf8_decode($path));
		return str_replace('/',SLASH,$path);
	}
	public function get_parameter($parameter){
		$path = rawurldecode($parameter);
		$path = str_replace('\\', '/', $path);
		$path = str_replace('//', '/', $path);
		$path = substr($path,strlen($this->root));
		if(substr($path, -1)=='/') $path = substr($path, 0,-1);
		return $path;
	}
	function logit($message){
		if(isset($this->on['log']) ){
			$function = $this->on['log'];
			$function($message);
		}
		if($this->logs=='') return;
		if(!file_exists(dirname($this->logs))) mkdir(dirname($this->logs));
		file_put_contents($this->logs, $message.PHP_EOL,FILE_APPEND);
	}
	public static function emit_header($code){
		if (ob_get_length()) ob_end_clean();
		header(self::headerFromCode($code));
	}
	public static function headerFromCode($code){
		$codes = array(
			200 => 'HTTP/1.1 200 Ok',
	        201 => 'HTTP/1.1 201 Created',
	        204 => 'HTTP/1.1 204 No Content',
	        207 => 'HTTP/1.1 207 Multi-Status',
	        401 => 'HTTP/1.0 401 Unauthorized',
	        403 => 'HTTP/1.1 403 Forbidden',
	        404 => 'HTTP/1.1 404 Not Found',
	        406 => 'HTTP/1.1 406 Not acceptable',
	        409 => 'HTTP/1.1 409 Conflict',
	        415 => 'HTTP/1.1 415 Unsupported Media Type',
	        423 => 'HTTP/1.1 423 Locked',
	        500 => 'HTTP/1.1 500 Internal Server Error',
	        501 => 'HTTP/1.1 501 Method not implemented',
		);
		return isset($codes[$code]) ? $codes[$code] : $codes[500];
	}
}
?>