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