openrat-cms

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

WebdavAction.class.php (33954B)


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