openrat-cms

# OpenRat Content Management System
git clone http://git.code.weiherhei.de/openrat-cms.git
Log | Files | Refs

WebDAV.class.php (31751B)


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