Location: PHPKode > projects > OpenRat CMS > openrat/actionClasses/WebdavAction.class.php
<?php


/**
 * Action-Klasse fuer WebDAV.<br>
 * 
 * Das virtuelle Ordnersystem dieses CMS kann über das WebDAV-Protokoll
 * dargestellt werden.
 * 
 * Diese Klasse nimmt die Anfragen von WebDAV-Clients entgegen, zerlegt die
 * Anfrage und erzeugt eine Antwort, die im HTTP-Body zurück übertragen
 * wird.
 * <br>
 * WebDAV ist spezifiziert in der RFC 2518.<br>
 * Siehe <code>http://www.ietf.org/rfc/rfc2518.txt</code><br>
 * 
 * Implementiert wird DAV-Level 1 (d.h. ohne LOCK).
 * 
 * @author Jan Dankert
 * @package openrat.actions
 */

class WebdavAction extends Action
{
	// Zahlreiche Instanzvariablen, die im Konstruktor
	// beim Zerlegen der Anfrag gefüllt werden.
	var $defaultSubAction = 'show';
	var $database;
	var $depth;
	var $project;
	var $folder;
	var $obj;
	var $filename;
	var $pathnames = array();
	var $uri;
	var $headers;
	var $requestType;
	var $request;
	var $destination = null;
	var $fullSkriptName;
	var $create;
	var $readonly;
	var $maxFileSize;
	var $webdav_conf;
	var $overwrite = false;
	
	
	/**
	 * Im Kontruktor wird der Request analysiert und ggf. eine Authentifzierung
	 * durchgefuehrt.
	 */
	function WebdavAction()
	{
		if (!defined('E_STRICT')) 
			define('E_STRICT', 2048);

		// Nicht notwendig, da wir den Error-Handler umbiegen:
		error_reporting(0); // PHP-Fehlermeldungen zerstoeren XML-Dokument, daher ausschalten.
		
		// PHP-Fehler ins Log schreiben, damit die Ausgabe nicht zerstoert wird.
		if (version_compare(PHP_VERSION, '5.0.0', '>'))
			set_error_handler('webdavErrorHandler',E_ERROR | E_WARNING);
		else
			set_error_handler('webdavErrorHandler');

		global $conf;
		$this->webdav_conf = $conf['webdav'];

		if	( $this->webdav_conf['compliant_to_redmond'] )
			header('MS-Author-Via: DAV'           ); // Extrawurst fuer MS-Clients.
			
		if	( $this->webdav_conf['expose_openrat'] )
			header('X-Dav-powered-by: OpenRat CMS'); // Bandbreite verschwenden :)

		Logger::trace( 'WEBDAV: URI='.$_SERVER['REQUEST_URI']);
		
		if	( !$conf['webdav']['enable'])
		{
			Logger::warn( 'WEBDAV is disabled by configuration' );
			$this->httpStatus('403 Forbidden');
			exit;
		}
		
		$this->create      = $this->webdav_conf['create'];
		$this->readonly    = $this->webdav_conf['readonly'];
		$this->maxFileSize = $this->webdav_conf['max_file_size'];
		
		Logger::debug( 'WEBDAV method is '.$_GET['subaction'] );
		
		$this->headers = getallheaders();
		/* DAV compliant servers MUST support the "0", "1" and
		 * "infinity" behaviors. By default, the PROPFIND method without a Depth
		 * header MUST act as if a "Depth: infinity" header was included. */
		if	( !isset($this->headers['Depth']) )
			$this->depth = 1;
		elseif	( strtolower($this->headers['Depth'])=='infinity')
			$this->depth = 1;
		else
			$this->depth = intval($this->headers['Depth']);

		if	( isset($this->headers['Destination']) )
			$this->destination = $this->headers['Destination'];

		if	( isset($this->headers['Overwrite']) )
			$this->overwrite = $this->headers['Overwrite'] == 'T';
			
		// Pr�fen, ob Benutzer angemeldet ist.
		$user = $this->getUserFromSession();

		// Authentisierung erzwingen (außer bei Methode OPTIONS).
        // For the motivation for not checking OPTIONS requests see 
        // http://pear.php.net/bugs/bug.php?id=5363
		if	( !is_object($user) && $_GET[REQ_PARAM_SUBACTION] != 'options' )
		{
			Logger::debug( 'Checking Authentication' );
			
			if	( !is_object(Session::getDatabase()) )
				$this->setDefaultDb();
				
			$ok = false;
			if	( isset($_SERVER['PHP_AUTH_USER']) )
			{
				$user = new User();
				$user->name = $_SERVER['PHP_AUTH_USER'];
				
				$ok = $user->checkPassword( $_SERVER['PHP_AUTH_PW'] );
				
				if	( $ok )
				{
					$user->load();
					$user->setCurrent();
					$this->redirectWithSessionId();
				}
			}
			
			if	( !$ok )
			{
				// Client ist nicht angemeldet, daher wird nun die
				// Authentisierung angefordert.
				Logger::debug( 'Requesting Client to authenticate' );
				header('WWW-Authenticate: Basic realm="'.OR_TITLE.'"');
				$this->httpStatus('401 Unauthorized');
				exit;
			}
		}
		elseif	( !is_object($user) && $_GET[REQ_PARAM_SUBACTION] == 'options' )
		{
			$this->setDefaultDb();
		}
		
		
		$this->fullSkriptName = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME'].'/';	

		if	( $this->webdav_conf['session_in_uri'] )
			$sos = 1+strlen(session_id())+strlen($this->webdav_conf['session_in_uri_prefix']);
		else
			$sos = 0;
			
		// URL parsen.
		$uri = substr($_SERVER['REQUEST_URI'],strlen($_SERVER['SCRIPT_NAME']) + $sos);

		Logger::debug( 'WebDAV: URI="'.$uri.'"' );
			
		$uri = $this->parseURI( $uri );
		$this->requestType = $uri['type'   ];
		$this->folder      = $uri['folder' ];
		$this->obj         = $uri['object' ];
		$this->project     = $uri['project'];

		$this->fullSkriptName .= implode('/',$uri['path']);
		
		if	( is_object($this->obj) && $this->obj->isFolder )	
			$this->fullSkriptName .= '/';	

		/*
		 * Verzeichnisse muessen mit einem '/' enden. Falls nicht, Redirect aussfuehren.
		 * 
		 * RFC 2518, 5.2 Collection Resources, Page 11:
		 * "For example, if a client invokes a
		 * method on http://foo.bar/blah (no trailing slash), the resource
		 * http://foo.bar/blah/ (trailing slash) may respond as if the operation
		 * were invoked on it, and should return a content-location header with
		 * http://foo.bar/blah/ in it.  In general clients SHOULD use the "/"
		 * form of collection names."
		 */
		if	( is_object($this->obj)       &&
			  $this->obj->isFolder        &&
			  $_GET['subaction'] == 'get' &&
			  substr($_SERVER['REQUEST_URI'],strlen($_SERVER['REQUEST_URI'])-1 ) != '/' )
		{
			Logger::debug( 'WebDAV: Redirecting lame client to slashyfied URL' );
			
			header('HTTP/1.1 302 Moved Temporarily');
			header('Location: '.$_SERVER['REQUEST_URI'].'/');
			exit;	
		}	

		// Falls vorhanden, den "Destination"-Header parsen.
		if	( isset($_SERVER['HTTP_DESTINATION']) )
		{
			$destUri = parse_url( $_SERVER['HTTP_DESTINATION'] );
			
			$uri = substr($destUri['path'],strlen($_SERVER['SCRIPT_NAME'])+$sos);
				
			// URL parsen.
			$this->destination = $this->parseURI( $uri );
		}

		// Den Request-BODY aus der Standardeingabe lesen.
		$this->request = implode('',file('php://input')); 
	}
	
	

