openrat-webdav

git clone http://git.code.weiherhei.de/openrat-webdav.git
Log | Files | Refs

WebDAV.class.php (32074B)


      1 <?php
      2 
      3 define('LB',"\n");
      4 class WebDAV
      5 {
      6 
      7 	// Zahlreiche Instanzvariablen, die im Konstruktor
      8 	// beim Zerlegen der Anfrag gef�llt werden.
      9 	public $database;
     10 	public $depth;
     11 	public $projectid;
     12 	public $request;
     13 	public $filename;
     14 	public $uri;
     15 	public $headers;
     16 	public $data;
     17 
     18 	public $destination = null;
     19 	public $sitePath;
     20 	public $create;
     21 	public $readonly;
     22 	public $maxFileSize;
     23 	public $webdav_conf;
     24 	public $overwrite = false;
     25 
     26 	private $httpMethod;
     27 
     28     /**
     29      * CMS-Client
     30      * @var CMS
     31      */
     32     private $client;
     33 
     34     const MEMKEY = 1;
     35     private $shm;
     36     private $store;
     37 
     38 
     39     /**
     40 	 * Im Kontruktor wird der Request analysiert und ggf. eine Authentifzierung
     41 	 * durchgefuehrt. Anschließend wird eine interne Methode mit dem Namen davXXX() aufgerufen.
     42 	 */
     43 	function __construct()
     44 	{
     45         // Dateitoken-Schlüssel ermitteln
     46         $key = ftok(__FILE__, 'a');
     47         // 0,5 MB
     48         $this->shm = shm_attach($key, 0.5 * 1000 * 1000, 0666);
     49 
     50         global $config;
     51 
     52         $this->httpMethod = strtoupper($_SERVER['REQUEST_METHOD']);
     53 
     54 		Logger::trace( 'WEBDAV request' );
     55 		
     56 		if	( $config['dav.compliant_to_redmond'] )
     57 			header('MS-Author-Via: DAV'           ); // Extrawurst fuer MS-Clients.
     58 			
     59 		if	( $config['dav.expose_openrat'] )
     60 			header('X-Dav-powered-by: OpenRat CMS'); // Bandbreite verschwenden :)
     61 
     62  		Logger::trace( 'WEBDAV: URI='.$_SERVER['REQUEST_URI']);
     63 		
     64 		if	( !$config['dav.enable'])
     65 		{
     66  			Logger::warn( 'WEBDAV is disabled by configuration' );
     67 		
     68 			$this->httpStatus('403 Forbidden');
     69 			exit;
     70 		}
     71 		
     72 		$this->create      = $config['dav.create'];
     73 		$this->readonly    = $config['dav.readonly'];
     74 		$this->maxFileSize = $config['cms.max_file_size'];
     75 		
     76 		$this->headers = getallheaders();
     77 		/* DAV compliant servers MUST support the "0", "1" and
     78 		 * "infinity" behaviors. By default, the PROPFIND method without a Depth
     79 		 * header MUST act as if a "Depth: infinity" header was included. */
     80 		if	( !isset($this->headers['Depth']) )
     81 			$this->depth = 1;
     82 		elseif	( strtolower($this->headers['Depth'])=='infinity')
     83 			$this->depth = 1;
     84 		else
     85 			$this->depth = intval($this->headers['Depth']);
     86 
     87 		if	( isset($this->headers['Destination']) )
     88 			$this->destination = $this->headers['Destination'];
     89 
     90 		$this->overwrite = @$this->headers['Overwrite'] == 'T';
     91 
     92 
     93         if	( @$config['dav.anonymous'])
     94             $username = @$config['cms.user'];
     95         else
     96             $username = @$_SERVER['PHP_AUTH_USER'];
     97 
     98         $this->username = $username;
     99 
    100         if   ( shm_has_var($this->shm, self::MEMKEY) )
    101 
    102             $this->store = shm_get_var($this->shm, self::MEMKEY);
    103         else
    104             $this->store = array();
    105 
    106         Logger::trace('Store at startup: '.LB.print_r($this->store,true) );
    107 
    108         if   ( is_array(@$this->store[$username]) )
    109             ;
    110         else
    111             $this->store[$username] = array();
    112 
    113         $this->client = new CMS();
    114         $this->client->client->cookies = $this->store[$username];
    115 
    116 		if	( $this->store[$username] )
    117 		{
    118 			// Es gibt Cookies. Also:
    119             // Benutzer ist bereits im CMS eingeloggt.
    120 		}
    121 		else
    122 		{
    123 			// Login
    124 			if	( $this->httpMethod != 'OPTIONS' ) // Bei OPTIONS kein Login anfordern
    125 			{
    126 				if	( isset($_SERVER['PHP_AUTH_USER']) )
    127 				{
    128 					
    129 					$username = $_SERVER['PHP_AUTH_USER'];
    130 					$pass     = $_SERVER['PHP_AUTH_PW'  ];
    131 					
    132 					try {
    133 						$this->client->login($username, $pass, $config['cms.database']);
    134 
    135                         $this->store[$username] = $this->client->client->cookies;
    136                         shm_put_var($this->shm,self::MEMKEY, $this->store);
    137 					}
    138 					catch( Exception $e )
    139 					{
    140 						$this->httpStatus('401 Unauthorized');
    141 						header('WWW-Authenticate: Basic realm="'.$config['dav.realm'].'"');
    142 						error_log( print_r($e->getMessage(),true) );
    143 						echo  'Failed login for user '.$username;
    144 						exit;
    145 					}
    146 				}
    147 				elseif	( $config['dav.anonymous'])
    148 				{
    149 					$username = $config['cms.user'];
    150 					$pass     = $config['cms.password'];
    151 					
    152 					$loginOk = $this->client->login($username, $pass, $config['cms.database']);
    153 					if	( !$loginOk ) {
    154 						$this->httpStatus('500 Internal Server Error');
    155 						echo 'Could not authenticate user '.$username;
    156 						exit;
    157 					}
    158 
    159 					$this->store[$username] = $this->client->client->cookies;
    160                     shm_put_var($this->shm,self::MEMKEY, $this->store);
    161                 }
    162 				else
    163 				{
    164 					// Client ist nicht angemeldet, daher wird nun die
    165 					// Authentisierung angefordert.
    166 					header('WWW-Authenticate: Basic realm="'.$config['dav.realm'].'"');
    167 					$this->httpStatus('401 Unauthorized');
    168 					echo  'Authentification required for '.$config['dav.realm'];
    169 					exit;
    170 					
    171 				}
    172 			}
    173 			else
    174 			{
    175 				return; // Bei OPTIONS müssen wir keine URL auswerten und können direkt zur Methode springen.
    176 			}
    177 		}
    178 		
    179 		
    180 		$this->sitePath = $this->siteURL().$_SERVER['PHP_SELF'];
    181 
    182 		// Path-Info. If not set, use '/'
    183         $pathInfo = @$_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO'] : '/';
    184 
    185         $this->request = new URIParser($this->client, $pathInfo);
    186 
    187         Logger::trace( $this->request->__toString() );
    188 
    189 		/*
    190 		 * Directories MUST end with a '/'. If not, redirect.
    191 		 * 
    192 		 * RFC 2518, 5.2 Collection Resources, Page 11:
    193 		 * "For example, if a client invokes a
    194 		 * method on http://foo.bar/blah (no trailing slash), the resource
    195 		 * http://foo.bar/blah/ (trailing slash) may respond as if the operation
    196 		 * were invoked on it, and should return a content-location header with
    197 		 * http://foo.bar/blah/ in it.  In general clients SHOULD use the "/"
    198 		 * form of collection names."
    199 		 */
    200 		if	( in_array($this->request->type,array('folder','root')) &&
    201 			  substr($this->sitePath,-1 ) != '/' )
    202 		    if   ( $config['dav.redirect_collections_to_trailing_slash'] )
    203             {
    204                 // redirect the collection to append the trailing slash.
    205                 // this is recommended by the spec (see above).
    206                 Logger::debug( 'Redirecting lame client to slashyfied folder URL' );
    207 
    208                 header('HTTP/1.1 302 Moved Temporarily');
    209                 header('Location: '.$this->sitePath.'/');
    210                 exit;
    211             }
    212             else
    213             {
    214                 // no redirect - so we append the trailing slash.
    215                 // this is allowed by the spec (see above).
    216                 $this->sitePath .= '/';
    217             }
    218 
    219 
    220 		// Falls vorhanden, den "Destination"-Header parsen.
    221 		if	( isset($_SERVER['HTTP_DESTINATION']) )
    222 		{
    223 			$destUri = parse_url( $_SERVER['HTTP_DESTINATION'] );
    224 			
    225 			$uri = substr($destUri['path'],strlen($_SERVER['SCRIPT_NAME'])+$sos);
    226 				
    227 			// URL parsen.
    228 			$this->destination = new URIParser( $this->client,$uri );
    229 		}
    230 
    231 		// Den Request-BODY aus der Standardeingabe lesen.
    232 		$this->data = implode('',file('php://input'));
    233 	}
    234 
    235 	/**
    236 	 * HTTP-Methode OPTIONS.<br>
    237 	 * <br>
    238 	 * Es werden die verfuegbaren Methoden ermittelt und ausgegeben.
    239 	 */
    240 	public function davOPTIONS()
    241 	{
    242 		header('DAV: 1'); // Wir haben DAV-Level 1.
    243 		header('Allow: '.implode(', ',$this->allowed_methods()) );
    244 		
    245 		Logger::trace('OPTIONS: '.'Allow: '.implode(', ',$this->allowed_methods()));
    246 
    247 		$this->httpStatus( '200 OK' );
    248 	}
    249 	
    250 	
    251 
    252 
    253 
    254 	/**
    255 	 * WebDav-HEAD-Methode.
    256 	 */	
    257 	public function davHEAD()
    258 	{
    259 		if	( ! $this->request->objectid )
    260 		{
    261 			$this->httpStatus( '404 Not Found' );
    262 		}
    263 		elseif	( $this->request->type == 'folder' )
    264 		{
    265 			$this->httpStatus( '200 OK' );
    266 		}
    267 		elseif( $this->obj->isPage )
    268 		{
    269 			$this->httpStatus( '200 OK' );
    270 		}
    271 		elseif( $this->obj->isLink )
    272 		{
    273 			$this->httpStatus( '200 OK' );
    274 		}
    275 		elseif( $this->obj->isFile )
    276 		{
    277 			$this->httpStatus( '200 OK' );
    278 		}
    279 	}
    280 	
    281 	
    282 	
    283 	/**
    284 	 * WebDav-GET-Methode.
    285 	 * Die gew�nschte Datei wird geladen und im HTTP-Body mitgeliefert.
    286 	 */	
    287 	public function davGET()
    288 	{
    289 		switch( $this->request->type )
    290 		{
    291 			case 'root':
    292 			case 'folder':
    293 				$this->getDirectoryIndex();
    294 				break;
    295 				
    296 			case 'page':
    297 				$this->httpStatus( '200 OK' );
    298 				header('Content-Type: text/html');
    299 			
    300 				echo '<html><head><title>OpenRat WEBDAV Access</title></head>';
    301 				echo '<body>';
    302 				echo '<h1>'.$this->request->type.'</h1>';
    303 				echo '<pre>';
    304 				echo 'No Content available';
    305 				echo '</pre>';
    306 				echo '</body>';
    307 				echo '</html>';
    308 				break;
    309 				
    310 			case 'link':
    311 				$this->httpStatus( '200 OK' );
    312 			
    313 				header('Content-Type: text/plain');
    314 				
    315 				$link = $this->client->link( $this->request->objectid );
    316 				echo 'url: '      .$link['url']           ."\n";
    317 				echo 'target-id: '.$link['linkedObjectId']."\n";
    318 				
    319 				break;
    320 				
    321 			case 'file':
    322 			case 'image':
    323 			case 'text':
    324 				$this->httpStatus( '200 OK' );
    325 				
    326 				$file      = $this->client->file     ( $this->request->objectid );
    327 				$filevalue = $this->client->filevalue( $this->request->objectid );
    328 				
    329 				header('Content-Type: '.$file['mimetype']);
    330 				header('X-File-Id: '   .$this->request->objectid );
    331 		
    332 				// Angabe Content-Disposition
    333 				// - Bild soll "inline" gezeigt werden
    334 				// - Dateiname wird benutzt, wenn der Browser das Bild speichern moechte
    335 				header('Content-Disposition: inline; filename='.$file['filename'].'.'.$file['extension'] );
    336 				header('Content-Transfer-Encoding: binary' );
    337 				header('Content-Description: '.$file['filename'].'.'.$file['extension'] );
    338 		
    339 				// Groesse des Bildes in Bytes
    340 				// Der Browser hat so die Moeglichkeit, einen Fortschrittsbalken zu zeigen
    341 				header('Content-Length: '.$file['size'] );
    342 				
    343 				echo $filevalue;
    344 				
    345 				break;
    346 
    347             default:
    348                 $this->httpStatus( '200 OK' );
    349                 header('Content-Type: text/html');
    350 
    351                 echo '<html><head><title>OpenRat WEBDAV Access</title></head>';
    352                 echo '<body>';
    353                 echo '<h1>'.$this->request->type.'</h1>';
    354                 echo '<pre>';
    355                 echo 'Unknown node type: '.$this->request->type;
    356                 echo '</pre>';
    357                 echo '</body>';
    358                 echo '</html>';
    359                 break;
    360 
    361 
    362         }
    363 	}
    364 	
    365 	
    366 	
    367 	/**
    368 	 * Erzeugt ein Unix-�hnliche Ausgabe des Verzeichnisses als HTML.
    369 	 */
    370 	private function getDirectoryIndex()
    371 	{
    372 		$this->httpStatus( '200 OK' );
    373 		
    374 		// Verzeichnis ausgeben
    375 		header('Content-Type: text/html');
    376 		$nl = "\n";
    377 		
    378 		
    379 		
    380 
    381 		$titel = 'Index of '.htmlspecialchars($this->sitePath);
    382 		$format = "%15s  %-19s  %-s\n";
    383 		
    384 		echo '<html><head><title>'.$titel.'</title></head>';
    385 		echo '<body>';
    386 		echo '<h1>'.$titel.'</h1>'.$nl;
    387 		echo '<pre>';
    388 		
    389 		printf($format, "Size", "Last modified", "Filename");
    390 		
    391 		
    392 		switch( $this->request->type )
    393 		{
    394 			case 'root':  // Projektliste
    395 		
    396 				$result = $this->client->projectlist();
    397 				$projects = $result['projects'];
    398 				foreach( $projects as $projectid=>$p )
    399 				{
    400 					echo '<a href="'.$p['name'].'">'.$p['name'].'</a>'.$nl;
    401 				}
    402 				break;						
    403 		
    404 			case 'folder':  // Verzeichnisinhalt
    405 		
    406 				$folder = $this->client->folder( $this->request->objectid );
    407 					
    408 				foreach( $folder['content']['object'] as $object )
    409 				{
    410 					
    411 					printf($format,
    412 						number_format(1),
    413 						strftime("%Y-%m-%d %H:%M:%S",$object['date'] ),
    414 						'<a href="./'.$object['filename'].'">'.$object['filename'].'</a>');
    415 					echo $nl;
    416 						
    417 				}
    418 		}		
    419 		
    420 		echo '</pre>';
    421 		echo '</body>';
    422 		echo '</html>';
    423 	}
    424 	
    425 	
    426 
    427 	/**
    428 	 * Die Methode LOCK sollte garnicht aufgerufen werden, da wir nur
    429 	 * Dav-Level 1 implementieren und dies dem Client auch mitteilen.<br>
    430 	 * <br>
    431 	 * Ausgabe von HTTP-Status 412 (Precondition failed)
    432 	 */	
    433 	function davLOCK()
    434 	{
    435 		$this->httpStatus('412 Precondition failed');
    436 		$this->davOPTIONS();
    437 	}
    438 
    439 
    440 		
    441 	/**
    442 	 * Die Methode UNLOCK sollte garnicht aufgerufen werden, da wir nur
    443 	 * Dav-Level 1 implementieren und dies dem Client auch mitteilen.<br>
    444 	 * <br>
    445 	 * Ausgabe von HTTP-Status 412 (Precondition failed)
    446 	 */	
    447 	public function davUNLOCK()
    448 	{
    449 		$this->httpStatus('412 Precondition failed');
    450 		$this->davOPTIONS();
    451 	}
    452 
    453 
    454 
    455 	/**
    456 	 * Die Methode POST ist bei WebDav nicht sinnvoll.<br>
    457 	 * <br>
    458 	 * Ausgabe von HTTP-Status 405 (Method Not Allowed)
    459 	 */	
    460 	public function davPOST()
    461 	{
    462 		// Die Methode POST ist bei Webdav nicht sinnvoll.
    463 		$this->httpStatus('405 Method Not Allowed' );
    464 	}
    465 	
    466 	
    467 	
    468 	/**
    469 	 * Verzeichnis anlegen.
    470 	 */		
    471 	public function davMKCOL()
    472 	{
    473 		
    474 		if	( !empty($this->data) )
    475 		{
    476 			$this->httpStatus('415 Unsupported Media Type' ); // Kein Body erlaubt
    477 		}
    478 		elseif	( $this->readonly )
    479 		{
    480 			$this->httpStatus('403 Forbidden' ); // Kein Schreibzugriff erlaubt
    481 		}
    482 		elseif  ( !$this->folder->hasRight( ACL_CREATE_FOLDER ) )
    483 		{
    484 			$this->httpStatus('403 Forbidden' ); // Benutzer darf das nicht
    485 		}
    486 		elseif	( $this->obj == null )
    487 		{
    488 			// Die URI ist noch nicht vorhanden
    489 			$f = new Folder();
    490 			$f->filename  = basename($this->sitePath);
    491 			$f->parentid  = $this->folder->objectid;
    492 			$f->projectid = $this->project->projectid;
    493 			$f->add();
    494 			$this->httpStatus('201 Created');
    495 		}
    496 		else
    497 		{
    498 			// MKCOL ist nicht moeglich, wenn die URI schon existiert.
    499 			Logger::warn('MKCOL-Request to an existing resource');
    500 			$this->httpStatus('405 Method Not Allowed' );
    501 		}
    502 	}
    503 
    504 
    505 		
    506 	/**
    507 	 * Objekt l�schen.
    508 	 */		
    509 	public function davDELETE()
    510 	{
    511 		if	( $this->readonly )
    512 		{
    513 			$this->httpStatus('403 Forbidden' ); // Kein Schreibzugriff erlaubt
    514 		}
    515 		else
    516 		{
    517 			if	( $this->obj == null )
    518 			{
    519 				// Nicht existente URIs kann man auch nicht loeschen.
    520 				$this->httpStatus('404 Not Found' );
    521 			}
    522 			elseif  ( ! $this->obj->hasRight( ACL_DELETE ) )
    523 			{
    524 				$this->httpStatus('403 Forbidden' ); // Benutzer darf die Resource nicht loeschen
    525 			}
    526 			elseif	( $this->obj->isFolder )
    527 			{
    528 				$f = new Folder( $this->obj->objectid );
    529 				$f->deleteAll();
    530 				$this->httpStatus( true ); // OK
    531 				Logger::debug('Deleted folder with id '.$this->obj->objectid );
    532 			}
    533 			elseif	( $this->obj->isFile )
    534 			{
    535 				$f = new File( $this->obj->objectid );
    536 				$f->delete();
    537 				$this->httpStatus( true ); // OK
    538 			}
    539 			elseif	( $this->obj->isPage )
    540 			{
    541 				$p = new Page( $this->obj->objectid );
    542 				$p->delete();
    543 				$this->httpStatus( true ); // OK
    544 			}
    545 			elseif	( $this->obj->isLink )
    546 			{
    547 				$l = new Link( $this->obj->objectid );
    548 				$l->delete();
    549 				$this->httpStatus( true ); // OK
    550 			}
    551 
    552 		}
    553 	}
    554 
    555 
    556 		
    557 	/**
    558 	 * Kopieren eines Objektes.<br>
    559 	 * Momentan ist nur das Kopieren einer Datei implementiert.<br>
    560 	 * Das Kopieren von Ordnern, Verkn�pfungen und Seiten ist nicht moeglich.
    561 	 */		
    562 	public function davCOPY()
    563 	{
    564 		if	( $this->readonly || !$this->create )
    565 		{
    566 			error_log('WEBDAV: COPY request, but readonly or no creating');
    567 			$this->httpStatus('405 Not Allowed' );
    568 		}
    569 		elseif( $this->obj == null )
    570 		{
    571 			// Was nicht da ist, laesst sich auch nicht verschieben.
    572 			error_log('WEBDAV: COPY request, but Source not found');
    573 			$this->httpStatus('405 Not Allowed' );
    574 		}
    575 		elseif ( $this->destination == null )
    576 		{
    577 			error_log('WEBDAV: COPY request, but no "Destination:"-Header');
    578 			// $this->httpStatus('405 Not Allowed' );
    579 			$this->httpStatus('412 Precondition failed');
    580 		}
    581 		else
    582 		{
    583 			// URL parsen.
    584 			$dest = $this->destination;
    585 			$destinationProject = $dest['project'];
    586 			$destinationFolder  = $dest['folder' ];
    587 			$destinationObject  = $dest['object' ];
    588 			
    589 			if	( $dest['type'] != 'object' )
    590 			{
    591 				Logger::debug('WEBDAV: COPY request, but "Destination:"-Header mismatch');
    592 				$this->httpStatus('405 Not Allowed');
    593 			}
    594 			elseif	( $this->project->projectid != $destinationProject->projectid )
    595 			{
    596 				// Kopieren in anderes Projekt nicht moeglich.
    597 				Logger::debug('WEBDAV: COPY request denied, project does not match');
    598 				$this->httpStatus('403 Forbidden');
    599 			}
    600 			elseif	( $destinationObject != null )
    601 			{
    602 				Logger::debug('WEBDAV: COPY request denied, Destination exists. Overwriting is not supported');
    603 				$this->httpStatus('403 Forbidden');
    604 			}
    605 			elseif ( is_object($destinationFolder) && ! $destinationFolder->hasRight( ACL_CREATE_FILE ) )
    606 			{
    607 				$this->httpStatus('403 Forbidden' ); // Benutzer darf das nicht
    608 			}
    609 			elseif ( is_object($destinationObject) && $destinationObject->isFolder)
    610 			{
    611 				Logger::debug('WEBDAV: COPY request denied, Folder-Copy not implemented');
    612 				$this->httpStatus('405 Not Allowed');
    613 			}
    614 			elseif ( is_object($destinationObject) && $destinationObject->isLink)
    615 			{
    616 				Logger::debug('WEBDAV: COPY request denied, Link copy not implemented');
    617 				$this->httpStatus('405 Not Allowed');
    618 			}
    619 			elseif ( is_object($destinationObject) && $destinationObject->isPage)
    620 			{
    621 				Logger::debug('WEBDAV: COPY request denied, Page copy not implemented');
    622 				$this->httpStatus('405 Not Allowed');
    623 			}
    624 			else
    625 			{
    626 				$f = new File();
    627 				$f->filename = basename($_SERVER['HTTP_DESTINATION']);
    628 				$f->name     = '';
    629 				$f->parentid = $destinationFolder->objectid;
    630 				$f->projectid = $this->project->projectid;
    631 				$f->add();
    632 				$f->copyValueFromFile( $this->obj->objectid );
    633 				
    634 				Logger::debug('WEBDAV: COPY request accepted' );
    635 				// Objekt wird in anderen Ordner kopiert.
    636 				$this->httpStatus('201 Created' );
    637 			}	
    638 		}
    639 
    640 	}
    641 
    642 
    643 		
    644 	/**
    645 	 * Verschieben eines Objektes.<br>
    646 	 * <br>
    647 	 * Folgende Operationen sind m�glich:<br>
    648 	 * - Unbenennen eines Objektes (alle Typen)<br> 
    649 	 * - Verschieben eines Objektes (alle Typen) in einen anderen Ordner.<br>
    650 	 */		
    651 	public function davMOVE()
    652 	{
    653 		if	( $this->readonly )
    654 		{
    655 			$this->httpStatus('403 Forbidden - Readonly Mode' ); // Schreibgeschuetzt
    656 		}
    657 		elseif	( !$this->create )
    658 		{
    659 			$this->httpStatus('403 Forbidden - No creation' ); // Schreibgeschuetzt
    660 		}
    661 		elseif( $this->obj == null )
    662 		{
    663 			// Was nicht da ist, laesst sich auch nicht verschieben.
    664 			$this->httpStatus('404 Not Found' );
    665 		}
    666 		elseif( is_object($this->obj) && ! $this->obj->hasRight( ACL_WRITE ) )
    667 		{
    668 			// Was nicht da ist, laesst sich auch nicht verschieben.
    669 			Logger::error('Source '.$this->obj->objectid.' is not writable: Forbidden');
    670 			$this->httpStatus('403 Forbidden' );
    671 		}
    672 		elseif ( $this->destination == null )
    673 		{
    674 			Logger::error('WEBDAV: MOVE request, but no "Destination:"-Header');
    675 			// $this->httpStatus('405 Not Allowed' );
    676 			$this->httpStatus('412 Precondition failed');
    677 		}
    678 		else
    679 		{
    680 			$dest = $this->destination;
    681 			$destinationProject = $dest['project'];
    682 			$destinationFolder  = $dest['folder' ];
    683 			$destinationObject  = $dest['object' ];
    684 
    685 			if	( $dest['type'] != 'object' )
    686 			{
    687 				Logger::debug('WEBDAV: MOVE request, but "Destination:"-Header mismatch');
    688 				$this->httpStatus('405 Not Allowed');
    689 				return;
    690 			}
    691 
    692 			if	( is_object($destinationFolder) && ! $destinationFolder->hasRight( ACL_CREATE_FILE ) )
    693 			{
    694 				Logger::error('Source '.$this->obj->objectid.' is not writable: Forbidden');
    695 				$this->httpStatus('403 Forbidden' );
    696 			}
    697 			
    698 			if	( $destinationObject != null )
    699 			{
    700 				Logger::debug('WEBDAV: MOVE request denied, destination exists');
    701 				$this->httpStatus('412 Precondition Failed');
    702 				return;
    703 			}
    704 			
    705 			if	( $this->project->projectid != $destinationProject->projectid )
    706 			{
    707 				// Verschieben in anderes Projekt nicht moeglich.
    708 				Logger::debug('WEBDAV: MOVE request denied, project does not match');
    709 				$this->httpStatus('405 Not Allowed');
    710 				return;
    711 			}
    712 			
    713 			if	( $this->folder->objectid == $destinationFolder->objectid )
    714 			{
    715 				Logger::debug('WEBDAV: MOVE request accepted, object renamed');
    716 				// Resource bleibt in gleichem Ordner.
    717 				$this->obj->filename = basename($_SERVER['HTTP_DESTINATION']);
    718 				$this->obj->objectSave(false);
    719 				$this->httpStatus('201 Created' );
    720 				return;
    721 			}
    722 			
    723 			if	( $destinationFolder->isFolder )
    724 			{
    725 				Logger::debug('WEBDAV: MOVE request accepted, Destination: '.$destinationFolder->filename );
    726 				// Objekt wird in anderen Ordner verschoben.
    727 				$this->obj->setParentId( $destinationFolder->objectid );
    728 				$this->httpStatus('201 Created' );
    729 				return;
    730 			}
    731 			
    732 			Logger::warn('WEBDAV: MOVE request failed' );
    733 			$this->httpStatus('500 Internal Server Error' );
    734 		}
    735 	}
    736 
    737 
    738 		
    739 	/**
    740 	 * Anlegen oder �berschreiben Dateien �ber PUT.<br>
    741 	 * Dateien k�nnen neu angelegt und �berschrieben werden.<br>
    742 	 * <br>
    743 	 * Seiten k�nnen nicht �berschrieben werden. Wird versucht,
    744 	 * eine Seite mit PUT zu �berschreiben, wird der Status "405 Not Allowed" gemeldet.<br>
    745 	 */		
    746 	public function davPUT()
    747 	{
    748 		// TODO: 409 (Conflict) wenn übergeordneter Ordner nicht da.
    749 
    750 		if	( $this->readonly )
    751 		{
    752 			$this->httpStatus('405 Not Allowed' );
    753 		}		
    754 		elseif	( strlen($this->data) > $this->maxFileSize*1000 )
    755 		{
    756 			// Maximale Dateigroesse ueberschritten.
    757 			// Der Status 207 "Zuwenig Speicherplatz" passt nicht ganz, aber fast :)
    758 			$this->httpStatus('507 Insufficient Storage' );
    759 		}
    760 		elseif	( ! $this->request->objectid )
    761 		{
    762 			// Neue Datei anlegen
    763 			if	( !$this->create )
    764 			{
    765 				Logger::warn('WEBDAV: Creation of files not allowed by configuration' );
    766 				$this->httpStatus('405 Not Allowed' );
    767 			}
    768 			
    769 			$this->client->fileAdd( $this->data );
    770 			$this->httpStatus('201 Created');
    771 			return;
    772 		}
    773 		elseif	( $this->request->objectid )
    774 		{
    775 			// Bestehende Datei ueberschreiben.
    776 			$id = $this->request->objectid;
    777             $this->client->fileAdd( $id,$this->data );
    778 
    779 			$this->httpStatus('204 No Content');
    780 			return;
    781 		}
    782 		elseif	( $this->obj->isFolder )
    783 		{
    784 			Logger::error('PUT on folder is not supported, use PROPFIND. Lame client?' );
    785 			$this->httpStatus('405 Not Allowed' );
    786 		}
    787 		else
    788 		{
    789 			// Fuer andere Objekttypen (Links, Seiten) ist kein PUT moeglich.
    790 			Logger::warn('PUT only available for files. Pages and links are ignored' );
    791 			$this->httpStatus('405 Not Allowed' );
    792 		}
    793 	}
    794 	
    795 	
    796 
    797 	/**
    798 	 * WebDav-Methode PROPFIND.
    799 	 * 
    800 	 * Diese Methode wird
    801 	 * - beim Ermitteln von Verzeichnisinhalten und
    802 	 * - beim Ermitteln von Metainformationen zu einer Datei
    803 	 * verwendet.
    804 	 * 
    805 	 * Das Ergebnis wird in einer XML-Zeichenkette geliefert.
    806 	 */	
    807 	public function davPROPFIND()
    808 	{
    809 		switch( $this->request->type )
    810 		{
    811 			case 'root':  // Projektliste
    812 				
    813 				$inhalte = array();
    814 				
    815 				$objektinhalt = array();
    816 				$z = 30*365.25*24*60*60;
    817 				$objektinhalt['createdate'    ] = $z;
    818 				$objektinhalt['lastchangedate'] = $z;
    819 				$objektinhalt['size'          ] = 1;
    820 				$objektinhalt['name'          ] = $this->sitePath;
    821 				$objektinhalt['displayname'   ] = '';
    822 				$objektinhalt['type']           = 'folder';
    823 
    824 				$inhalte[] = $objektinhalt;
    825 
    826 				try {
    827                     $result = $this->client->projectlist();
    828                 } catch( Exception $e) {
    829 				    Logger::error($e->__toString().$this->client->__toString());
    830 				    throw $e;
    831                 }
    832 				$projects = $result['projects'];
    833 				foreach( $projects as $projectid=>$p )
    834 				{
    835 					$objektinhalt = array();
    836 					$objektinhalt['createdate'    ] = TIME_20000101;
    837 					$objektinhalt['lastchangedate'] = TIME_20000101;
    838 					$objektinhalt['size'          ] = 1;
    839 					$objektinhalt['name'          ] = $this->sitePath.$p['name'].'/';
    840 					$objektinhalt['displayname'   ] = $p['name'];
    841 					$objektinhalt['type']           = 'folder';
    842 					$inhalte[] = $objektinhalt;
    843 				}
    844 					
    845 				$this->multiStatus( $inhalte );
    846 				break;
    847 
    848 			case 'folder':  // Verzeichnisinhalt
    849 
    850 				$folder = $this->client->folder( $this->request->objectid );
    851 
    852 				$inhalte = array();
    853 
    854 				$objektinhalt = array();
    855 				$objektinhalt['createdate'    ] = $folder['properties']['create_date'];
    856 				$objektinhalt['lastchangedate'] = $folder['properties']['lastchange_date'];
    857 				$objektinhalt['name'          ] = $this->sitePath;
    858 				$objektinhalt['displayname'   ] = $folder['properties']['filename'];
    859 				$objektinhalt['type'          ] = 'folder';
    860 				$objektinhalt['size'          ] = 0;
    861 
    862 				$inhalte[] = $objektinhalt;
    863 					
    864 				if	( $this->depth > 0 )
    865 				{
    866 					
    867 					foreach( $folder['content']['object'] as $object )
    868 					{
    869 						$objektinhalt = array();
    870 						$objektinhalt['createdate'    ] = $object['date'];
    871 						$objektinhalt['lastchangedate'] = $object['date'];
    872 						$objektinhalt['displayname'   ] = $object['filename'];
    873 
    874 						switch( $object['type'] )
    875 						{
    876 							case 'folder':
    877 								$objektinhalt['name'] = $this->sitePath.$object['filename'].'/';
    878 								$objektinhalt['type'] = 'folder';
    879 								$objektinhalt['size'] = 0;
    880 								$inhalte[] = $objektinhalt;
    881 								break;
    882 							case 'file':
    883 							case 'image':
    884 							case 'text':
    885 								$objektinhalt['name'] = $this->sitePath.$object['filename'];
    886 								$objektinhalt['type'] = 'file';
    887 								$objektinhalt['size'] = $object['size'];
    888 								$objektinhalt['mime'] = 'application/x-non-readable';
    889 								$inhalte[] = $objektinhalt;
    890 								break;
    891 							case 'link':
    892 								$objektinhalt['name'] = $this->sitePath.$object['filename'];
    893 								$objektinhalt['type'] = 'file';
    894 								$objektinhalt['size'] = 0;
    895 								$objektinhalt['mime'] = 'application/x-non-readable';
    896 								$inhalte[] = $objektinhalt;
    897 								break;
    898 							case 'url':
    899 							case 'alias':
    900 								$objektinhalt['name'] = $this->sitePath.$object['filename'];
    901 								$objektinhalt['type'] = 'file';
    902 								$objektinhalt['size'] = 0;
    903 								$objektinhalt['mime'] = 'application/x-non-readable';
    904 								$inhalte[] = $objektinhalt;
    905 								break;
    906 							case 'page':
    907 								$objektinhalt['name'] = $this->sitePath.$object['filename'];
    908 								$objektinhalt['type'] = 'file';
    909 								$objektinhalt['size'] = 0;
    910 								$inhalte[] = $objektinhalt;
    911 								break;
    912 							default:
    913 						}
    914 					}
    915 				}
    916 				$this->multiStatus( $inhalte );
    917 				break;
    918 				
    919 			case 'page':
    920 				$page = $this->client->page( $this->request->objectid );
    921 				$prop = $page['properties'];
    922 				$objektinhalt = array();
    923 				$objektinhalt['name']           = $this->sitePath;
    924 				$objektinhalt['displayname']    = $prop['filename'];
    925 				$objektinhalt['createdate'    ] = $prop['date'];
    926 				$objektinhalt['lastchangedate'] = $prop['date'];
    927 				
    928 				$objektinhalt['size'          ] = 0;
    929 				$objektinhalt['type'          ] = 'file';
    930 
    931                 $this->multiStatus( array($objektinhalt) );
    932 
    933                 break;
    934 				
    935 			case 'file':
    936 			case 'text':
    937 			case 'image':
    938 				$file = $this->client->file( $this->request->objectid );
    939 				$objektinhalt = array();
    940 				$objektinhalt['name']           = $this->sitePath;
    941 				$objektinhalt['displayname']    = $file['filename'];
    942 				$objektinhalt['createdate'    ] = $file['date'];
    943 				$objektinhalt['lastchangedate'] = $file['date'];
    944 				
    945 				$objektinhalt['size'          ] = $file['size'];
    946 				$objektinhalt['type'          ] = 'file';
    947 
    948                 $this->multiStatus( array($objektinhalt) );
    949 
    950             break;
    951 				
    952 			case 'link':
    953 				
    954 				$link = $this->client->link( $this->request->objectid );
    955 				
    956 				$objektinhalt = array();
    957 				$objektinhalt['name']           = $this->sitePath;
    958 				$objektinhalt['displayname']    = $link['filename'];
    959 				$objektinhalt['createdate'    ] = $link['date'];
    960 				$objektinhalt['lastchangedate'] = $link['date'];
    961 
    962 				$objektinhalt['size'          ] = 0;
    963 				$objektinhalt['type'          ] = 'file';
    964 				
    965 				
    966 				$this->multiStatus( array($objektinhalt) );
    967 				
    968 				break;
    969 				
    970 			case 'url':
    971 
    972 				$link = $this->client->link( $this->request->objectid );
    973 
    974 				$objektinhalt = array();
    975 				$objektinhalt['name']           = $this->sitePath;
    976 				$objektinhalt['displayname']    = $link['filename'];
    977 				$objektinhalt['createdate'    ] = $link['date'];
    978 				$objektinhalt['lastchangedate'] = $link['date'];
    979 
    980 				$objektinhalt['size'          ] = 0;
    981 				$objektinhalt['type'          ] = 'file';
    982 
    983 
    984 				$this->multiStatus( array($objektinhalt) );
    985 
    986 				break;
    987 
    988 			default:
    989 				Logger::warn('Internal Error, unknown request type: '. $this->request->type);
    990 				$this->httpStatus('500 Internal Server Error');
    991 		}
    992 	}
    993 	
    994 	
    995 	/**
    996 	 * Webdav-Methode PROPPATCH ist nicht implementiert.
    997 	 */
    998 	public function davPROPPATCH()
    999 	{
   1000 		// TODO: Multistatus erzeugen.
   1001 		// Evtl. ist '409 Conflict' besser?
   1002 		$this->httpStatus('405 Not Allowed');
   1003 	}
   1004 	
   1005 	
   1006 	/**
   1007 	 * Erzeugt einen Multi-Status.
   1008 	 * @access private
   1009 	 */
   1010 	private function multiStatus( $files )
   1011 	{
   1012 		$this->httpStatus('207 Multi-Status');
   1013 		header('Content-Type: text/xml; charset=utf-8');
   1014 		
   1015 		$response = '';
   1016 		$response .= '<?xml version="1.0" encoding="utf-8" ?>';
   1017 		$response .= '<d:multistatus xmlns:d="DAV:">';
   1018 
   1019 		foreach( $files as $file )
   1020 			$response .= $this->getResponse( $file['name'],$file );
   1021 		
   1022 		$response .= '</d:multistatus>';
   1023 
   1024 		$response = utf8_encode($response);
   1025 
   1026 		header('Content-Length: '.strlen($response));
   1027  		Logger::trace('Sending Multistatus:'."\n".$response);
   1028 		echo $response;
   1029 	}
   1030 	
   1031 	
   1032 	/**
   1033 	 * Erzeugt ein "response"-Element, welches in ein "multistatus"-element verwendet werden kann.
   1034 	 */
   1035 	private function getResponse( $file,$options )
   1036 	{
   1037 		// TODO: Nur angeforderte Elemente erzeugen.
   1038 		$response = '';
   1039 		$response .= '<d:response>';
   1040 		$response .= '<d:href>'.$file.'</d:href>';
   1041 		$response .= '<d:propstat>';
   1042 		$response .= '<d:prop>';
   1043 		//		$response .= '<d:source></d:source>';
   1044 		$response .= '<d:creationdate>'.date('r',$options['createdate']).'</d:creationdate>';
   1045 		$response .= '<d:displayname>'.$options['displayname'].'</d:displayname>';
   1046 		$response .= '<d:getcontentlength>'.$options['size'].'</d:getcontentlength>';
   1047 		$response .= '<d:getlastmodified xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">'.date('r',$options['lastchangedate']).'</d:getlastmodified>';
   1048 		
   1049 		if	( $options['type'] == 'folder')
   1050 			$response .= '<d:resourcetype><d:collection/></d:resourcetype>';
   1051 		else
   1052 			$response .= '<d:resourcetype />';
   1053 			
   1054 		$response .= '<d:categories />';
   1055 		$response .= '<d:fields></d:fields>';
   1056 		
   1057 		
   1058 		
   1059 //		$response .= '<d:getcontenttype>text/html</d:getcontenttype>';
   1060 //		$response .= '<d:getcontentlength />';
   1061 //		$response .= '<d:getcontentlanguage />';
   1062 //		$response .= '<d:executable />';
   1063 //		$response .= '<d:resourcetype>';
   1064 //		$response .= '<d:collection />';
   1065 //		$response .= '</d:resourcetype>';
   1066 //		$response .= '<d:getetag />';
   1067 
   1068 		$response .= '</d:prop>';
   1069 		$response .= '<d:status>HTTP/1.1 200 OK</d:status>';
   1070 		$response .= '</d:propstat>';
   1071 		$response .= '</d:response>';
   1072 
   1073 		return $response;		
   1074 	}
   1075 	
   1076 	
   1077 	
   1078     /**
   1079      * Setzt einen HTTP-Status.<br>
   1080      * <br>
   1081      * Es wird ein HTTP-Status gesetzt, zus�tzlich wird der Status in den Header "X-WebDAV-Status" geschrieben.<br>
   1082      * Ist der Status nicht 200 oder 207 (hier folgt ein BODY), wird das Skript beendet.
   1083      */
   1084     protected function httpStatus( $status = true )
   1085     {
   1086         if	( $status === true )
   1087             $status = '200 OK';
   1088 
   1089         // 		Logger::debug('WEBDAV: HTTP-Status: '.$status);
   1090 
   1091         header('HTTP/1.1 '.$status);
   1092         header('X-WebDAV-Status: '.$status,true);
   1093 
   1094         // RFC 2616 (HTTP/1.1), Section 10.4.6 "405 Method Not Allowed" says:
   1095         //   "[...] The response MUST include an
   1096         //    Allow header containing a list of valid methods for the requested
   1097         //    resource."
   1098         //
   1099         // RFC 2616 (HTTP/1.1), Section 14.7 "Allow" says:
   1100         //   "[...] An Allow header field MUST be
   1101         //     present in a 405 (Method Not Allowed) response."
   1102         if	( substr($status,0,3) == '405' )
   1103             header('Allow: '.implode(', ',$this->allowed_methods()) );
   1104     }
   1105 
   1106 
   1107 
   1108 
   1109     private function allowed_methods()
   1110     {
   1111 
   1112         if	 ($this->readonly)
   1113             return array('OPTIONS','HEAD','GET','PROPFIND');  // Readonly-Modus
   1114         else
   1115             // PROPPATCH unterstuetzen wir garnicht, aber lt. Spec sollten wir das.
   1116             return array('OPTIONS','HEAD','GET','PROPFIND','DELETE','PUT','COPY','MOVE','MKCOL','PROPPATCH');
   1117     }
   1118 
   1119 
   1120 
   1121     private function siteURL()
   1122     {
   1123         $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
   1124         $domainName = $_SERVER['HTTP_HOST'];
   1125         return $protocol.$domainName;
   1126     }
   1127 
   1128     private function slashify( $path )
   1129     {
   1130         return $path.( substr( $path,-1 ) != '/' )?'/':'';
   1131     }
   1132 
   1133     public function __destruct()
   1134     {
   1135         $this->store[$this->username] = $this->client->client->cookies;
   1136         shm_put_var($this->shm,self::MEMKEY,$this->store);
   1137     }
   1138 }