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]; } } ?>