openrat-cms

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

WebdavAction.class.php (34066B)


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