File dav/DAV.class.php

Last commit: Thu Nov 7 01:07:03 2019 +0100	Jan Dankert	No direct calls any more. All data of the CMS is transferred via the API within an array.
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 }
Download dav/DAV.class.php
History Thu, 7 Nov 2019 01:07:03 +0100 Jan Dankert No direct calls any more. All data of the CMS is transferred via the API within an array. Wed, 6 Nov 2019 23:30:46 +0100 Jan Dankert Security Fix: The user password must be part of the key for the cookie store. Wed, 6 Nov 2019 23:21:38 +0100 Jan Dankert Fix: Enable creation of projects and folders. Tue, 5 Nov 2019 00:02:24 +0100 Jan Dankert Refactoring: Multistatus is only used for PROPFIND. Mon, 4 Nov 2019 23:56:38 +0100 Jan Dankert Refactoring: Renaming WebDAV to DAV base class.