	/**
	 * Falls ein WebDAV-Client keine Cookies setzen kann (was HTTP/1.1 eigentlich
	 * der Fall sein sollte), kann die Session-Id in die URL eingetragen
	 * werden. Dies muss in der Konfiguration aktiviert werden.
	 */
	function redirectWithSessionId()
	{
		if	( $this->webdav_conf['session_in_uri'] )
		{
			header('Location: '.dirname($_SERVER['REQUEST_URI']).'/'. $this->webdav_conf['session_in_uri_prefix'].session_id().'/'.basename($_SERVER['REQUEST_URI']));
			//$this->httpStatus('303 See Other');
			$this->httpStatus('302 Moved');
		}
	}
	
	
	
	/**
	 * Da im WebDAV-Request keine Datenbank-Id angegeben werden kann, benutzen
	 * wir hier die Standard-Datenbank.
	 */
	function setDefaultDb()
	{
		global $conf;
		
		if	( !isset($conf['database']['default']) )
		{
			Logger::error('No default database in configuration');
			$this->httpStatus('500 Internal Server Error - no default-database in configuration');
		}

		$dbid = $conf['database']['default'];

		$db = new DB( $conf['database'][$dbid] );
		$db->id = $dbid;
		Session::setDatabase( $db );
	}

	
	
	function allowed_methods()
	{
		
		if	 ($this->readonly)
			return array('OPTIONS','HEAD','GET','PROPFIND');  // Readonly-Modus
		else
			// PROPPATCH unterstuetzen wir garnicht, aber lt. Spec sollten wir das.
			return array('OPTIONS','HEAD','GET','PROPFIND','DELETE','PUT','COPY','MOVE','MKCOL','PROPPATCH');
	}
	
	
	
	/**
	 * HTTP-Methode OPTIONS.<br>
	 * <br>
	 * Es werden die verfuegbaren Methoden ermittelt und ausgegeben.
	 */
	function options()
	{
		header('DAV: 1'); // Wir haben DAV-Level 1.
		header('Allow: '.implode(', ',$this->allowed_methods()) );

		$this->httpStatus( '200 OK' );
	}
	
	

