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