123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676 |
- <?php
- class WebDav{
- public $root,$folder,$logs,$version = 1,$user,$lockfile,$ignoreFiles = array();
- public $on = array(
- 'login' => 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 = '<D:multistatus xmlns:D="DAV:">'.$xml.'</D:multistatus>';
- 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>(.*)<\/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 = '<?xml version="1.0" encoding="utf-8"?>
- <d:multistatus xmlns:d="DAV:" '.$ns.'>
- <d:response>
- <d:href>/</d:href>
- <d:propstat>
- <d:status>HTTP/1.1 200 OK</d:status>
- <d:prop>
- ';
- foreach ($properties as $key => $value) {
- $response .= '<'.$value['ns'].':'.$value['key'].' />';
- }
- $response .= '
-
- </d:prop>
- </d:propstat>
- </d:response>
- </d:multistatus>';
- $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 = '<?xml version="1.0" encoding="utf-8" ?>
- <D:prop xmlns:D="DAV:">
- <D:lockdiscovery>
- <D:activelock>
- <D:locktype><D:write/></D:locktype>
- <D:lockscope><D:exclusive/></D:lockscope>
- <D:depth>'.$lock['depth'].'</D:depth>
- <D:owner>
- <D:href>'.$lock['owner'].'</D:href>
- </D:owner>
- <D:timeout>'.$lock['timeout'].'</D:timeout>
- <D:locktoken>
- <D:href>'.$lock['token'].'</D:href>
- </D:locktoken>
- <D:lockroot>
- <D:href>'.$requestUri.'</D:href>
- </D:lockroot>
- </D:activelock>
- </D:lockdiscovery>
- </D:prop>';
- 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 = '<D:response>
- <D:href>'.$url.'</D:href>';
- $xml .= '<D:propstat>
- <D:prop>';
- if($fileInfos['type']=='directory'){
- $xml .='<D:resourcetype>
- <D:collection/>
- </D:resourcetype>';
- }else{
- $xml .='<D:resourcetype/>';
- $xml .='<D:supportedlock>
- <D:lockentry>
- <D:lockscope><D:exclusive/></D:lockscope>
- <D:locktype><D:write/></D:locktype>
- </D:lockentry>
- </D:supportedlock>';
- }
- if(isset($locks[base64_encode($url)])){
- $lock = $locks[base64_encode($url)];
- //Envoi du verrou au client si un verrou est en place
- $xml .='<D:lockdiscovery>
- <D:activelock>
- <D:locktype><D:write/></D:locktype>
- <D:lockscope><D:exclusive/></D:lockscope>
- <D:depth>'.$lock['depth'].'</D:depth>
- <D:owner>'.$lock['owner'].'</D:owner>
- <D:timeout>'.$lock['timeout'].'</D:timeout>
- <D:locktoken>
- <D:href>'.$lock['token'].'</D:href>
- </D:locktoken>
- </D:activelock>
- </D:lockdiscovery>';
- }
-
- if(isset($fileInfos['length']))
- $xml.= '<D:getcontentlength>'.$fileInfos['length'].'</D:getcontentlength>';
- $xml .='
- <D:getetag>"'.sha1($fileInfos['update_time']).'"</D:getetag>
- <D:getlastmodified>'.$fileInfos['update_time'].'</D:getlastmodified>
- <D:creationdate>'.$fileInfos['create_time'].'</D:creationdate>
- </D:prop>
- <D:status>HTTP/1.1 200 OK</D:status>
- </D:propstat>';
-
- if(!isset($fileInfos['length'])){
- $xml .= '<D:propstat>
- <D:prop>
- <D:getcontentlength/>
- </D:prop>
- <D:status>HTTP/1.1 404 Not Found</D:status>
- </D:propstat>';
- }
-
- $xml .= '</D:response>';
- 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];
- }
- }
- ?>
|