	/**
	 * Setzt einen HTTP-Status.<br>
	 * <br>
	 * Es wird ein HTTP-Status gesetzt, zus�tzlich wird der Status in den Header "X-WebDAV-Status" geschrieben.<br>
	 * Ist der Status nicht 200 oder 207 (hier folgt ein BODY), wird das Skript beendet.
	 */	
	function httpStatus( $status = true )
	{
		if	( $status === true )
			$status = '200 OK';
			
		Logger::debug('WEBDAV: HTTP-Status: '.$status);
		
		header('HTTP/1.1 '.$status);
		header('X-WebDAV-Status: '.$status,true);

		// RFC 2616 (HTTP/1.1), Section 10.4.6 "405 Method Not Allowed" says:
		//   "[...] The response MUST include an
		//    Allow header containing a list of valid methods for the requested
		//    resource."
		// 
		// RFC 2616 (HTTP/1.1), Section 14.7 "Allow" says:
		//   "[...] An Allow header field MUST be
		//     present in a 405 (Method Not Allowed) response."
		if	( substr($status,0,3) == '405' )
			header('Allow: '.implode(', ',$this->allowed_methods()) );
	}
	
	

	/**
	 * WebDav-HEAD-Methode.
	 */	
	function head()
	{
		if	( $this->obj == null )
		{
			$this->httpStatus( '404 Not Found' );
		}
		elseif	( $this->obj->isFolder )
		{
			$this->httpStatus( '200 OK' );
		}
		elseif( $this->obj->isPage )
		{
			$this->httpStatus( '200 OK' );
		}
		elseif( $this->obj->isLink )
		{
			$this->httpStatus( '200 OK' );
		}
		elseif( $this->obj->isFile )
		{
			$this->httpStatus( '200 OK' );
		}
	}
	
	
	
	/**
	 * WebDav-GET-Methode.
	 * Die gew�nschte Datei wird geladen und im HTTP-Body mitgeliefert.
	 */	
	function get()
	{
		if	( $this->obj->isFolder )
			$this->getDirectory();
		elseif( $this->obj->isPage )
		{
			$this->httpStatus( '200 OK' );
			
			header('Content-Type: text/html');
			
			$page = new Page( $this->obj->objectid );
			$page->load();
			echo '<html><head><title>OpenRat WEBDAV Access</title></head>';
			echo '<body>';
			echo '<h1>'.$page->full_filename().'</h1>';
			echo '<pre>';
			echo 'No Content available';
			echo '</pre>';
			echo '</body>';
			echo '</html>';
		}
		elseif( $this->obj->isLink )
		{
			$this->httpStatus( '200 OK' );
			
			header('Content-Type: text/plain');
			
			$link = new Link( $this->obj->objectid );
			$link->load();
			echo 'url: '      .$link->url           ."\n";
			echo 'target-id: '.$link->linkedObjectId."\n";
		}
		elseif( $this->obj->isFile )
		{
			$this->httpStatus( '200 OK' );
			
			$file = new File( $this->obj->objectid );
			$file->load();
			
			header('Content-Type: '.$file->mimeType() );
			header('X-File-Id: '.$file->fileid );
	
			// Angabe Content-Disposition
			// - Bild soll "inline" gezeigt werden
			// - Dateiname wird benutzt, wenn der Browser das Bild speichern moechte
			header('Content-Disposition: inline; filename='.$file->filenameWithExtension() );
			header('Content-Transfer-Encoding: binary' );
			header('Content-Description: '.$file->name );
	
			$file->write(); // Bild aus Datenbank laden und in temporäre Datei schreiben

			// Groesse des Bildes in Bytes
			// Der Browser hat so die Moeglichkeit, einen Fortschrittsbalken zu zeigen
			header('Content-Length: '.filesize($file->tmpfile()) );
			readfile( $file->tmpfile() );
		}
	}
	
	
	
	/**
	 * Erzeugt ein Unix-�hnliche Ausgabe des Verzeichnisses als HTML.
	 */
	function getDirectory()
	{
		$this->httpStatus( '200 OK' );
		
		// Verzeichnis ausgeben
		header('Content-Type: text/html');
		$nl = "\n";
		$titel = 'Index of '.htmlspecialchars($this->fullSkriptName);
        $format = "%15s  %-19s  %-s\n";
		
		echo '<html><head><title>'.$titel.'</title></head>';
		echo '<body>';
        echo '<h1>'.$titel.'</h1>'.$nl;
		echo '<pre>';

        printf($format, "Size", "Last modified", "Filename");

		if	( $this->requestType == 'projectlist' )
		{
			foreach( Project::getAll() as $projectName )
			{
				$objektinhalt = array();
				$z = 30*365.25*24*60*60;
				$objektinhalt['createdate'    ] = $z;
				$objektinhalt['lastchangedate'] = $z;
				$objektinhalt['size'          ] = 1;
				echo '<a href="'.$this->fullSkriptName.'/'.$projectName.'">   </a>';
			}
		}
		elseif( $this->requestType == 'object' )  // Verzeichnisinhalt
		{
			$objects = $this->folder->getObjects();

			foreach( $objects as $object  )
			{
				printf($format, 
				       number_format(1),
				       strftime("%Y-%m-%d %H:%M:%S",$object->lastchangeDate ),
				       '<a href="'.$object->filename.'">'.$object->filename.'</a>');
				echo $nl;
			}
		}
		
		echo '</pre>';
		echo '</body>';
		echo '</html>';
	}
	
	

