openrat-webdav

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

DAV.class.php (9829B)


      1 <?php
      2 
      3 define('LB',"\n");
      4 
      5 abstract class DAV
      6 {
      7 
      8 	// Zahlreiche Instanzvariablen, die im Konstruktor
      9 	// beim Zerlegen der Anfrag gef�llt werden.
     10 	public $database;
     11 	public $depth;
     12 	public $projectid;
     13 	public $request;
     14 	public $filename;
     15 	public $uri;
     16 	public $headers;
     17 	public $data;
     18 
     19 	public $destination = null;
     20 	public $sitePath;
     21 	public $create;
     22 	public $readonly;
     23 	public $maxFileSize;
     24 	public $webdav_conf;
     25 	public $overwrite = false;
     26 
     27 	private $httpMethod;
     28 
     29     /**
     30      * CMS-Client
     31      * @var CMS
     32      */
     33     protected $client;
     34 
     35     const MEMKEY = 1;
     36     private $shm;
     37     private $store;
     38 
     39 
     40     /**
     41 	 * Im Kontruktor wird der Request analysiert und ggf. eine Authentifzierung
     42 	 * durchgefuehrt. Anschließend wird eine interne Methode mit dem Namen davXXX() aufgerufen.
     43 	 */
     44 	function __construct()
     45 	{
     46         // Dateitoken-Schlüssel ermitteln
     47         $key = ftok(__FILE__, 'a');
     48         // 0,5 MB
     49         $this->shm = shm_attach($key, 0.5 * 1000 * 1000, 0666);
     50 
     51         global $config;
     52 
     53         $this->httpMethod = strtoupper($_SERVER['REQUEST_METHOD']);
     54 
     55 		Logger::trace( 'WEBDAV request' );
     56 		
     57 		if	( $config['dav.compliant_to_redmond'] )
     58 			header('MS-Author-Via: DAV'           ); // Extrawurst fuer MS-Clients.
     59 			
     60 		if	( $config['dav.expose_openrat'] )
     61 			header('X-Dav-powered-by: OpenRat CMS'); // Bandbreite verschwenden :)
     62 
     63  		Logger::trace( 'WEBDAV: URI='.$_SERVER['REQUEST_URI']);
     64 		
     65 		if	( !$config['dav.enable'])
     66 		{
     67  			Logger::warn( 'WEBDAV is disabled by configuration' );
     68 		
     69 			$this->httpStatus('403 Forbidden');
     70 			exit;
     71 		}
     72 		
     73 		$this->create      = $config['dav.create'];
     74 		$this->readonly    = $config['dav.readonly'];
     75 		$this->maxFileSize = $config['cms.max_file_size'];
     76 		
     77 		$this->headers = getallheaders();
     78 		/* DAV compliant servers MUST support the "0", "1" and
     79 		 * "infinity" behaviors. By default, the PROPFIND method without a Depth
     80 		 * header MUST act as if a "Depth: infinity" header was included. */
     81 		if	( !isset($this->headers['Depth']) )
     82 			$this->depth = 1;
     83 		elseif	( strtolower($this->headers['Depth'])=='infinity')
     84 			$this->depth = 1;
     85 		else
     86 			$this->depth = intval($this->headers['Depth']);
     87 
     88 		if	( isset($this->headers['Destination']) )
     89 			$this->destination = $this->headers['Destination'];
     90 
     91 		$this->overwrite = @$this->headers['Overwrite'] == 'T';
     92 
     93 
     94         if	( @$config['dav.anonymous']) {
     95 
     96             $username = @$config['cms.user'    ];
     97             $pass     = @$config['cms.password'];
     98         }
     99         else
    100         {
    101             $username = @$_SERVER['PHP_AUTH_USER'];
    102             $pass     = @$_SERVER['PHP_AUTH_PW'  ];
    103 
    104         }
    105 
    106         $this->storeKey = $username.':'.$pass;
    107 
    108         if   ( shm_has_var($this->shm, self::MEMKEY) )
    109 
    110             $this->store = shm_get_var($this->shm, self::MEMKEY);
    111         else
    112             $this->store = array();
    113 
    114         Logger::trace('Store at startup: '.LB.print_r($this->store,true) );
    115 
    116         if   ( is_array(@$this->store[$this->storeKey]) )
    117             ;
    118         else
    119             $this->store[$this->storeKey] = array();
    120 
    121         $this->client = new CMS();
    122         $this->client->client->cookies = $this->store[$this->storeKey];
    123 
    124 		if	( $this->store[$this->storeKey] )
    125 		{
    126 			// Es gibt Cookies. Also:
    127             // Benutzer ist bereits im CMS eingeloggt.
    128 		}
    129 		else
    130 		{
    131 			// Login
    132 			if	( $this->httpMethod != 'OPTIONS' ) // Bei OPTIONS kein Login anfordern
    133 			{
    134 				if	( isset($_SERVER['PHP_AUTH_USER']) )
    135 				{
    136 
    137 					try {
    138 						$this->client->login($username, $pass, $config['cms.database']);
    139 
    140                         $this->store[$this->storeKey] = $this->client->client->cookies;
    141                         shm_put_var($this->shm,self::MEMKEY, $this->store);
    142 					}
    143 					catch( Exception $e )
    144 					{
    145 						$this->httpStatus('401 Unauthorized');
    146 						header('WWW-Authenticate: Basic realm="'.$config['dav.realm'].'"');
    147 						error_log( print_r($e->getMessage(),true) );
    148 						echo  'Failed login for user '.$username;
    149 						exit;
    150 					}
    151 				}
    152 				elseif	( $config['dav.anonymous'])
    153 				{
    154 					$loginOk = $this->client->login($username, $pass, $config['cms.database']);
    155 					if	( !$loginOk ) {
    156 						$this->httpStatus('500 Internal Server Error');
    157 						echo 'Could not authenticate user '.$username;
    158 						exit;
    159 					}
    160 
    161 					$this->store[$this->storeKey] = $this->client->client->cookies;
    162                     shm_put_var($this->shm,self::MEMKEY, $this->store);
    163                 }
    164 				else
    165 				{
    166 					// Client ist nicht angemeldet, daher wird nun die
    167 					// Authentisierung angefordert.
    168 					header('WWW-Authenticate: Basic realm="'.$config['dav.realm'].'"');
    169 					$this->httpStatus('401 Unauthorized');
    170 					echo  'Authentification required for '.$config['dav.realm'];
    171 					exit;
    172 					
    173 				}
    174 			}
    175 			else
    176 			{
    177 				return; // Bei OPTIONS müssen wir keine URL auswerten und können direkt zur Methode springen.
    178 			}
    179 		}
    180 		
    181 		
    182 		$this->sitePath = $this->siteURL().$_SERVER['PHP_SELF'];
    183 
    184 		// Path-Info. If not set, use '/'
    185         $pathInfo = @$_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO'] : '/';
    186 
    187         $this->request = new URIParser($this->client, $pathInfo);
    188 
    189         Logger::trace( $this->request->__toString() );
    190 
    191 		/*
    192 		 * Directories MUST end with a '/'. If not, redirect.
    193 		 * 
    194 		 * RFC 2518, 5.2 Collection Resources, Page 11:
    195 		 * "For example, if a client invokes a
    196 		 * method on http://foo.bar/blah (no trailing slash), the resource
    197 		 * http://foo.bar/blah/ (trailing slash) may respond as if the operation
    198 		 * were invoked on it, and should return a content-location header with
    199 		 * http://foo.bar/blah/ in it.  In general clients SHOULD use the "/"
    200 		 * form of collection names."
    201 		 */
    202 		if	( in_array($this->request->type,array('folder','root')) &&
    203 			  substr($this->sitePath,-1 ) != '/' )
    204 		    if   ( $config['dav.redirect_collections_to_trailing_slash'] )
    205             {
    206                 // redirect the collection to append the trailing slash.
    207                 // this is recommended by the spec (see above).
    208                 Logger::debug( 'Redirecting lame client to slashyfied folder URL' );
    209 
    210                 header('HTTP/1.1 302 Moved Temporarily');
    211                 header('Location: '.$this->sitePath.'/');
    212                 exit;
    213             }
    214             else
    215             {
    216                 // no redirect - so we append the trailing slash.
    217                 // this is allowed by the spec (see above).
    218                 $this->sitePath .= '/';
    219             }
    220 
    221 
    222 		// Falls vorhanden, den "Destination"-Header parsen.
    223 		if	( isset($_SERVER['HTTP_DESTINATION']) )
    224 		{
    225 			$destUri = parse_url( $_SERVER['HTTP_DESTINATION'] );
    226 			
    227 			$uri = substr($destUri['path'],strlen($_SERVER['SCRIPT_NAME'])+$sos);
    228 				
    229 			// URL parsen.
    230 			$this->destination = new URIParser( $this->client,$uri );
    231 		}
    232 
    233 		// Den Request-BODY aus der Standardeingabe lesen.
    234 		$this->data = implode('',file('php://input'));
    235 	}
    236 
    237 
    238 	
    239 
    240 	
    241     /**
    242      * Setzt einen HTTP-Status.<br>
    243      * <br>
    244      * Es wird ein HTTP-Status gesetzt, zus�tzlich wird der Status in den Header "X-DAV-Status" geschrieben.<br>
    245      * Ist der Status nicht 200 oder 207 (hier folgt ein BODY), wird das Skript beendet.
    246      */
    247     public static function httpStatus( $status = true )
    248     {
    249         if	( $status === true )
    250             $status = '200 OK';
    251 
    252         Logger::debug('WEBDAV: HTTP-Status: '.$status);
    253 
    254         header('HTTP/1.1 '.$status);
    255         header('X-DAV-Status: '.$status,true);
    256     }
    257 
    258 
    259     /**
    260      * Setzt einen HTTP-Status.<br>
    261      * <br>
    262      * Es wird ein HTTP-Status gesetzt, zus�tzlich wird der Status in den Header "X-DAV-Status" geschrieben.<br>
    263      * Ist der Status nicht 200 oder 207 (hier folgt ein BODY), wird das Skript beendet.
    264      */
    265     public static function httpForbidden()
    266     {
    267         $status = 403;
    268         header('HTTP/1.1 '.$status);
    269         header('X-WebDAV-Status: '.$status,true);
    270         Logger::debug('WEBDAV: HTTP-Status: '.$status);
    271     }
    272 
    273 
    274     /**
    275      * Setzt einen HTTP-Status.<br>
    276      * <br>
    277      * Es wird ein HTTP-Status gesetzt, zus�tzlich wird der Status in den Header "X-DAV-Status" geschrieben.<br>
    278      * Ist der Status nicht 200 oder 207 (hier folgt ein BODY), wird das Skript beendet.
    279      */
    280     public function httpMethodNotAllowed()
    281     {
    282         $status = 405;
    283         header('HTTP/1.1 '.$status);
    284         header('X-WebDAV-Status: '.$status,true);
    285 
    286         // RFC 2616 (HTTP/1.1), Section 10.4.6 "405 Method Not Allowed" says:
    287         //   "[...] The response MUST include an
    288         //    Allow header containing a list of valid methods for the requested
    289         //    resource."
    290         //
    291         // RFC 2616 (HTTP/1.1), Section 14.7 "Allow" says:
    292         //   "[...] An Allow header field MUST be
    293         //     present in a 405 (Method Not Allowed) response."
    294         header('Allow: '.implode(', ',$this->allowed_methods()) );
    295 
    296         self::httpStatus('405 Method Not Allowed');
    297     }
    298 
    299 
    300 
    301 
    302     protected function allowed_methods()
    303     {
    304 
    305         if	 ($this->readonly)
    306             return array('OPTIONS','HEAD','GET','PROPFIND');  // Readonly-Modus
    307         else
    308             // PROPPATCH unterstuetzen wir garnicht, aber lt. Spec sollten wir das.
    309             return array('OPTIONS','HEAD','GET','PROPFIND','DELETE','PUT','COPY','MOVE','MKCOL','PROPPATCH');
    310     }
    311 
    312 
    313 
    314     private function siteURL()
    315     {
    316         $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
    317         $domainName = $_SERVER['HTTP_HOST'];
    318         return $protocol.$domainName;
    319     }
    320 
    321     private function slashify( $path )
    322     {
    323         return $path.( substr( $path,-1 ) != '/' )?'/':'';
    324     }
    325 
    326     public function __destruct()
    327     {
    328         $this->store[$this->storeKey] = $this->client->client->cookies;
    329         shm_put_var($this->shm,self::MEMKEY,$this->store);
    330     }
    331 
    332 
    333     public abstract function execute();
    334 }