WebDAV.class.php (32074B)
1 <?php 2 3 define('LB',"\n"); 4 class WebDAV 5 { 6 7 // Zahlreiche Instanzvariablen, die im Konstruktor 8 // beim Zerlegen der Anfrag gef�llt werden. 9 public $database; 10 public $depth; 11 public $projectid; 12 public $request; 13 public $filename; 14 public $uri; 15 public $headers; 16 public $data; 17 18 public $destination = null; 19 public $sitePath; 20 public $create; 21 public $readonly; 22 public $maxFileSize; 23 public $webdav_conf; 24 public $overwrite = false; 25 26 private $httpMethod; 27 28 /** 29 * CMS-Client 30 * @var CMS 31 */ 32 private $client; 33 34 const MEMKEY = 1; 35 private $shm; 36 private $store; 37 38 39 /** 40 * Im Kontruktor wird der Request analysiert und ggf. eine Authentifzierung 41 * durchgefuehrt. Anschließend wird eine interne Methode mit dem Namen davXXX() aufgerufen. 42 */ 43 function __construct() 44 { 45 // Dateitoken-Schlüssel ermitteln 46 $key = ftok(__FILE__, 'a'); 47 // 0,5 MB 48 $this->shm = shm_attach($key, 0.5 * 1000 * 1000, 0666); 49 50 global $config; 51 52 $this->httpMethod = strtoupper($_SERVER['REQUEST_METHOD']); 53 54 Logger::trace( 'WEBDAV request' ); 55 56 if ( $config['dav.compliant_to_redmond'] ) 57 header('MS-Author-Via: DAV' ); // Extrawurst fuer MS-Clients. 58 59 if ( $config['dav.expose_openrat'] ) 60 header('X-Dav-powered-by: OpenRat CMS'); // Bandbreite verschwenden :) 61 62 Logger::trace( 'WEBDAV: URI='.$_SERVER['REQUEST_URI']); 63 64 if ( !$config['dav.enable']) 65 { 66 Logger::warn( 'WEBDAV is disabled by configuration' ); 67 68 $this->httpStatus('403 Forbidden'); 69 exit; 70 } 71 72 $this->create = $config['dav.create']; 73 $this->readonly = $config['dav.readonly']; 74 $this->maxFileSize = $config['cms.max_file_size']; 75 76 $this->headers = getallheaders(); 77 /* DAV compliant servers MUST support the "0", "1" and 78 * "infinity" behaviors. By default, the PROPFIND method without a Depth 79 * header MUST act as if a "Depth: infinity" header was included. */ 80 if ( !isset($this->headers['Depth']) ) 81 $this->depth = 1; 82 elseif ( strtolower($this->headers['Depth'])=='infinity') 83 $this->depth = 1; 84 else 85 $this->depth = intval($this->headers['Depth']); 86 87 if ( isset($this->headers['Destination']) ) 88 $this->destination = $this->headers['Destination']; 89 90 $this->overwrite = @$this->headers['Overwrite'] == 'T'; 91 92 93 if ( @$config['dav.anonymous']) 94 $username = @$config['cms.user']; 95 else 96 $username = @$_SERVER['PHP_AUTH_USER']; 97 98 $this->username = $username; 99 100 if ( shm_has_var($this->shm, self::MEMKEY) ) 101 102 $this->store = shm_get_var($this->shm, self::MEMKEY); 103 else 104 $this->store = array(); 105 106 Logger::trace('Store at startup: '.LB.print_r($this->store,true) ); 107 108 if ( is_array(@$this->store[$username]) ) 109 ; 110 else 111 $this->store[$username] = array(); 112 113 $this->client = new CMS(); 114 $this->client->client->cookies = $this->store[$username]; 115 116 if ( $this->store[$username] ) 117 { 118 // Es gibt Cookies. Also: 119 // Benutzer ist bereits im CMS eingeloggt. 120 } 121 else 122 { 123 // Login 124 if ( $this->httpMethod != 'OPTIONS' ) // Bei OPTIONS kein Login anfordern 125 { 126 if ( isset($_SERVER['PHP_AUTH_USER']) ) 127 { 128 129 $username = $_SERVER['PHP_AUTH_USER']; 130 $pass = $_SERVER['PHP_AUTH_PW' ]; 131 132 try { 133 $this->client->login($username, $pass, $config['cms.database']); 134 135 $this->store[$username] = $this->client->client->cookies; 136 shm_put_var($this->shm,self::MEMKEY, $this->store); 137 } 138 catch( Exception $e ) 139 { 140 $this->httpStatus('401 Unauthorized'); 141 header('WWW-Authenticate: Basic realm="'.$config['dav.realm'].'"'); 142 error_log( print_r($e->getMessage(),true) ); 143 echo 'Failed login for user '.$username; 144 exit; 145 } 146 } 147 elseif ( $config['dav.anonymous']) 148 { 149 $username = $config['cms.user']; 150 $pass = $config['cms.password']; 151 152 $loginOk = $this->client->login($username, $pass, $config['cms.database']); 153 if ( !$loginOk ) { 154 $this->httpStatus('500 Internal Server Error'); 155 echo 'Could not authenticate user '.$username; 156 exit; 157 } 158 159 $this->store[$username] = $this->client->client->cookies; 160 shm_put_var($this->shm,self::MEMKEY, $this->store); 161 } 162 else 163 { 164 // Client ist nicht angemeldet, daher wird nun die 165 // Authentisierung angefordert. 166 header('WWW-Authenticate: Basic realm="'.$config['dav.realm'].'"'); 167 $this->httpStatus('401 Unauthorized'); 168 echo 'Authentification required for '.$config['dav.realm']; 169 exit; 170 171 } 172 } 173 else 174 { 175 return; // Bei OPTIONS müssen wir keine URL auswerten und können direkt zur Methode springen. 176 } 177 } 178 179 180 $this->sitePath = $this->siteURL().$_SERVER['PHP_SELF']; 181 182 // Path-Info. If not set, use '/' 183 $pathInfo = @$_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO'] : '/'; 184 185 $this->request = new URIParser($this->client, $pathInfo); 186 187 Logger::trace( $this->request->__toString() ); 188 189 /* 190 * Directories MUST end with a '/'. If not, redirect. 191 * 192 * RFC 2518, 5.2 Collection Resources, Page 11: 193 * "For example, if a client invokes a 194 * method on http://foo.bar/blah (no trailing slash), the resource 195 * http://foo.bar/blah/ (trailing slash) may respond as if the operation 196 * were invoked on it, and should return a content-location header with 197 * http://foo.bar/blah/ in it. In general clients SHOULD use the "/" 198 * form of collection names." 199 */ 200 if ( in_array($this->request->type,array('folder','root')) && 201 substr($this->sitePath,-1 ) != '/' ) 202 if ( $config['dav.redirect_collections_to_trailing_slash'] ) 203 { 204 // redirect the collection to append the trailing slash. 205 // this is recommended by the spec (see above). 206 Logger::debug( 'Redirecting lame client to slashyfied folder URL' ); 207 208 header('HTTP/1.1 302 Moved Temporarily'); 209 header('Location: '.$this->sitePath.'/'); 210 exit; 211 } 212 else 213 { 214 // no redirect - so we append the trailing slash. 215 // this is allowed by the spec (see above). 216 $this->sitePath .= '/'; 217 } 218 219 220 // Falls vorhanden, den "Destination"-Header parsen. 221 if ( isset($_SERVER['HTTP_DESTINATION']) ) 222 { 223 $destUri = parse_url( $_SERVER['HTTP_DESTINATION'] ); 224 225 $uri = substr($destUri['path'],strlen($_SERVER['SCRIPT_NAME'])+$sos); 226 227 // URL parsen. 228 $this->destination = new URIParser( $this->client,$uri ); 229 } 230 231 // Den Request-BODY aus der Standardeingabe lesen. 232 $this->data = implode('',file('php://input')); 233 } 234 235 /** 236 * HTTP-Methode OPTIONS.<br> 237 * <br> 238 * Es werden die verfuegbaren Methoden ermittelt und ausgegeben. 239 */ 240 public function davOPTIONS() 241 { 242 header('DAV: 1'); // Wir haben DAV-Level 1. 243 header('Allow: '.implode(', ',$this->allowed_methods()) ); 244 245 Logger::trace('OPTIONS: '.'Allow: '.implode(', ',$this->allowed_methods())); 246 247 $this->httpStatus( '200 OK' ); 248 } 249 250 251 252 253 254 /** 255 * WebDav-HEAD-Methode. 256 */ 257 public function davHEAD() 258 { 259 if ( ! $this->request->objectid ) 260 { 261 $this->httpStatus( '404 Not Found' ); 262 } 263 elseif ( $this->request->type == 'folder' ) 264 { 265 $this->httpStatus( '200 OK' ); 266 } 267 elseif( $this->obj->isPage ) 268 { 269 $this->httpStatus( '200 OK' ); 270 } 271 elseif( $this->obj->isLink ) 272 { 273 $this->httpStatus( '200 OK' ); 274 } 275 elseif( $this->obj->isFile ) 276 { 277 $this->httpStatus( '200 OK' ); 278 } 279 } 280 281 282 283 /** 284 * WebDav-GET-Methode. 285 * Die gew�nschte Datei wird geladen und im HTTP-Body mitgeliefert. 286 */ 287 public function davGET() 288 { 289 switch( $this->request->type ) 290 { 291 case 'root': 292 case 'folder': 293 $this->getDirectoryIndex(); 294 break; 295 296 case 'page': 297 $this->httpStatus( '200 OK' ); 298 header('Content-Type: text/html'); 299 300 echo '<html><head><title>OpenRat WEBDAV Access</title></head>'; 301 echo '<body>'; 302 echo '<h1>'.$this->request->type.'</h1>'; 303 echo '<pre>'; 304 echo 'No Content available'; 305 echo '</pre>'; 306 echo '</body>'; 307 echo '</html>'; 308 break; 309 310 case 'link': 311 $this->httpStatus( '200 OK' ); 312 313 header('Content-Type: text/plain'); 314 315 $link = $this->client->link( $this->request->objectid ); 316 echo 'url: ' .$link['url'] ."\n"; 317 echo 'target-id: '.$link['linkedObjectId']."\n"; 318 319 break; 320 321 case 'file': 322 case 'image': 323 case 'text': 324 $this->httpStatus( '200 OK' ); 325 326 $file = $this->client->file ( $this->request->objectid ); 327 $filevalue = $this->client->filevalue( $this->request->objectid ); 328 329 header('Content-Type: '.$file['mimetype']); 330 header('X-File-Id: ' .$this->request->objectid ); 331 332 // Angabe Content-Disposition 333 // - Bild soll "inline" gezeigt werden 334 // - Dateiname wird benutzt, wenn der Browser das Bild speichern moechte 335 header('Content-Disposition: inline; filename='.$file['filename'].'.'.$file['extension'] ); 336 header('Content-Transfer-Encoding: binary' ); 337 header('Content-Description: '.$file['filename'].'.'.$file['extension'] ); 338 339 // Groesse des Bildes in Bytes 340 // Der Browser hat so die Moeglichkeit, einen Fortschrittsbalken zu zeigen 341 header('Content-Length: '.$file['size'] ); 342 343 echo $filevalue; 344 345 break; 346 347 default: 348 $this->httpStatus( '200 OK' ); 349 header('Content-Type: text/html'); 350 351 echo '<html><head><title>OpenRat WEBDAV Access</title></head>'; 352 echo '<body>'; 353 echo '<h1>'.$this->request->type.'</h1>'; 354 echo '<pre>'; 355 echo 'Unknown node type: '.$this->request->type; 356 echo '</pre>'; 357 echo '</body>'; 358 echo '</html>'; 359 break; 360 361 362 } 363 } 364 365 366 367 /** 368 * Erzeugt ein Unix-�hnliche Ausgabe des Verzeichnisses als HTML. 369 */ 370 private function getDirectoryIndex() 371 { 372 $this->httpStatus( '200 OK' ); 373 374 // Verzeichnis ausgeben 375 header('Content-Type: text/html'); 376 $nl = "\n"; 377 378 379 380 381 $titel = 'Index of '.htmlspecialchars($this->sitePath); 382 $format = "%15s %-19s %-s\n"; 383 384 echo '<html><head><title>'.$titel.'</title></head>'; 385 echo '<body>'; 386 echo '<h1>'.$titel.'</h1>'.$nl; 387 echo '<pre>'; 388 389 printf($format, "Size", "Last modified", "Filename"); 390 391 392 switch( $this->request->type ) 393 { 394 case 'root': // Projektliste 395 396 $result = $this->client->projectlist(); 397 $projects = $result['projects']; 398 foreach( $projects as $projectid=>$p ) 399 { 400 echo '<a href="'.$p['name'].'">'.$p['name'].'</a>'.$nl; 401 } 402 break; 403 404 case 'folder': // Verzeichnisinhalt 405 406 $folder = $this->client->folder( $this->request->objectid ); 407 408 foreach( $folder['content']['object'] as $object ) 409 { 410 411 printf($format, 412 number_format(1), 413 strftime("%Y-%m-%d %H:%M:%S",$object['date'] ), 414 '<a href="./'.$object['filename'].'">'.$object['filename'].'</a>'); 415 echo $nl; 416 417 } 418 } 419 420 echo '</pre>'; 421 echo '</body>'; 422 echo '</html>'; 423 } 424 425 426 427 /** 428 * Die Methode LOCK sollte garnicht aufgerufen werden, da wir nur 429 * Dav-Level 1 implementieren und dies dem Client auch mitteilen.<br> 430 * <br> 431 * Ausgabe von HTTP-Status 412 (Precondition failed) 432 */ 433 function davLOCK() 434 { 435 $this->httpStatus('412 Precondition failed'); 436 $this->davOPTIONS(); 437 } 438 439 440 441 /** 442 * Die Methode UNLOCK sollte garnicht aufgerufen werden, da wir nur 443 * Dav-Level 1 implementieren und dies dem Client auch mitteilen.<br> 444 * <br> 445 * Ausgabe von HTTP-Status 412 (Precondition failed) 446 */ 447 public function davUNLOCK() 448 { 449 $this->httpStatus('412 Precondition failed'); 450 $this->davOPTIONS(); 451 } 452 453 454 455 /** 456 * Die Methode POST ist bei WebDav nicht sinnvoll.<br> 457 * <br> 458 * Ausgabe von HTTP-Status 405 (Method Not Allowed) 459 */ 460 public function davPOST() 461 { 462 // Die Methode POST ist bei Webdav nicht sinnvoll. 463 $this->httpStatus('405 Method Not Allowed' ); 464 } 465 466 467 468 /** 469 * Verzeichnis anlegen. 470 */ 471 public function davMKCOL() 472 { 473 474 if ( !empty($this->data) ) 475 { 476 $this->httpStatus('415 Unsupported Media Type' ); // Kein Body erlaubt 477 } 478 elseif ( $this->readonly ) 479 { 480 $this->httpStatus('403 Forbidden' ); // Kein Schreibzugriff erlaubt 481 } 482 elseif ( !$this->folder->hasRight( ACL_CREATE_FOLDER ) ) 483 { 484 $this->httpStatus('403 Forbidden' ); // Benutzer darf das nicht 485 } 486 elseif ( $this->obj == null ) 487 { 488 // Die URI ist noch nicht vorhanden 489 $f = new Folder(); 490 $f->filename = basename($this->sitePath); 491 $f->parentid = $this->folder->objectid; 492 $f->projectid = $this->project->projectid; 493 $f->add(); 494 $this->httpStatus('201 Created'); 495 } 496 else 497 { 498 // MKCOL ist nicht moeglich, wenn die URI schon existiert. 499 Logger::warn('MKCOL-Request to an existing resource'); 500 $this->httpStatus('405 Method Not Allowed' ); 501 } 502 } 503 504 505 506 /** 507 * Objekt l�schen. 508 */ 509 public function davDELETE() 510 { 511 if ( $this->readonly ) 512 { 513 $this->httpStatus('403 Forbidden' ); // Kein Schreibzugriff erlaubt 514 } 515 else 516 { 517 if ( $this->obj == null ) 518 { 519 // Nicht existente URIs kann man auch nicht loeschen. 520 $this->httpStatus('404 Not Found' ); 521 } 522 elseif ( ! $this->obj->hasRight( ACL_DELETE ) ) 523 { 524 $this->httpStatus('403 Forbidden' ); // Benutzer darf die Resource nicht loeschen 525 } 526 elseif ( $this->obj->isFolder ) 527 { 528 $f = new Folder( $this->obj->objectid ); 529 $f->deleteAll(); 530 $this->httpStatus( true ); // OK 531 Logger::debug('Deleted folder with id '.$this->obj->objectid ); 532 } 533 elseif ( $this->obj->isFile ) 534 { 535 $f = new File( $this->obj->objectid ); 536 $f->delete(); 537 $this->httpStatus( true ); // OK 538 } 539 elseif ( $this->obj->isPage ) 540 { 541 $p = new Page( $this->obj->objectid ); 542 $p->delete(); 543 $this->httpStatus( true ); // OK 544 } 545 elseif ( $this->obj->isLink ) 546 { 547 $l = new Link( $this->obj->objectid ); 548 $l->delete(); 549 $this->httpStatus( true ); // OK 550 } 551 552 } 553 } 554 555 556 557 /** 558 * Kopieren eines Objektes.<br> 559 * Momentan ist nur das Kopieren einer Datei implementiert.<br> 560 * Das Kopieren von Ordnern, Verkn�pfungen und Seiten ist nicht moeglich. 561 */ 562 public function davCOPY() 563 { 564 if ( $this->readonly || !$this->create ) 565 { 566 error_log('WEBDAV: COPY request, but readonly or no creating'); 567 $this->httpStatus('405 Not Allowed' ); 568 } 569 elseif( $this->obj == null ) 570 { 571 // Was nicht da ist, laesst sich auch nicht verschieben. 572 error_log('WEBDAV: COPY request, but Source not found'); 573 $this->httpStatus('405 Not Allowed' ); 574 } 575 elseif ( $this->destination == null ) 576 { 577 error_log('WEBDAV: COPY request, but no "Destination:"-Header'); 578 // $this->httpStatus('405 Not Allowed' ); 579 $this->httpStatus('412 Precondition failed'); 580 } 581 else 582 { 583 // URL parsen. 584 $dest = $this->destination; 585 $destinationProject = $dest['project']; 586 $destinationFolder = $dest['folder' ]; 587 $destinationObject = $dest['object' ]; 588 589 if ( $dest['type'] != 'object' ) 590 { 591 Logger::debug('WEBDAV: COPY request, but "Destination:"-Header mismatch'); 592 $this->httpStatus('405 Not Allowed'); 593 } 594 elseif ( $this->project->projectid != $destinationProject->projectid ) 595 { 596 // Kopieren in anderes Projekt nicht moeglich. 597 Logger::debug('WEBDAV: COPY request denied, project does not match'); 598 $this->httpStatus('403 Forbidden'); 599 } 600 elseif ( $destinationObject != null ) 601 { 602 Logger::debug('WEBDAV: COPY request denied, Destination exists. Overwriting is not supported'); 603 $this->httpStatus('403 Forbidden'); 604 } 605 elseif ( is_object($destinationFolder) && ! $destinationFolder->hasRight( ACL_CREATE_FILE ) ) 606 { 607 $this->httpStatus('403 Forbidden' ); // Benutzer darf das nicht 608 } 609 elseif ( is_object($destinationObject) && $destinationObject->isFolder) 610 { 611 Logger::debug('WEBDAV: COPY request denied, Folder-Copy not implemented'); 612 $this->httpStatus('405 Not Allowed'); 613 } 614 elseif ( is_object($destinationObject) && $destinationObject->isLink) 615 { 616 Logger::debug('WEBDAV: COPY request denied, Link copy not implemented'); 617 $this->httpStatus('405 Not Allowed'); 618 } 619 elseif ( is_object($destinationObject) && $destinationObject->isPage) 620 { 621 Logger::debug('WEBDAV: COPY request denied, Page copy not implemented'); 622 $this->httpStatus('405 Not Allowed'); 623 } 624 else 625 { 626 $f = new File(); 627 $f->filename = basename($_SERVER['HTTP_DESTINATION']); 628 $f->name = ''; 629 $f->parentid = $destinationFolder->objectid; 630 $f->projectid = $this->project->projectid; 631 $f->add(); 632 $f->copyValueFromFile( $this->obj->objectid ); 633 634 Logger::debug('WEBDAV: COPY request accepted' ); 635 // Objekt wird in anderen Ordner kopiert. 636 $this->httpStatus('201 Created' ); 637 } 638 } 639 640 } 641 642 643 644 /** 645 * Verschieben eines Objektes.<br> 646 * <br> 647 * Folgende Operationen sind m�glich:<br> 648 * - Unbenennen eines Objektes (alle Typen)<br> 649 * - Verschieben eines Objektes (alle Typen) in einen anderen Ordner.<br> 650 */ 651 public function davMOVE() 652 { 653 if ( $this->readonly ) 654 { 655 $this->httpStatus('403 Forbidden - Readonly Mode' ); // Schreibgeschuetzt 656 } 657 elseif ( !$this->create ) 658 { 659 $this->httpStatus('403 Forbidden - No creation' ); // Schreibgeschuetzt 660 } 661 elseif( $this->obj == null ) 662 { 663 // Was nicht da ist, laesst sich auch nicht verschieben. 664 $this->httpStatus('404 Not Found' ); 665 } 666 elseif( is_object($this->obj) && ! $this->obj->hasRight( ACL_WRITE ) ) 667 { 668 // Was nicht da ist, laesst sich auch nicht verschieben. 669 Logger::error('Source '.$this->obj->objectid.' is not writable: Forbidden'); 670 $this->httpStatus('403 Forbidden' ); 671 } 672 elseif ( $this->destination == null ) 673 { 674 Logger::error('WEBDAV: MOVE request, but no "Destination:"-Header'); 675 // $this->httpStatus('405 Not Allowed' ); 676 $this->httpStatus('412 Precondition failed'); 677 } 678 else 679 { 680 $dest = $this->destination; 681 $destinationProject = $dest['project']; 682 $destinationFolder = $dest['folder' ]; 683 $destinationObject = $dest['object' ]; 684 685 if ( $dest['type'] != 'object' ) 686 { 687 Logger::debug('WEBDAV: MOVE request, but "Destination:"-Header mismatch'); 688 $this->httpStatus('405 Not Allowed'); 689 return; 690 } 691 692 if ( is_object($destinationFolder) && ! $destinationFolder->hasRight( ACL_CREATE_FILE ) ) 693 { 694 Logger::error('Source '.$this->obj->objectid.' is not writable: Forbidden'); 695 $this->httpStatus('403 Forbidden' ); 696 } 697 698 if ( $destinationObject != null ) 699 { 700 Logger::debug('WEBDAV: MOVE request denied, destination exists'); 701 $this->httpStatus('412 Precondition Failed'); 702 return; 703 } 704 705 if ( $this->project->projectid != $destinationProject->projectid ) 706 { 707 // Verschieben in anderes Projekt nicht moeglich. 708 Logger::debug('WEBDAV: MOVE request denied, project does not match'); 709 $this->httpStatus('405 Not Allowed'); 710 return; 711 } 712 713 if ( $this->folder->objectid == $destinationFolder->objectid ) 714 { 715 Logger::debug('WEBDAV: MOVE request accepted, object renamed'); 716 // Resource bleibt in gleichem Ordner. 717 $this->obj->filename = basename($_SERVER['HTTP_DESTINATION']); 718 $this->obj->objectSave(false); 719 $this->httpStatus('201 Created' ); 720 return; 721 } 722 723 if ( $destinationFolder->isFolder ) 724 { 725 Logger::debug('WEBDAV: MOVE request accepted, Destination: '.$destinationFolder->filename ); 726 // Objekt wird in anderen Ordner verschoben. 727 $this->obj->setParentId( $destinationFolder->objectid ); 728 $this->httpStatus('201 Created' ); 729 return; 730 } 731 732 Logger::warn('WEBDAV: MOVE request failed' ); 733 $this->httpStatus('500 Internal Server Error' ); 734 } 735 } 736 737 738 739 /** 740 * Anlegen oder �berschreiben Dateien �ber PUT.<br> 741 * Dateien k�nnen neu angelegt und �berschrieben werden.<br> 742 * <br> 743 * Seiten k�nnen nicht �berschrieben werden. Wird versucht, 744 * eine Seite mit PUT zu �berschreiben, wird der Status "405 Not Allowed" gemeldet.<br> 745 */ 746 public function davPUT() 747 { 748 // TODO: 409 (Conflict) wenn übergeordneter Ordner nicht da. 749 750 if ( $this->readonly ) 751 { 752 $this->httpStatus('405 Not Allowed' ); 753 } 754 elseif ( strlen($this->data) > $this->maxFileSize*1000 ) 755 { 756 // Maximale Dateigroesse ueberschritten. 757 // Der Status 207 "Zuwenig Speicherplatz" passt nicht ganz, aber fast :) 758 $this->httpStatus('507 Insufficient Storage' ); 759 } 760 elseif ( ! $this->request->objectid ) 761 { 762 // Neue Datei anlegen 763 if ( !$this->create ) 764 { 765 Logger::warn('WEBDAV: Creation of files not allowed by configuration' ); 766 $this->httpStatus('405 Not Allowed' ); 767 } 768 769 $this->client->fileAdd( $this->data ); 770 $this->httpStatus('201 Created'); 771 return; 772 } 773 elseif ( $this->request->objectid ) 774 { 775 // Bestehende Datei ueberschreiben. 776 $id = $this->request->objectid; 777 $this->client->fileAdd( $id,$this->data ); 778 779 $this->httpStatus('204 No Content'); 780 return; 781 } 782 elseif ( $this->obj->isFolder ) 783 { 784 Logger::error('PUT on folder is not supported, use PROPFIND. Lame client?' ); 785 $this->httpStatus('405 Not Allowed' ); 786 } 787 else 788 { 789 // Fuer andere Objekttypen (Links, Seiten) ist kein PUT moeglich. 790 Logger::warn('PUT only available for files. Pages and links are ignored' ); 791 $this->httpStatus('405 Not Allowed' ); 792 } 793 } 794 795 796 797 /** 798 * WebDav-Methode PROPFIND. 799 * 800 * Diese Methode wird 801 * - beim Ermitteln von Verzeichnisinhalten und 802 * - beim Ermitteln von Metainformationen zu einer Datei 803 * verwendet. 804 * 805 * Das Ergebnis wird in einer XML-Zeichenkette geliefert. 806 */ 807 public function davPROPFIND() 808 { 809 switch( $this->request->type ) 810 { 811 case 'root': // Projektliste 812 813 $inhalte = array(); 814 815 $objektinhalt = array(); 816 $z = 30*365.25*24*60*60; 817 $objektinhalt['createdate' ] = $z; 818 $objektinhalt['lastchangedate'] = $z; 819 $objektinhalt['size' ] = 1; 820 $objektinhalt['name' ] = $this->sitePath; 821 $objektinhalt['displayname' ] = ''; 822 $objektinhalt['type'] = 'folder'; 823 824 $inhalte[] = $objektinhalt; 825 826 try { 827 $result = $this->client->projectlist(); 828 } catch( Exception $e) { 829 Logger::error($e->__toString().$this->client->__toString()); 830 throw $e; 831 } 832 $projects = $result['projects']; 833 foreach( $projects as $projectid=>$p ) 834 { 835 $objektinhalt = array(); 836 $objektinhalt['createdate' ] = TIME_20000101; 837 $objektinhalt['lastchangedate'] = TIME_20000101; 838 $objektinhalt['size' ] = 1; 839 $objektinhalt['name' ] = $this->sitePath.$p['name'].'/'; 840 $objektinhalt['displayname' ] = $p['name']; 841 $objektinhalt['type'] = 'folder'; 842 $inhalte[] = $objektinhalt; 843 } 844 845 $this->multiStatus( $inhalte ); 846 break; 847 848 case 'folder': // Verzeichnisinhalt 849 850 $folder = $this->client->folder( $this->request->objectid ); 851 852 $inhalte = array(); 853 854 $objektinhalt = array(); 855 $objektinhalt['createdate' ] = $folder['properties']['create_date']; 856 $objektinhalt['lastchangedate'] = $folder['properties']['lastchange_date']; 857 $objektinhalt['name' ] = $this->sitePath; 858 $objektinhalt['displayname' ] = $folder['properties']['filename']; 859 $objektinhalt['type' ] = 'folder'; 860 $objektinhalt['size' ] = 0; 861 862 $inhalte[] = $objektinhalt; 863 864 if ( $this->depth > 0 ) 865 { 866 867 foreach( $folder['content']['object'] as $object ) 868 { 869 $objektinhalt = array(); 870 $objektinhalt['createdate' ] = $object['date']; 871 $objektinhalt['lastchangedate'] = $object['date']; 872 $objektinhalt['displayname' ] = $object['filename']; 873 874 switch( $object['type'] ) 875 { 876 case 'folder': 877 $objektinhalt['name'] = $this->sitePath.$object['filename'].'/'; 878 $objektinhalt['type'] = 'folder'; 879 $objektinhalt['size'] = 0; 880 $inhalte[] = $objektinhalt; 881 break; 882 case 'file': 883 case 'image': 884 case 'text': 885 $objektinhalt['name'] = $this->sitePath.$object['filename']; 886 $objektinhalt['type'] = 'file'; 887 $objektinhalt['size'] = $object['size']; 888 $objektinhalt['mime'] = 'application/x-non-readable'; 889 $inhalte[] = $objektinhalt; 890 break; 891 case 'link': 892 $objektinhalt['name'] = $this->sitePath.$object['filename']; 893 $objektinhalt['type'] = 'file'; 894 $objektinhalt['size'] = 0; 895 $objektinhalt['mime'] = 'application/x-non-readable'; 896 $inhalte[] = $objektinhalt; 897 break; 898 case 'url': 899 case 'alias': 900 $objektinhalt['name'] = $this->sitePath.$object['filename']; 901 $objektinhalt['type'] = 'file'; 902 $objektinhalt['size'] = 0; 903 $objektinhalt['mime'] = 'application/x-non-readable'; 904 $inhalte[] = $objektinhalt; 905 break; 906 case 'page': 907 $objektinhalt['name'] = $this->sitePath.$object['filename']; 908 $objektinhalt['type'] = 'file'; 909 $objektinhalt['size'] = 0; 910 $inhalte[] = $objektinhalt; 911 break; 912 default: 913 } 914 } 915 } 916 $this->multiStatus( $inhalte ); 917 break; 918 919 case 'page': 920 $page = $this->client->page( $this->request->objectid ); 921 $prop = $page['properties']; 922 $objektinhalt = array(); 923 $objektinhalt['name'] = $this->sitePath; 924 $objektinhalt['displayname'] = $prop['filename']; 925 $objektinhalt['createdate' ] = $prop['date']; 926 $objektinhalt['lastchangedate'] = $prop['date']; 927 928 $objektinhalt['size' ] = 0; 929 $objektinhalt['type' ] = 'file'; 930 931 $this->multiStatus( array($objektinhalt) ); 932 933 break; 934 935 case 'file': 936 case 'text': 937 case 'image': 938 $file = $this->client->file( $this->request->objectid ); 939 $objektinhalt = array(); 940 $objektinhalt['name'] = $this->sitePath; 941 $objektinhalt['displayname'] = $file['filename']; 942 $objektinhalt['createdate' ] = $file['date']; 943 $objektinhalt['lastchangedate'] = $file['date']; 944 945 $objektinhalt['size' ] = $file['size']; 946 $objektinhalt['type' ] = 'file'; 947 948 $this->multiStatus( array($objektinhalt) ); 949 950 break; 951 952 case 'link': 953 954 $link = $this->client->link( $this->request->objectid ); 955 956 $objektinhalt = array(); 957 $objektinhalt['name'] = $this->sitePath; 958 $objektinhalt['displayname'] = $link['filename']; 959 $objektinhalt['createdate' ] = $link['date']; 960 $objektinhalt['lastchangedate'] = $link['date']; 961 962 $objektinhalt['size' ] = 0; 963 $objektinhalt['type' ] = 'file'; 964 965 966 $this->multiStatus( array($objektinhalt) ); 967 968 break; 969 970 case 'url': 971 972 $link = $this->client->link( $this->request->objectid ); 973 974 $objektinhalt = array(); 975 $objektinhalt['name'] = $this->sitePath; 976 $objektinhalt['displayname'] = $link['filename']; 977 $objektinhalt['createdate' ] = $link['date']; 978 $objektinhalt['lastchangedate'] = $link['date']; 979 980 $objektinhalt['size' ] = 0; 981 $objektinhalt['type' ] = 'file'; 982 983 984 $this->multiStatus( array($objektinhalt) ); 985 986 break; 987 988 default: 989 Logger::warn('Internal Error, unknown request type: '. $this->request->type); 990 $this->httpStatus('500 Internal Server Error'); 991 } 992 } 993 994 995 /** 996 * Webdav-Methode PROPPATCH ist nicht implementiert. 997 */ 998 public function davPROPPATCH() 999 { 1000 // TODO: Multistatus erzeugen. 1001 // Evtl. ist '409 Conflict' besser? 1002 $this->httpStatus('405 Not Allowed'); 1003 } 1004 1005 1006 /** 1007 * Erzeugt einen Multi-Status. 1008 * @access private 1009 */ 1010 private function multiStatus( $files ) 1011 { 1012 $this->httpStatus('207 Multi-Status'); 1013 header('Content-Type: text/xml; charset=utf-8'); 1014 1015 $response = ''; 1016 $response .= '<?xml version="1.0" encoding="utf-8" ?>'; 1017 $response .= '<d:multistatus xmlns:d="DAV:">'; 1018 1019 foreach( $files as $file ) 1020 $response .= $this->getResponse( $file['name'],$file ); 1021 1022 $response .= '</d:multistatus>'; 1023 1024 $response = utf8_encode($response); 1025 1026 header('Content-Length: '.strlen($response)); 1027 Logger::trace('Sending Multistatus:'."\n".$response); 1028 echo $response; 1029 } 1030 1031 1032 /** 1033 * Erzeugt ein "response"-Element, welches in ein "multistatus"-element verwendet werden kann. 1034 */ 1035 private function getResponse( $file,$options ) 1036 { 1037 // TODO: Nur angeforderte Elemente erzeugen. 1038 $response = ''; 1039 $response .= '<d:response>'; 1040 $response .= '<d:href>'.$file.'</d:href>'; 1041 $response .= '<d:propstat>'; 1042 $response .= '<d:prop>'; 1043 // $response .= '<d:source></d:source>'; 1044 $response .= '<d:creationdate>'.date('r',$options['createdate']).'</d:creationdate>'; 1045 $response .= '<d:displayname>'.$options['displayname'].'</d:displayname>'; 1046 $response .= '<d:getcontentlength>'.$options['size'].'</d:getcontentlength>'; 1047 $response .= '<d:getlastmodified xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">'.date('r',$options['lastchangedate']).'</d:getlastmodified>'; 1048 1049 if ( $options['type'] == 'folder') 1050 $response .= '<d:resourcetype><d:collection/></d:resourcetype>'; 1051 else 1052 $response .= '<d:resourcetype />'; 1053 1054 $response .= '<d:categories />'; 1055 $response .= '<d:fields></d:fields>'; 1056 1057 1058 1059 // $response .= '<d:getcontenttype>text/html</d:getcontenttype>'; 1060 // $response .= '<d:getcontentlength />'; 1061 // $response .= '<d:getcontentlanguage />'; 1062 // $response .= '<d:executable />'; 1063 // $response .= '<d:resourcetype>'; 1064 // $response .= '<d:collection />'; 1065 // $response .= '</d:resourcetype>'; 1066 // $response .= '<d:getetag />'; 1067 1068 $response .= '</d:prop>'; 1069 $response .= '<d:status>HTTP/1.1 200 OK</d:status>'; 1070 $response .= '</d:propstat>'; 1071 $response .= '</d:response>'; 1072 1073 return $response; 1074 } 1075 1076 1077 1078 /** 1079 * Setzt einen HTTP-Status.<br> 1080 * <br> 1081 * Es wird ein HTTP-Status gesetzt, zus�tzlich wird der Status in den Header "X-WebDAV-Status" geschrieben.<br> 1082 * Ist der Status nicht 200 oder 207 (hier folgt ein BODY), wird das Skript beendet. 1083 */ 1084 protected function httpStatus( $status = true ) 1085 { 1086 if ( $status === true ) 1087 $status = '200 OK'; 1088 1089 // Logger::debug('WEBDAV: HTTP-Status: '.$status); 1090 1091 header('HTTP/1.1 '.$status); 1092 header('X-WebDAV-Status: '.$status,true); 1093 1094 // RFC 2616 (HTTP/1.1), Section 10.4.6 "405 Method Not Allowed" says: 1095 // "[...] The response MUST include an 1096 // Allow header containing a list of valid methods for the requested 1097 // resource." 1098 // 1099 // RFC 2616 (HTTP/1.1), Section 14.7 "Allow" says: 1100 // "[...] An Allow header field MUST be 1101 // present in a 405 (Method Not Allowed) response." 1102 if ( substr($status,0,3) == '405' ) 1103 header('Allow: '.implode(', ',$this->allowed_methods()) ); 1104 } 1105 1106 1107 1108 1109 private function allowed_methods() 1110 { 1111 1112 if ($this->readonly) 1113 return array('OPTIONS','HEAD','GET','PROPFIND'); // Readonly-Modus 1114 else 1115 // PROPPATCH unterstuetzen wir garnicht, aber lt. Spec sollten wir das. 1116 return array('OPTIONS','HEAD','GET','PROPFIND','DELETE','PUT','COPY','MOVE','MKCOL','PROPPATCH'); 1117 } 1118 1119 1120 1121 private function siteURL() 1122 { 1123 $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; 1124 $domainName = $_SERVER['HTTP_HOST']; 1125 return $protocol.$domainName; 1126 } 1127 1128 private function slashify( $path ) 1129 { 1130 return $path.( substr( $path,-1 ) != '/' )?'/':''; 1131 } 1132 1133 public function __destruct() 1134 { 1135 $this->store[$this->username] = $this->client->client->cookies; 1136 shm_put_var($this->shm,self::MEMKEY,$this->store); 1137 } 1138 }