	/**
	 * Die Methode LOCK sollte garnicht aufgerufen werden, da wir nur
	 * Dav-Level 1 implementieren und dies dem Client auch mitteilen.<br>
	 * <br>
	 * Ausgabe von HTTP-Status 412 (Precondition failed)
	 */	
	function lock()
	{
		$this->httpStatus('412 Precondition failed');
		$this->options();
	}


		
	/**
	 * Die Methode UNLOCK sollte garnicht aufgerufen werden, da wir nur
	 * Dav-Level 1 implementieren und dies dem Client auch mitteilen.<br>
	 * <br>
	 * Ausgabe von HTTP-Status 412 (Precondition failed)
	 */	
	function unlock()
	{
		$this->httpStatus('412 Precondition failed');
		$this->options();
	}



	/**
	 * Die Methode POST ist bei WebDav nicht sinnvoll.<br>
	 * <br>
	 * Ausgabe von HTTP-Status 405 (Method Not Allowed)
	 */	
	function post()
	{
		// Die Methode POST ist bei Webdav nicht sinnvoll.
		$this->httpStatus('405 Method Not Allowed' );
	}
	
	
	
	/**
	 * Verzeichnis anlegen.
	 */		
	function mkcol()
	{
		
		if	( !empty($this->request) )
		{
			$this->httpStatus('415 Unsupported Media Type' ); // Kein Body erlaubt
		}
		elseif	( $this->readonly )
		{
			$this->httpStatus('403 Forbidden' ); // Kein Schreibzugriff erlaubt
		}
		elseif  ( !$this->folder->hasRight( ACL_CREATE_FOLDER ) )
		{
			$this->httpStatus('403 Forbidden' ); // Benutzer darf das nicht
		}
		elseif	( $this->obj == null )
		{
			// Die URI ist noch nicht vorhanden
			$f = new Folder();
			$f->filename  = basename($this->fullSkriptName);
			$f->parentid  = $this->folder->objectid;
			$f->projectid = $this->project->projectid;
			$f->add();
			$this->httpStatus('201 Created');
		}
		else
		{
			// MKCOL ist nicht moeglich, wenn die URI schon existiert.
			Logger::warn('MKCOL-Request to an existing resource');
			$this->httpStatus('405 Method Not Allowed' );
		}
	}


		
	/**
	 * Objekt l�schen.
	 */		
	function delete()
	{
		if	( $this->readonly )
		{
			$this->httpStatus('403 Forbidden' ); // Kein Schreibzugriff erlaubt
		}
		else
		{
			if	( $this->obj == null )
			{
				// Nicht existente URIs kann man auch nicht loeschen.
				$this->httpStatus('404 Not Found' );
			}
			elseif  ( ! $this->obj->hasRight( ACL_DELETE ) )
			{
				$this->httpStatus('403 Forbidden' ); // Benutzer darf die Resource nicht loeschen
			}
			elseif	( $this->obj->isFolder )
			{
				$f = new Folder( $this->obj->objectid );
				$f->deleteAll();
				$this->httpStatus( true ); // OK
				Logger::debug('Deleted folder with id '.$this->obj->objectid );
			}
			elseif	( $this->obj->isFile )
			{
				$f = new File( $this->obj->objectid );
				$f->delete();
				$this->httpStatus( true ); // OK
			}
			elseif	( $this->obj->isPage )
			{
				$p = new Page( $this->obj->objectid );
				$p->delete();
				$this->httpStatus( true ); // OK
			}
			elseif	( $this->obj->isLink )
			{
				$l = new Link( $this->obj->objectid );
				$l->delete();
				$this->httpStatus( true ); // OK
			}

		}
	}


		
	/**
	 * Kopieren eines Objektes.<br>
	 * Momentan ist nur das Kopieren einer Datei implementiert.<br>
	 * Das Kopieren von Ordnern, Verkn�pfungen und Seiten ist nicht moeglich.
	 */		
	function copy()
	{
		if	( $this->readonly || !$this->create )
		{
			Logger::error('WEBDAV: COPY request, but readonly or no creating');
			$this->httpStatus('405 Not Allowed' );
		}
		elseif( $this->obj == null )
		{
			// Was nicht da ist, laesst sich auch nicht verschieben.
			Logger::error('WEBDAV: COPY request, but Source not found');
			$this->httpStatus('405 Not Allowed' );
		}
		elseif ( $this->destination == null )
		{
			Logger::error('WEBDAV: COPY request, but no "Destination:"-Header');
			// $this->httpStatus('405 Not Allowed' );
			$this->httpStatus('412 Precondition failed');
		}
		else
		{
			// URL parsen.
			$dest = $this->destination;
			$destinationProject = $dest['project'];
			$destinationFolder  = $dest['folder' ];
			$destinationObject  = $dest['object' ];
			
			if	( $dest['type'] != 'object' )
			{
				Logger::debug('WEBDAV: COPY request, but "Destination:"-Header mismatch');
				$this->httpStatus('405 Not Allowed');
			}
			elseif	( $this->project->projectid != $destinationProject->projectid )
			{
				// Kopieren in anderes Projekt nicht moeglich.
				Logger::debug('WEBDAV: COPY request denied, project does not match');
				$this->httpStatus('403 Forbidden');
			}
			elseif	( $destinationObject != null )
			{
				Logger::debug('WEBDAV: COPY request denied, Destination exists. Overwriting is not supported');
				$this->httpStatus('403 Forbidden');
			}
			elseif ( is_object($destinationFolder) && ! $destinationFolder->hasRight( ACL_CREATE_FILE ) )
			{
				$this->httpStatus('403 Forbidden' ); // Benutzer darf das nicht
			}
			elseif ( is_object($destinationObject) && $destinationObject->isFolder)
			{
				Logger::debug('WEBDAV: COPY request denied, Folder-Copy not implemented');
				$this->httpStatus('405 Not Allowed');
			}
			elseif ( is_object($destinationObject) && $destinationObject->isLink)
			{
				Logger::debug('WEBDAV: COPY request denied, Link copy not implemented');
				$this->httpStatus('405 Not Allowed');
			}
			elseif ( is_object($destinationObject) && $destinationObject->isPage)
			{
				Logger::debug('WEBDAV: COPY request denied, Page copy not implemented');
				$this->httpStatus('405 Not Allowed');
			}
			else
			{
				$f = new File();
				$f->filename = basename($_SERVER['HTTP_DESTINATION']);
				$f->name     = '';
				$f->parentid = $destinationFolder->objectid;
				$f->projectid = $this->project->projectid;
				$f->add();
				$f->copyValueFromFile( $this->obj->objectid );
				
				Logger::debug('WEBDAV: COPY request accepted' );
				// Objekt wird in anderen Ordner kopiert.
				$this->httpStatus('201 Created' );
			}	
		}

	}


		
	/**
	 * Verschieben eines Objektes.<br>
	 * <br>
	 * Folgende Operationen sind m�glich:<br>
	 * - Unbenennen eines Objektes (alle Typen)<br> 
	 * - Verschieben eines Objektes (alle Typen) in einen anderen Ordner.<br>
	 */		
	function move()
	{
		if	( $this->readonly )
		{
			$this->httpStatus('403 Forbidden - Readonly Mode' ); // Schreibgeschuetzt
		}
		elseif	( !$this->create )
		{
			$this->httpStatus('403 Forbidden - No creation' ); // Schreibgeschuetzt
		}
		elseif( $this->obj == null )
		{
			// Was nicht da ist, laesst sich auch nicht verschieben.
			$this->httpStatus('404 Not Found' );
		}
		elseif( is_object($this->obj) && ! $this->obj->hasRight( ACL_WRITE ) )
		{
			// Was nicht da ist, laesst sich auch nicht verschieben.
			Logger::error('Source '.$this->obj->objectid.' is not writable: Forbidden');
			$this->httpStatus('403 Forbidden' );
		}
		elseif ( $this->destination == null )
		{
			Logger::error('WEBDAV: MOVE request, but no "Destination:"-Header');
			// $this->httpStatus('405 Not Allowed' );
			$this->httpStatus('412 Precondition failed');
		}
		else
		{
			$dest = $this->destination;
			$destinationProject = $dest['project'];
			$destinationFolder  = $dest['folder' ];
			$destinationObject  = $dest['object' ];

			if	( $dest['type'] != 'object' )
			{
				Logger::debug('WEBDAV: MOVE request, but "Destination:"-Header mismatch');
				$this->httpStatus('405 Not Allowed');
				return;
			}

			if	( is_object($destinationFolder) && ! $destinationFolder->hasRight( ACL_CREATE_FILE ) )
			{
				Logger::error('Source '.$this->obj->objectid.' is not writable: Forbidden');
				$this->httpStatus('403 Forbidden' );
			}
			
			if	( $destinationObject != null )
			{
				Logger::debug('WEBDAV: MOVE request denied, destination exists');
				$this->httpStatus('412 Precondition Failed');
				return;
			}
			
			if	( $this->project->projectid != $destinationProject->projectid )
			{
				// Verschieben in anderes Projekt nicht moeglich.
				Logger::debug('WEBDAV: MOVE request denied, project does not match');
				$this->httpStatus('405 Not Allowed');
				return;
			}
			
			if	( $this->folder->objectid == $destinationFolder->objectid )
			{
				Logger::debug('WEBDAV: MOVE request accepted, object renamed');
				// Resource bleibt in gleichem Ordner.
				$this->obj->filename = basename($_SERVER['HTTP_DESTINATION']);
				$this->obj->objectSave(false);
				$this->httpStatus('201 Created' );
				return;
			}
			
			if	( $destinationFolder->isFolder )
			{
				Logger::debug('WEBDAV: MOVE request accepted, Destination: '.$destinationFolder->filename );
				// Objekt wird in anderen Ordner verschoben.
				$this->obj->setParentId( $destinationFolder->objectid );
				$this->httpStatus('201 Created' );
				return;
			}
			
			Logger::warn('WEBDAV: MOVE request failed' );
			$this->httpStatus('500 Internal Server Error' );
		}
	}


		
	/**
	 * Anlegen oder �berschreiben Dateien �ber PUT.<br>
	 * Dateien k�nnen neu angelegt und �berschrieben werden.<br>
	 * <br>
	 * Seiten k�nnen nicht �berschrieben werden. Wird versucht,
	 * eine Seite mit PUT zu �berschreiben, wird der Status "405 Not Allowed" gemeldet.<br>
	 */		
	function put()
	{
		// TODO: 409 (Conflict) wenn �bergeordneter Ordner nicht da.

		if	( $this->webdav_conf['readonly'] )
		{
			$this->httpStatus('405 Not Allowed' );
		}		
		elseif	( strlen($this->request) > $this->maxFileSize*1000 )
		{
			// Maximale Dateigroesse ueberschritten.
			// Der Status 207 "Zuwenig Speicherplatz" passt nicht ganz, aber fast :)
			$this->httpStatus('507 Insufficient Storage' );
		}
		elseif	( $this->obj == null )
		{
			// Neue Datei anlegen
			if	( !$this->webdav_conf['create'] )
			{
				Logger::warn('WEBDAV: Creation of files not allowed by configuration' );
				$this->httpStatus('405 Not Allowed' );
			}
			
			if	( ! $this->folder->hasRight( ACL_CREATE_FILE ) )
			{
				$this->httpStatus('403 Forbidden');
				return;
			}
			
			$file = new File();
			$file->filename  = basename($this->fullSkriptName);
			$file->extension = '';		
			$file->size      = strlen($this->request);
			$file->parentid  = $this->folder->objectid;
			$file->projectid = $this->project->projectid;
			$file->value     = $this->request;
			$file->add();
			$this->httpStatus('201 Created');
			return;
		}
		elseif	( $this->obj->isFile )
		{
			if	( ! $this->obj->hasRight( ACL_WRITE ) )
			{
				Logger::debug('PUT failed, parent folder not writable by user' );
				$this->httpStatus('403 Forbidden');
				return;
			}
			
			// Bestehende Datei ueberschreiben.
			$file = new File( $this->obj->objectid );
			$file->saveValue( $this->request );
			$file->setTimestamp();
			$this->httpStatus('204 No Content');
			Logger::debug('PUT ok, file is created' );
			return;
		}
		elseif	( $this->obj->isFolder )
		{
			Logger::error('PUT on folder is not supported, use PROPFIND. Lame client?' );
			$this->httpStatus('405 Not Allowed' );
		}
		else
		{
			// Fuer andere Objekttypen (Links, Seiten) ist kein PUT moeglich.
			Logger::warn('PUT only available for files, pages and links are ignored' );
			$this->httpStatus('405 Not Allowed' );
		}
	}
	
	

