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;
//header('Allows: options get head post delete trace propfind proppatch copy mkcol put lock unlock');
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.sys1:'.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;
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];
}
}
?>