	/**
	 * WebDav-Methode PROPFIND.
	 * 
	 * Diese Methode wird
	 * - beim Ermitteln von Verzeichnisinhalten und
	 * - beim Ermitteln von Metainformationen zu einer Datei
	 * verwendet.
	 * 
	 * Das Ergebnis wird in einer XML-Zeichenkette geliefert.
	 */	
	function propfind()
	{
		switch( $this->requestType )
		{
			case 'projectlist':  // Projektliste
				
				$inhalte = array();
				
				$objektinhalt = array();
				$z = 30*365.25*24*60*60;
				$objektinhalt['createdate'    ] = $z;
				$objektinhalt['lastchangedate'] = $z;
				$objektinhalt['size'          ] = 1;
				$objektinhalt['name'          ] = $this->fullSkriptName;
				$objektinhalt['displayname'   ] = '';
				$objektinhalt['type']           = 'folder';

				$inhalte[] = $objektinhalt;
				
				foreach( Project::getAll() as $projectid=>$projectName )
				{
					$project = new Project( $projectid );
					$rootObjectId = $project->getRootObjectId();
					$folder = new Folder( $rootObjectId );
					$folder->load();
					
					$objektinhalt = array();
					$z = 30*365.25*24*60*60;
					$objektinhalt['createdate'    ] = $z;
					$objektinhalt['lastchangedate'] = $folder->lastchangeDate;
					$objektinhalt['size'          ] = $project->size();
					$objektinhalt['name'          ] = $this->fullSkriptName.$projectName.'/';
					$objektinhalt['displayname'   ] = $projectName;
					$objektinhalt['type']           = 'folder';
					$inhalte[] = $objektinhalt;
				}
					
				$this->multiStatus( $inhalte );
				break;

			case 'object':  // Verzeichnisinhalt
			
				if	( $this->obj == null )
				{
					// Objekt existiert nicht.
					Logger::trace( 'WEBDAV: PROPFIND of non-existent object');
					$this->httpStatus('404 Not Found');
					return;
				}
				elseif	( $this->obj->isFolder )
				{
					if	( ! $this->obj->hasRight( ACL_READ ))
					{
						Logger::debug( 'Folder '.$this->obj->objectid.': access denied');
						$this->httpStatus('403 Forbidden');
					}
					
					$inhalte = array();

					$objektinhalt = array();
					$objektinhalt['createdate'    ] = $this->obj->createDate;
					$objektinhalt['lastchangedate'] = $this->obj->lastchangeDate;
					$objektinhalt['name'          ] = $this->fullSkriptName;
					$objektinhalt['displayname'   ] = basename($this->fullSkriptName);
					$objektinhalt['type'          ] = 'folder';
					$objektinhalt['size'          ] = 0;
					$inhalte[] = $objektinhalt;
					
					if	( $this->depth > 0 )
					{
						$objects = $this->folder->getObjects();
						foreach( $objects as $object  )
						{
							if	( ! $object->hasRight( ACL_READ ))
								continue;
							
							//$object->loadRaw();
							$objektinhalt = array();
							$objektinhalt['createdate'    ] = $object->createDate;
							$objektinhalt['lastchangedate'] = $object->lastchangeDate;
							$objektinhalt['displayname'   ] = $object->filename;

							switch( $object->getType() )
							{
								
								case OR_TYPE_FOLDER:
									$objektinhalt['name'] = $this->fullSkriptName.$object->filename.'/';
									$objektinhalt['type'] = 'folder';
									$objektinhalt['size'] = 0;
									$inhalte[] = $objektinhalt;
									break;
								case OR_TYPE_FILE:
									$objektinhalt['name'] = $this->fullSkriptName.$object->filename;
									$objektinhalt['type'] = 'file';
									$file = new File($object->objectid);
									$file->load();
									$objektinhalt['size'] = $file->size;
									$objektinhalt['mime'] = 'application/x-non-readable';
									$inhalte[] = $objektinhalt;
									break;
								case OR_TYPE_LINK:
									$objektinhalt['name'] = $this->fullSkriptName.$object->filename;
									$objektinhalt['type'] = 'file';
									$objektinhalt['size'] = 0;
									$objektinhalt['mime'] = 'application/x-non-readable';
									$inhalte[] = $objektinhalt;
									break;
								case OR_TYPE_PAGE:
									$objektinhalt['name'] = $this->fullSkriptName.$object->filename;
									$objektinhalt['type'] = 'file';
									$objektinhalt['size'] = 0;
									$inhalte[] = $objektinhalt;
									break;
								default:
							}
						}
					}
					Logger::trace( 'WEBDAV: PROPFIND-2');
					
//					if	( count($inhalte)==0 )
//						$inhalte[] = array('createdate'=>0,'lastchangedate'=>0,'name'=>'empty','size'=>0,'type'=>'file');
						
					Logger::trace('Anzahl Dateien:'.count($inhalte));
					$this->multiStatus( $inhalte );
				}
				else
				{
					$object = $this->obj;
					Logger::trace( 'WEBDAV: PROPFIND of file');
					$objektinhalt = array();
					$objektinhalt = array();
					$objektinhalt['name']           = $this->fullSkriptName.'/'.$object->filename.'/';
					$objektinhalt['displayname']    = $object->filename;
					$objektinhalt['createdate'    ] = $object->createDate;
					$objektinhalt['lastchangedate'] = $object->lastchangeDate;
					$file = new File( $this->obj->objectid );
					$file->load();
					$objektinhalt['size'          ] = $file->size;
					$objektinhalt['type'          ] = 'file';
					
					
					$this->multiStatus( array($objektinhalt) );
				}
				break;
				
			default:
				Logger::warn('Internal Error, unknown request type: '. $this->requestType);
				$this->httpStatus('500 Internal Server Error');
		}
	}
	
	
	/**
	 * Webdav-Methode PROPPATCH ist nicht implementiert.
	 */
	function proppatch()
	{
		// TODO: Multistatus erzeugen.
		// Evtl. ist '409 Conflict' besser?
		$this->httpStatus('405 Not Allowed');
	}
	
	
	/**
	 * Erzeugt einen Multi-Status.
	 * @access private
	 */
	function multiStatus( $files )
	{
		$this->httpStatus('207 Multi-Status');
		header('Content-Type: text/xml; charset=utf-8');
		
		$response = '';
		$response .= '<?xml version="1.0" encoding="utf-8" ?>';
		$response .= '<d:multistatus xmlns:d="DAV:">';

		foreach( $files as $file )
			$response .= $this->getResponse( $file['name'],$file );
		
		$response .= '</d:multistatus>';
		Logger::trace('PROPFIND: '.$response);

		$response = utf8_encode($response);

		header('Content-Length: '.strlen($response));
		echo $response;
	}
	
	
	/**
	 * Erzeugt ein "response"-Element, welches in ein "multistatus"-element verwendet werden kann.
	 */
	function getResponse( $file,$options )
	{
		// TODO: Nur angeforderte Elemente erzeugen.
		$response = '';
		$response .= '<d:response>';
		$response .= '<d:href>'.$file.'</d:href>';
		$response .= '<d:propstat>';
		$response .= '<d:prop>';
		//		$response .= '<d:source></d:source>';
		$response .= '<d:creationdate>'.date('r',$options['createdate']).'</d:creationdate>';
		$response .= '<d:displayname>'.$options['displayname'].'</d:displayname>';
		$response .= '<d:getcontentlength>'.$options['size'].'</d:getcontentlength>';
		$response .= '<d:getlastmodified xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">'.date('r',$options['lastchangedate']).'</d:getlastmodified>';
		
		if	( $options['type'] == 'folder')
			$response .= '<d:resourcetype><d:collection/></d:resourcetype>';
		else
			$response .= '<d:resourcetype />';
			
		$response .= '<d:categories />';
		$response .= '<d:fields></d:fields>';
		
		
		
//		$response .= '<d:getcontenttype>text/html</d:getcontenttype>';
//		$response .= '<d:getcontentlength />';
//		$response .= '<d:getcontentlanguage />';
//		$response .= '<d:executable />';
//		$response .= '<d:resourcetype>';
//		$response .= '<d:collection />';
//		$response .= '</d:resourcetype>';
//		$response .= '<d:getetag />';

		$response .= '</d:prop>';
		$response .= '<d:status>HTTP/1.1 200 OK</d:status>';
		$response .= '</d:propstat>';
		$response .= '</d:response>';

		return $response;		
	}
	
	
	
	/**
	 * URI parsen.
	 */
	function parseURI( $uri )
	{
		// Ergebnis initialisieren (damit alle Schl�ssel vorhanden sind)
		$ergebnis = array('type'    => null,
		                  'project' => null,
		                  'path'    => array(),
		                  'folder'  => null,
		                  'object'  => null  );
		
		Logger::trace( 'WEBDAV: Parsen der URI '.$uri);
		$uriParts = explode('/',$uri);
		
		$nr = 0;
		$f = null;
		$o = null;
		$ergebnis['type'] = 'projectlist';

		foreach( $uriParts as $uriPart )
		{
			if	( empty( $uriPart))
				continue;

			$ergebnis['path'][] = $uriPart;	

			if	( $f == null )			
			{
				// URI='/project/'
				// Name des Projektes in der URL, es wird das Projekt geladen.
				$ergebnis['type'] = 'object';
				
				$p = new Project();
				$p->name = $uriPart;
				Logger::trace("Projektname: ".$p->name);
				$p->loadByName();
				$ergebnis['project'] = $p;
				// Das Projekt hat weder Sprache noch Variante gesetzt.
				//Session::setProjectLanguage( new Language( $this->project->getDefaultLanguageId() ) );
				//Session::setProjectModel   ( new Model   ( $this->project->getDefaultModelId()    ) );

				$oid = $p->getRootObjectId();

				$f = new Folder($oid);
				$ergebnis['object'] = $f;
				$ergebnis['folder'] = $f;
			
			}
			else
			{
				if	( $ergebnis['object'] == null )
				{
					$this->httpStatus('409 Conflict');
					exit;
				}
				
				$oid = $f->getObjectIdByFileName($uriPart);

				if	( $oid == 0 )
				{
					Logger::trace( 'WEBDAV: URL-Part does not exist: '.$uriPart);
					$ergebnis['object'] = null;
				}
				else
				{
					Logger::trace( 'Teil '.$uriPart);
					$o = new Object($oid);
					$o->load();
					$ergebnis['object'] = $o;
					
					if	( $o->isFolder )
					{
						$f = new Folder($oid);
						$ergebnis['folder'] = $f;
					}
				}
			}
		}

		return $ergebnis;
	 }
}



/**
 * Fehler-Handler fuer WEBDAV.<br>
 * Bei einem Laufzeitfehler ist eine Ausgabe des Fehlers auf der Standardausgabe sinnlos,
 * da der WebDAV-Client dies nicht lesen oder erkennen kann.
 * Daher wird der Fehler-Handler umgebogen, so dass nur ein Logeintrag sowie ein
 * Server-Fehler erzeugt wird.
 */
function webdavErrorHandler($errno, $errstr, $errfile, $errline) 
{
	Logger::warn('WEBDAV ERROR: '.$errno.'/'.$errstr.'/file:'.$errfile.'/line:'.$errline);

	// Wir teilen dem Client mit, dass auf dem Server was schief gelaufen ist.	
	WebdavAction::httpStatus('500 Internal Server Error, WebDAV-Request failed with "'.$errstr.'"');
}

?>
Return current item: OpenRat CMS