ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
Server.php
Go to the documentation of this file.
1 <?php
2 //
3 // +----------------------------------------------------------------------+
4 // | PHP Version 4 |
5 // +----------------------------------------------------------------------+
6 // | Copyright (c) 1997-2003 The PHP Group |
7 // +----------------------------------------------------------------------+
8 // | This source file is subject to version 2.02 of the PHP license, |
9 // | that is bundled with this package in the file LICENSE, and is |
10 // | available at through the world-wide-web at |
11 // | http://www.php.net/license/2_02.txt. |
12 // | If you did not receive a copy of the PHP license and are unable to |
13 // | obtain it through the world-wide-web, please send a note to |
14 // | license@php.net so we can mail you a copy immediately. |
15 // +----------------------------------------------------------------------+
16 // | Authors: Hartmut Holzgraefe <hholzgra@php.net> |
17 // | Christian Stocker <chregu@bitflux.ch> |
18 // +----------------------------------------------------------------------+
19 //
20 // $Id: Server.php,v 1.28 2005/04/05 22:51:09 hholzgra Exp $
21 //
22 
23 require_once "Services/WebDAV/classes/Tools/_parse_propfind.php";
24 require_once "Services/WebDAV/classes/Tools/_parse_proppatch.php";
25 require_once "Services/WebDAV/classes/Tools/_parse_lockinfo.php";
26 
27 
38 {
39  // {{{ Member Variables
40 
46  public $uri;
47 
48 
54  public $base_uri;
55 
56 
62  public $path;
63 
69  public $http_auth_realm = "PHP WebDAV";
70 
76  public $dav_powered_by = "";
77 
83  public $_if_header_uris = array();
84 
90  public $_http_status = "200 OK";
91 
97  public $_prop_encoding = "utf-8";
98 
99  // }}}
100 
101  // {{{ Constructor
102 
108  private function __construct()
109  {
110  // PHP messages destroy XML output -> switch them off
111  //ini_set("display_errors", 0);
112  }
113 
114  // }}}
115 
116  // {{{ ServeRequest()
125  public function serveRequest()
126  {
127  // default uri is the complete request uri
128  // FIXME: use ilHTTPS::isDetected
129  $uri = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:");
130  $uri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]";
131 
132  $this->base_uri = $uri;
133  $this->uri = $uri . $_SERVER[PATH_INFO];
134 
135  // identify ourselves
136  if (empty($this->dav_powered_by)) {
137  header("X-Dav-Powered-By: PHP class: " . get_class($this));
138  } else {
139  header("X-Dav-Powered-By: " . $this->dav_powered_by);
140  }
141 
142  $this->writelog(__METHOD__ . ': Using uri: ' . $this->uri);
143 
144  // check authentication
145  if (!$this->_check_auth()) {
146  // RFC2518 says we must use Digest instead of Basic
147  // but Microsoft Clients do not support Digest
148  // and we don't support NTLM and Kerberos
149  // so we are stuck with Basic here
150  header('WWW-Authenticate: Basic realm="' . ($this->http_auth_realm) . '"');
151 
152  // Windows seems to require this being the last header sent
153  // (changed according to PECL bug #3138)
154  $this->http_status('401 Unauthorized');
155  $this->writelog('Check auth failed');
156 
157  return;
158  }
159 
160  // check
161  if (!$this->_check_if_header_conditions()) {
162  $this->writelog(__METHOD__ . ': Precondition failed.');
163  $this->http_status("412 Precondition failed");
164  return;
165  }
166 
167  // set path
168  $this->path = $this->_urldecode($_SERVER["PATH_INFO"]);
169  if (!strlen($this->path)) {
170  header("Location: " . $this->base_uri . "/");
171  $this->writelog('HTTP_WebDAV_Server.ServeRequest() missing path info');
172  $this->path = '/';
173  //exit;
174  }
175  // BEGIN WebDAV: Don't strip backslashes. Backslashes are a valid part of a unix filename!
176  /*
177  if(ini_get("magic_quotes_gpc")) {
178  $this->path = stripslashes($this->path);
179  }*/
180  // END PATCH WebDAV: Don't strip backslashes. Backslashes are a valid part of a unix filename!
181 
182 
183  // detect requested method names
184  $method = strtolower($_SERVER["REQUEST_METHOD"]);
185  $wrapper = "http_" . $method;
186 
187  $this->writelog(__METHOD__ . ': Using request method: ' . $method);
188 
189  // activate HEAD emulation by GET if no HEAD method found
190  if ($method == "head" && !method_exists($this, "head")) {
191  $method = "get";
192  $this->writelog(__METHOD__ . ': Using head emulation by get.');
193  }
194 
195  if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) {
196  $this->writelog(__METHOD__ . ': Calling wrapper: ' . $wrapper);
197  $this->$wrapper(); // call method by name
198  } else { // method not found/implemented
199  if ($_SERVER["REQUEST_METHOD"] == "LOCK") {
200  $this->writelog(__METHOD__ . ': Method not found/implemented. Sending 412');
201  $this->http_status("412 Precondition failed");
202  } else {
203  $this->writelog(__METHOD__ . ': Method not found/implemented. Sending allowd methods');
204  $this->http_status("405 Method not allowed");
205  header("Allow: " . join(", ", $this->_allow())); // tell client what's allowed
206  }
207  }
208  }
209 
210  // }}}
211 
212  // {{{ abstract WebDAV methods
213 
214  // {{{ GET()
233  /* abstract
234  function GET(&$params)
235  {
236  // dummy entry for PHPDoc
237  }
238  */
239 
240  // }}}
241 
242  // {{{ PUT()
253  /* abstract
254  function PUT()
255  {
256  // dummy entry for PHPDoc
257  }
258  */
259 
260  // }}}
261 
262  // {{{ COPY()
263 
274  /* abstract
275  function COPY()
276  {
277  // dummy entry for PHPDoc
278  }
279  */
280 
281  // }}}
282 
283  // {{{ MOVE()
284 
295  /* abstract
296  function MOVE()
297  {
298  // dummy entry for PHPDoc
299  }
300  */
301 
302  // }}}
303 
304  // {{{ DELETE()
305 
316  /* abstract
317  function DELETE()
318  {
319  // dummy entry for PHPDoc
320  }
321  */
322  // }}}
323 
324  // {{{ PROPFIND()
325 
336  /* abstract
337  function PROPFIND()
338  {
339  // dummy entry for PHPDoc
340  }
341  */
342 
343  // }}}
344 
345  // {{{ PROPPATCH()
346 
357  /* abstract
358  function PROPPATCH()
359  {
360  // dummy entry for PHPDoc
361  }
362  */
363  // }}}
364 
365  // {{{ LOCK()
366 
377  /* abstract
378  function LOCK()
379  {
380  // dummy entry for PHPDoc
381  }
382  */
383  // }}}
384 
385  // {{{ UNLOCK()
386 
397  /* abstract
398  function UNLOCK()
399  {
400  // dummy entry for PHPDoc
401  }
402  */
403  // }}}
404 
405  // }}}
406 
407  // {{{ other abstract methods
408 
409  // {{{ check_auth()
410 
423  /* abstract
424  function checkAuth($type, $username, $password)
425  {
426  // dummy entry for PHPDoc
427  }
428  */
429 
430  // }}}
431 
432  // {{{ checklock()
433 
446  /* abstract
447  function checklock($resource)
448  {
449  // dummy entry for PHPDoc
450  }
451  */
452 
453  // }}}
454 
455  // }}}
456 
457  // {{{ WebDAV HTTP method wrappers
458 
459  // {{{ http_OPTIONS()
460 
471  public function http_OPTIONS()
472  {
473  // Microsoft clients default to the Frontpage protocol
474  // unless we tell them to use WebDAV
475  header("MS-Author-Via: DAV");
476 
477  // get allowed methods
478  $allow = $this->_allow();
479 
480  // dav header
481  $dav = array(1); // assume we are always dav class 1 compliant
482  if (isset($allow['LOCK'])) {
483  $dav[] = 2; // dav class 2 requires that locking is supported
484  }
485 
486  // tell clients what we found
487  $this->http_status("200 OK");
488  header("DAV: " . join(",", $dav));
489  header("Allow: " . join(", ", $allow));
490  $this->writelog(__METHOD__ . ': dav=' . var_export($dav, true) . ' allow=' . var_export($allow, true));
491  header("Content-length: 0");
492  }
493 
494  // }}}
495 
496 
497  // {{{ http_PROPFIND()
498 
505  public function http_PROPFIND()
506  {
507  $options = array();
508  $options["path"] = $this->path;
509 
510  // search depth from header (default is "infinity)
511  if (isset($_SERVER['HTTP_DEPTH'])) {
512  $options["depth"] = $_SERVER["HTTP_DEPTH"];
513  } else {
514  $options["depth"] = "infinity";
515  }
516 
517  // analyze request payload
518  $propinfo = new _parse_propfind("php://input");
519  if (!$propinfo->success) {
520  $this->http_status("400 Error");
521  return;
522  }
523  $options['props'] = $propinfo->props;
524 
525  // call user handler
526  $files = array();
527  if (!$this->propfind($options, $files)) {
528  $this->http_status("404 Not Found");
529  return;
530  }
531 
532  // collect namespaces here
533  $ns_hash = array();
534 
535  // Microsoft Clients need this special namespace for date and time values
536  $ns_defs = "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\"";
537 
538  // now we loop over all returned file entries
539  foreach ($files["files"] as $filekey => $file) {
540 
541  // nothing to do if no properties were returend for a file
542  if (!isset($file["props"]) || !is_array($file["props"])) {
543  continue;
544  }
545 
546  // now loop over all returned properties
547  foreach ($file["props"] as $key => $prop) {
548  // as a convenience feature we do not require that user handlers
549  // restrict returned properties to the requested ones
550  // here we strip all unrequested entries out of the response
551 
552  switch ($options['props']) {
553  case "all":
554  // nothing to remove
555  break;
556 
557  case "names":
558  // only the names of all existing properties were requested
559  // so we remove all values
560  unset($files["files"][$filekey]["props"][$key]["val"]);
561  break;
562 
563  default:
564  $found = false;
565 
566  // search property name in requested properties
567  foreach ((array) $options["props"] as $reqprop) {
568  if ($reqprop["name"] == $prop["name"]
569  && $reqprop["xmlns"] == $prop["ns"]) {
570  $found = true;
571  break;
572  }
573  }
574 
575  // unset property and continue with next one if not found/requested
576  if (!$found) {
577  $files["files"][$filekey]["props"][$key]="";
578  continue(2);
579  }
580  break;
581  }
582 
583  // namespace handling
584  if (empty($prop["ns"])) {
585  continue;
586  } // no namespace
587  $ns = $prop["ns"];
588  if ($ns == "DAV:") {
589  continue;
590  } // default namespace
591  if (isset($ns_hash[$ns])) {
592  continue;
593  } // already known
594 
595  // register namespace
596  $ns_name = "ns" . (count($ns_hash) + 1);
597  $ns_hash[$ns] = $ns_name;
598  $ns_defs .= " xmlns:$ns_name=\"$ns\"";
599  }
600 
601  // we also need to add empty entries for properties that were requested
602  // but for which no values where returned by the user handler
603  if (is_array($options['props'])) {
604  foreach ($options["props"] as $reqprop) {
605  if ($reqprop['name']=="") {
606  continue;
607  } // skip empty entries
608 
609  $found = false;
610 
611  // check if property exists in result
612  foreach ($file["props"] as $prop) {
613  if ($reqprop["name"] == $prop["name"]
614  && $reqprop["xmlns"] == $prop["ns"]) {
615  $found = true;
616  break;
617  }
618  }
619 
620  if (!$found) {
621  if ($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
622  // lockdiscovery is handled by the base class
623  $files["files"][$filekey]["props"][]
624  = $this->mkprop(
625  "DAV:",
626  "lockdiscovery",
627  $this->lockdiscovery($files["files"][$filekey]['path'])
628  );
629  } else {
630  // add empty value for this property
631  $files["files"][$filekey]["noprops"][] =
632  $this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
633 
634  // register property namespace if not known yet
635  if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) {
636  $ns_name = "ns" . (count($ns_hash) + 1);
637  $ns_hash[$reqprop["xmlns"]] = $ns_name;
638  $ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\"";
639  }
640  }
641  }
642  }
643  }
644  }
645 
646  // now we generate the reply header ...
647  $this->http_status("207 Multi-Status");
648  header('Content-Type: text/xml; charset="utf-8"');
649 
650  // ... and payload
651  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
652  echo "<D:multistatus xmlns:D=\"DAV:\">\n";
653 
654  foreach ($files["files"] as $file) {
655  // ignore empty or incomplete entries
656  if (!is_array($file) || empty($file) || !isset($file["path"])) {
657  continue;
658  }
659  $path = $file['path'];
660  if (!is_string($path) || $path==="") {
661  continue;
662  }
663 
664  echo " <D:response $ns_defs>\n";
665 
666  // BEGIN WebDAV W. Randelshofer Don't slashify path because it confuses Mac OS X
667  //$href = $this->_slashify($_SERVER['SCRIPT_NAME'] . $path);
668  $href = $_SERVER['SCRIPT_NAME'] . $path;
669  //END PATCH WebDAV W. Randelshofer
670 
671  echo " <D:href>$href</D:href>\n";
672 
673  // report all found properties and their values (if any)
674  if (isset($file["props"]) && is_array($file["props"])) {
675  echo " <D:propstat>\n";
676  echo " <D:prop>\n";
677 
678  foreach ($file["props"] as $key => $prop) {
679  if (!is_array($prop)) {
680  continue;
681  }
682  if (!isset($prop["name"])) {
683  continue;
684  }
685  if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
686  // empty properties (cannot use empty() for check as "0" is a legal value here)
687  if ($prop["ns"]=="DAV:") {
688  echo " <D:$prop[name]/>\n";
689  } elseif (!empty($prop["ns"])) {
690  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
691  } else {
692  echo " <$prop[name] xmlns=\"\"/>";
693  }
694  } elseif ($prop["ns"] == "DAV:") {
695  // some WebDAV properties need special treatment
696  switch ($prop["name"]) {
697  case "creationdate":
698  echo " <D:creationdate ns0:dt=\"dateTime.tz\">"
699  // BEGIN WebDAV W. Randelshofer
700  . gmdate("Y-m-d\\TH:i:s\\Z", $prop['val'])
701  // . gmdate("D, d M Y H:i:s ", $prop['val'])
702  // END PATCH WebDAV W. Randelshofer
703  . "</D:creationdate>\n";
704  break;
705  case "getlastmodified":
706  echo " <D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"
707  . gmdate("D, d M Y H:i:s ", $prop['val'])
708  . "GMT</D:getlastmodified>\n";
709  break;
710  case "resourcetype":
711  echo " <D:resourcetype><D:$prop[val]/></D:resourcetype>\n";
712  break;
713  case "supportedlock":
714  echo " <D:supportedlock>$prop[val]</D:supportedlock>\n";
715  break;
716  case "lockdiscovery":
717  echo " <D:lockdiscovery>\n";
718  echo $prop["val"];
719  echo " </D:lockdiscovery>\n";
720  break;
721  default:
722  echo " <D:$prop[name]>"
723  . $this->_prop_encode(htmlspecialchars($prop['val']))
724  . "</D:$prop[name]>\n";
725  break;
726  }
727  } else {
728  // properties from namespaces != "DAV:" or without any namespace
729  if ($prop["ns"]) {
730  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>"
731  . $this->_prop_encode(htmlspecialchars($prop['val']))
732  . "</" . $ns_hash[$prop["ns"]] . ":$prop[name]>\n";
733  } else {
734  echo " <$prop[name] xmlns=\"\">"
735  . $this->_prop_encode(htmlspecialchars($prop['val']))
736  . "</$prop[name]>\n";
737  }
738  }
739  }
740 
741  echo " </D:prop>\n";
742  echo " <D:status>HTTP/1.1 200 OK</D:status>\n";
743  echo " </D:propstat>\n";
744  }
745 
746  // now report all properties requested but not found
747  if (isset($file["noprops"])) {
748  echo " <D:propstat>\n";
749  echo " <D:prop>\n";
750 
751  foreach ($file["noprops"] as $key => $prop) {
752  if ($prop["ns"] == "DAV:") {
753  echo " <D:$prop[name]/>\n";
754  } elseif ($prop["ns"] == "") {
755  echo " <$prop[name] xmlns=\"\"/>\n";
756  } else {
757  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
758  }
759  }
760 
761  echo " </D:prop>\n";
762  echo " <D:status>HTTP/1.1 404 Not Found</D:status>\n";
763  echo " </D:propstat>\n";
764  }
765 
766  echo " </D:response>\n";
767  }
768 
769  echo "</D:multistatus>\n";
770  }
771 
772 
773  // }}}
774 
775  // {{{ http_PROPPATCH()
776 
783  public function http_PROPPATCH()
784  {
785  if ($this->_check_lock_status($this->path)) {
786  $options = array();
787  $options["path"] = $this->path;
788 
789  $propinfo = new _parse_proppatch("php://input");
790 
791  if (!$propinfo->success) {
792  $this->http_status("400 Error");
793  return;
794  }
795 
796  $options['props'] = $propinfo->props;
797 
798  $responsedescr = $this->proppatch($options);
799 
800  $this->http_status("207 Multi-Status");
801  header('Content-Type: text/xml; charset="utf-8"');
802 
803  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
804 
805  echo "<D:multistatus xmlns:D=\"DAV:\">\n";
806  echo " <D:response>\n";
807  echo " <D:href>" . $this->_urlencode($_SERVER["SCRIPT_NAME"] . $this->path) . "</D:href>\n";
808 
809  foreach ($options["props"] as $prop) {
810  echo " <D:propstat>\n";
811  echo " <D:prop><$prop[name] xmlns=\"$prop[ns]\"/></D:prop>\n";
812  echo " <D:status>HTTP/1.1 $prop[status]</D:status>\n";
813  echo " </D:propstat>\n";
814  }
815 
816  if ($responsedescr) {
817  echo " <D:responsedescription>" .
818  $this->_prop_encode(htmlspecialchars($responsedescr)) .
819  "</D:responsedescription>\n";
820  }
821 
822  echo " </D:response>\n";
823  echo "</D:multistatus>\n";
824  } else {
825  $this->http_status("423 Locked");
826  }
827  }
828 
829  // }}}
830 
831 
832  // {{{ http_MKCOL()
833 
840  public function http_MKCOL()
841  {
842  $options = array();
843  $options["path"] = $this->path;
844 
845  $stat = $this->mkcol($options);
846 
847  $this->http_status($stat);
848  }
849 
850  // }}}
851 
852 
853  // {{{ http_GET()
854 
861  public function http_GET()
862  {
863  // TODO check for invalid stream
864  $options = array();
865  $options["path"] = $this->path;
866 
867  $this->_get_ranges($options);
868 
869  if (true === ($status = $this->get($options))) {
870  if (!headers_sent()) {
871  $status = "200 OK";
872 
873  if (!isset($options['mimetype'])) {
874  $options['mimetype'] = "application/octet-stream";
875  }
876  header("Content-type: $options[mimetype]");
877 
878  if (isset($options['mtime'])) {
879  header("Last-modified:" . gmdate("D, d M Y H:i:s ", $options['mtime']) . "GMT");
880  }
881 
882  if (isset($options['stream'])) {
883  // GET handler returned a stream
884  if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
885  // partial request and stream is seekable
886 
887  if (count($options['ranges']) === 1) {
888  $range = $options['ranges'][0];
889 
890  if (isset($range['start'])) {
891  fseek($options['stream'], $range['start'], SEEK_SET);
892  if (feof($options['stream'])) {
893  $this->http_status("416 Requested range not satisfiable");
894  exit;
895  }
896 
897  if (isset($range['end']) && $range['end'] != '') {
898  $size = $range['end']-$range['start']+1;
899  $this->http_status("206 partial");
900  header("Content-length: $size");
901  header("Content-range: $range[start]-$range[end]/"
902  . (isset($options['size']) ? $options['size'] : "*"));
903  while ($size && !feof($options['stream'])) {
904  $buffer = fread($options['stream'], 4096);
905  $size -= strlen($buffer);
906  echo $buffer;
907  }
908  } else {
909  $this->http_status("206 partial");
910  if (isset($options['size'])) {
911  header("Content-length: " . ($options['size'] - $range['start']));
912  header("Content-range: $start-$end/"
913  . (isset($options['size']) ? $options['size'] : "*"));
914  }
915  fpassthru($options['stream']);
916  }
917  } else {
918  header("Content-length: " . $range['last']);
919  fseek($options['stream'], -$range['last'], SEEK_END);
920  fpassthru($options['stream']);
921  }
922  } else {
923  $this->_multipart_byterange_header(); // init multipart
924  foreach ($options['ranges'] as $range) {
925  // TODO what if size unknown? 500?
926  if (isset($range['start'])) {
927  $from = $range['start'];
928  $to = !empty($range['end']) ? $range['end'] : $options['size']-1;
929  } else {
930  $from = $options['size'] - $range['last']-1;
931  $to = $options['size'] -1;
932  }
933  $total = isset($options['size']) ? $options['size'] : "*";
934  $size = $to - $from + 1;
935  $this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
936 
937 
938  fseek($options['stream'], $start, SEEK_SET);
939  while ($size && !feof($options['stream'])) {
940  $buffer = fread($options['stream'], 4096);
941  $size -= strlen($buffer);
942  echo $buffer;
943  }
944  }
945  $this->_multipart_byterange_header(); // end multipart
946  }
947  } else {
948  // normal request or stream isn't seekable, return full content
949  if (isset($options['size'])) {
950  header("Content-length: " . $options['size']);
951  }
952 
953  // BEGIN WebDAV W. Randelshofer
954  // fpassthru apparently only delivers up to 2 million bytes.
955  // use fread instead
956  //fpassthru($options['stream']);
957  while (!feof($options['stream'])) {
958  $buffer = fread($options['stream'], 4096);
959  echo $buffer;
960  }
961  // END PATCH WebDAV W. Randelshofer
962 
963  return; // no more headers
964  }
965  } elseif (isset($options['data'])) {
966  if (is_array($options['data'])) {
967  // reply to partial request
968  } else {
969  header("Content-length: " . strlen($options['data']));
970  echo $options['data'];
971  }
972  }
973  }
974  }
975 
976  if (false === $status) {
977  // BEGIN WebDAV Randelshofer
978  $status = '404 Not Found';
979  //$this->http_status("404 not found");
980  // END PATCH WebDAV Randelshofer
981  }
982 
983  if (!headers_sent()) {
984  // TODO: check setting of headers in various code pathes above
985  $this->http_status("$status");
986  }
987  }
988 
989 
996  public function _get_ranges(&$options)
997  {
998  // process Range: header if present
999  if (isset($_SERVER['HTTP_RANGE'])) {
1000 
1001  // we only support standard "bytes" range specifications for now
1002  if (preg_match("/bytes[[:space:]]*=[[:space:]]*(.+)/", $_SERVER['HTTP_RANGE'], $matches)) {
1003  $options["ranges"] = array();
1004 
1005  // ranges are comma separated
1006  foreach (explode(",", $matches[1]) as $range) {
1007  // ranges are either from-to pairs or just end positions
1008  list($start, $end) = explode("-", $range);
1009  $options["ranges"][] = ($start==="")
1010  ? array("last"=>$end)
1011  : array("start"=>$start, "end"=>$end);
1012  }
1013  }
1014  }
1015  }
1016 
1030  public function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false)
1031  {
1032  if ($mimetype === false) {
1033  if (!isset($this->multipart_separator)) {
1034  // initial
1035 
1036  // a little naive, this sequence *might* be part of the content
1037  // but it's really not likely and rather expensive to check
1038  $this->multipart_separator = "SEPARATOR_" . md5(microtime());
1039 
1040  // generate HTTP header
1041  header("Content-type: multipart/byteranges; boundary=" . $this->multipart_separator);
1042  } else {
1043  // final
1044 
1045  // generate closing multipart sequence
1046  echo "\n--{$this->multipart_separator}--";
1047  }
1048  } else {
1049  // generate separator and header for next part
1050  echo "\n--{$this->multipart_separator}\n";
1051  echo "Content-type: $mimetype\n";
1052  echo "Content-range: $from-$to/" . ($total === false ? "*" : $total);
1053  echo "\n\n";
1054  }
1055  }
1056 
1057 
1058 
1059  // }}}
1060 
1061  // {{{ http_HEAD()
1062 
1069  public function http_HEAD()
1070  {
1071  $status = false;
1072  $options = array();
1073  $options["path"] = $this->path;
1074 
1075  if (method_exists($this, "HEAD")) {
1076  $status = $this->head($options);
1077  } elseif (method_exists($this, "GET")) {
1078  ob_start();
1079  $status = $this->GET($options);
1080  ob_end_clean();
1081  }
1082 
1083  if ($status===true) {
1084  $status = "200 OK";
1085  }
1086  if ($status===false) {
1087  $status = "404 Not found";
1088  }
1089 
1090  $this->http_status($status);
1091  }
1092 
1093  // }}}
1094 
1095  // {{{ http_PUT()
1096 
1103  public function http_PUT()
1104  {
1105  if ($this->_check_lock_status($this->path)) {
1106  $options = array();
1107  $options["path"] = $this->path;
1108  $options["content_length"] = $_SERVER["CONTENT_LENGTH"];
1109 
1110  // get the Content-type
1111  if (isset($_SERVER["CONTENT_TYPE"])) {
1112  // for now we do not support any sort of multipart requests
1113  if (!strncmp($_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
1114  $this->http_status("501 not implemented");
1115  echo "The service does not support mulipart PUT requests";
1116  return;
1117  }
1118  $options["content_type"] = $_SERVER["CONTENT_TYPE"];
1119  } else {
1120  // default content type if none given
1121  $options["content_type"] = "application/octet-stream";
1122  }
1123 
1124  /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1125  ignore any Content-* (e.g. Content-Range) headers that it
1126  does not understand or implement and MUST return a 501
1127  (Not Implemented) response in such cases."
1128  */
1129  foreach ($_SERVER as $key => $val) {
1130  if (strncmp($key, "HTTP_CONTENT", 11)) {
1131  continue;
1132  }
1133  switch ($key) {
1134  case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1135  // TODO support this if ext/zlib filters are available
1136  $this->http_status("501 not implemented");
1137  echo "The service does not support '$val' content encoding";
1138  return;
1139 
1140  case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1141  // we assume it is not critical if this one is ignored
1142  // in the actual PUT implementation ...
1143  $options["content_language"] = $value;
1144  break;
1145 
1146  case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1147  /* The meaning of the Content-Location header in PUT
1148  or POST requests is undefined; servers are free
1149  to ignore it in those cases. */
1150  break;
1151 
1152  case 'HTTP_CONTENT_RANGE': // RFC 2616 14.16
1153  // single byte range requests are supported
1154  // the header format is also specified in RFC 2616 14.16
1155  // TODO we have to ensure that implementations support this or send 501 instead
1156  if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $value, $matches)) {
1157  $this->http_status("400 bad request");
1158  echo "The service does only support single byte ranges";
1159  return;
1160  }
1161 
1162  $range = array("start"=>$matches[1], "end"=>$matches[2]);
1163  if (is_numeric($matches[3])) {
1164  $range["total_length"] = $matches[3];
1165  }
1166  $option["ranges"][] = $range;
1167 
1168  // TODO make sure the implementation supports partial PUT
1169  // this has to be done in advance to avoid data being overwritten
1170  // on implementations that do not support this ...
1171  break;
1172 
1173  case 'HTTP_CONTENT_MD5': // RFC 2616 14.15
1174  // TODO: maybe we can just pretend here?
1175  $this->http_status("501 not implemented");
1176  echo "The service does not support content MD5 checksum verification";
1177  return;
1178 
1179  case 'HTTP_CONTENT_LENGTH':
1180  // defined on IIS and has the same value as CONTENT_LENGTH
1181  break;
1182 
1183  default:
1184  // any other unknown Content-* headers
1185  $this->http_status("501 not implemented");
1186  echo "The service does not support '$key'";
1187  return;
1188  }
1189  }
1190 
1191  $options["stream"] = fopen("php://input", "r");
1192 
1193  $stat = $this->PUT($options);
1194 
1195  if ($stat == false) {
1196  $stat = "403 Forbidden";
1197  } elseif (is_resource($stat) && get_resource_type($stat) == "stream") {
1198  $stream = $stat;
1199 
1200  $stat = $options["new"] ? "201 Created" : "204 No Content";
1201 
1202  if (!empty($options["ranges"])) {
1203  // TODO multipart support is missing (see also above)
1204  if (0 == fseek($stream, $range[0]["start"], SEEK_SET)) {
1205  $length = $range[0]["end"]-$range[0]["start"]+1;
1206  if (!fwrite($stream, fread($options["stream"], $length))) {
1207  $stat = "403 Forbidden";
1208  }
1209  } else {
1210  $stat = "403 Forbidden";
1211  }
1212  } else {
1213  while (!feof($options["stream"])) {
1214  // BEGIN WebDAV W. Randelshofer explicitly compare with false.
1215  if (false === ($written = fwrite($stream, fread($options["stream"], 4096)))) {
1216  // END WebDAV W. Randelshofer explicitly compare with false.
1217  $stat = "403 Forbidden";
1218  break;
1219  }
1220  $count += $written;
1221  }
1222  }
1223 
1224  fclose($stream);
1225  //$this->writelog('PUT wrote '.$written.' bytes');
1226  // BEGIN WebDAV W. Randelshofer finish the put-operation
1227  $this->PUTfinished($options);
1228  // END WebDAV W. Randelshofer finish the put-operation
1229  }
1230 
1231  $this->http_status($stat);
1232  } else {
1233  $this->http_status("423 Locked");
1234  }
1235  }
1236 
1237  // }}}
1238 
1239 
1240  // {{{ http_DELETE()
1241 
1248  public function http_DELETE()
1249  {
1250  // check RFC 2518 Section 9.2, last paragraph
1251  if (isset($_SERVER["HTTP_DEPTH"])) {
1252  if ($_SERVER["HTTP_DEPTH"] != "infinity") {
1253  $this->http_status("400 Bad Request");
1254  return;
1255  }
1256  }
1257 
1258  // check lock status
1259  if ($this->_check_lock_status($this->path)) {
1260  // ok, proceed
1261  $options = array();
1262  $options["path"] = $this->path;
1263 
1264  $stat = $this->delete($options);
1265 
1266  $this->http_status($stat);
1267  } else {
1268  // sorry, its locked
1269  $this->http_status("423 Locked");
1270  }
1271  }
1272 
1273  // }}}
1274 
1275  // {{{ http_COPY()
1276 
1283  public function http_COPY()
1284  {
1285  // no need to check source lock status here
1286  // destination lock status is always checked by the helper method
1287  $this->_copymove("copy");
1288  }
1289 
1290  // }}}
1291 
1292  // {{{ http_MOVE()
1293 
1300  public function http_MOVE()
1301  {
1302  //$this->writelog('MOVE()');
1303  if ($this->_check_lock_status($this->path)) {
1304  // destination lock status is always checked by the helper method
1305  $this->_copymove("move");
1306  } else {
1307  //$this->writelog('MOVE():423 Locked');
1308  $this->http_status("423 Locked");
1309  }
1310  }
1311 
1312  // }}}
1313 
1314 
1315  // {{{ http_LOCK()
1316 
1323  public function http_LOCK()
1324  {
1325  $options = array();
1326  $options["path"] = $this->path;
1327 
1328  if (isset($_SERVER['HTTP_DEPTH'])) {
1329  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1330  } else {
1331  $options["depth"] = "infinity";
1332  }
1333 
1334  if (isset($_SERVER["HTTP_TIMEOUT"])) {
1335  $options["timeout"] = explode(",", $_SERVER["HTTP_TIMEOUT"]);
1336  }
1337 
1338  if (empty($_SERVER['CONTENT_LENGTH']) && !empty($_SERVER['HTTP_IF'])) {
1339  // check if locking is possible
1340  if (!$this->_check_lock_status($this->path)) {
1341  $this->http_status("423 Locked");
1342  return;
1343  }
1344 
1345  // refresh lock
1346  $options["update"] = substr($_SERVER['HTTP_IF'], 2, -2);
1347  $stat = $this->lock($options);
1348  } else {
1349  // extract lock request information from request XML payload
1350  $lockinfo = new _parse_lockinfo("php://input");
1351  if (!$lockinfo->success) {
1352  $this->http_status("400 bad request");
1353  }
1354 
1355  // check if locking is possible
1356  if (!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) {
1357  $this->http_status("423 Locked");
1358  return;
1359  }
1360 
1361  // new lock
1362  $options["scope"] = $lockinfo->lockscope;
1363  $options["type"] = $lockinfo->locktype;
1364  $options["owner"] = $lockinfo->owner;
1365 
1366  $options["locktoken"] = $this->_new_locktoken();
1367 
1368  $stat = $this->lock($options);
1369  }
1370 
1371  if (is_bool($stat)) {
1372  $http_stat = $stat ? "200 OK" : "423 Locked";
1373  } else {
1374  $http_stat = $stat;
1375  }
1376 
1377  $this->http_status($http_stat);
1378 
1379  if ($http_stat{0} == 2) { // 2xx states are ok
1380  if ($options["timeout"]) {
1381  // more than a million is considered an absolute timestamp
1382  // less is more likely a relative value
1383  if ($options["timeout"]>1000000) {
1384  $timeout = "Second-" . ($options['timeout']-time());
1385  } else {
1386  $timeout = "Second-$options[timeout]";
1387  }
1388  } else {
1389  $timeout = "Infinite";
1390  }
1391  /*
1392  $this->writelog(
1393  'Content-Type: text/xml; charset="utf-8"'
1394  ."Lock-Token: <$options[locktoken]>"
1395  . "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
1396  . "<D:prop xmlns:D=\"DAV:\">\n"
1397  . " <D:lockdiscovery>\n"
1398  . " <D:activelock>\n"
1399  . " <D:lockscope><D:$options[scope]/></D:lockscope>\n"
1400  . " <D:locktype><D:$options[type]/></D:locktype>\n"
1401  . " <D:depth>$options[depth]</D:depth>\n"
1402  . " <D:owner>$options[owner]</D:owner>\n"
1403  . " <D:timeout>$timeout</D:timeout>\n"
1404  . " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n"
1405  . " </D:activelock>\n"
1406  . " </D:lockdiscovery>\n"
1407  . "</D:prop>\n\n"
1408  );*/
1409  header('Content-Type: text/xml; charset="utf-8"');
1410  header("Lock-Token: <$options[locktoken]>");
1411  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1412  echo "<D:prop xmlns:D=\"DAV:\">\n";
1413  echo " <D:lockdiscovery>\n";
1414  echo " <D:activelock>\n";
1415  echo " <D:lockscope><D:$options[scope]/></D:lockscope>\n";
1416  echo " <D:locktype><D:$options[type]/></D:locktype>\n";
1417  echo " <D:depth>$options[depth]</D:depth>\n";
1418  echo " <D:owner>$options[owner]</D:owner>\n";
1419  echo " <D:timeout>$timeout</D:timeout>\n";
1420  echo " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n";
1421  echo " </D:activelock>\n";
1422  echo " </D:lockdiscovery>\n";
1423  echo "</D:prop>\n\n";
1424  }
1425  }
1426 
1427 
1428  // }}}
1429 
1430  // {{{ http_UNLOCK()
1431 
1438  public function http_UNLOCK()
1439  {
1440  $options = array();
1441  $options["path"] = $this->path;
1442 
1443  if (isset($_SERVER['HTTP_DEPTH'])) {
1444  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1445  } else {
1446  $options["depth"] = "infinity";
1447  }
1448 
1449  // strip surrounding <>
1450  $options["token"] = substr(trim($_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
1451  //$this->writelog('http_UNLOCK HTTP_LOCK_TOKEN='.$_SERVER["HTTP_LOCK_TOKEN"]);
1452  // call user method
1453  $stat = $this->unlock($options);
1454 
1455  $this->http_status($stat);
1456  }
1457 
1458  // }}}
1459 
1460  // }}}
1461 
1462  // {{{ _copymove()
1463 
1464  public function _copymove($what)
1465  {
1466  //$this->writelog('_copymove('.$what.')');
1467  $options = array();
1468  $options["path"] = $this->path;
1469 
1470  if (isset($_SERVER["HTTP_DEPTH"])) {
1471  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1472  } else {
1473  $options["depth"] = "infinity";
1474  }
1475  //$this->writelog('_copymove dest='.$_SERVER["HTTP_DESTINATION"]);
1476  extract(parse_url($_SERVER["HTTP_DESTINATION"]));
1477  // BEGIN WebDAV: decode path (bereits in PEAR CVS gefixt)
1478  // We must decode the target path too.
1479  $path = $this->_urldecode($path);
1480  // END Patch WebDAV: decode path
1481  $http_host = $host;
1482  if (isset($port) && $port != 80) {
1483  $http_host.= ":$port";
1484  }
1485 
1486  list($http_header_host, $http_header_port) = explode(":", $_SERVER["HTTP_HOST"]);
1487  if (isset($http_header_port) && $http_header_port != 80) {
1488  $http_header_host .= ":" . $http_header_port;
1489  }
1490 
1491  if ($http_host == $http_header_host &&
1492  !strncmp(
1493  $_SERVER["SCRIPT_NAME"],
1494  $path,
1495  strlen($_SERVER["SCRIPT_NAME"])
1496  )) {
1497  $options["dest"] = substr($path, strlen($_SERVER["SCRIPT_NAME"]));
1498  //$this->writelog('_copymove() dest='.$options['dest']);
1499  if (!$this->_check_lock_status($options["dest"])) {
1500  //$this->writelog('_copymove():423 Locked');
1501  $this->http_status("423 Locked");
1502  return;
1503  }
1504  //$this->writelog('_copymove() ...');
1505  } else {
1506  $options["dest_url"] = $_SERVER["HTTP_DESTINATION"];
1507  }
1508 
1509  // see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3
1510  if (isset($_SERVER["HTTP_OVERWRITE"])) {
1511  $options["overwrite"] = $_SERVER["HTTP_OVERWRITE"] == "T";
1512  } else {
1513  $options["overwrite"] = true;
1514  }
1515 
1516  $stat = $this->$what($options);
1517  $this->http_status($stat);
1518  }
1519 
1520  // }}}
1521 
1522  // {{{ _allow()
1523 
1530  public function _allow()
1531  {
1532  // OPTIONS is always there
1533  $allow = array("OPTIONS" =>"OPTIONS");
1534 
1535  // all other METHODS need both a http_method() wrapper
1536  // and a method() implementation
1537  // the base class supplies wrappers only
1538  foreach (get_class_methods($this) as $method) {
1539  if (!strncmp("http_", $method, 5)) {
1540  $method = strtoupper(substr($method, 5));
1541  if (method_exists($this, $method)) {
1542  $allow[$method] = $method;
1543  }
1544  }
1545  }
1546 
1547  // we can emulate a missing HEAD implemetation using GET
1548  if (isset($allow["GET"])) {
1549  $allow["HEAD"] = "HEAD";
1550  }
1551 
1552  // no LOCK without checklok()
1553  if (!method_exists($this, "checklock")) {
1554  unset($allow["LOCK"]);
1555  unset($allow["UNLOCK"]);
1556  }
1557 
1558  return $allow;
1559  }
1560 
1561  // }}}
1562 
1571  public function mkprop()
1572  {
1573  $args = func_get_args();
1574  if (count($args) == 3) {
1575  return array("ns" => $args[0],
1576  "name" => $args[1],
1577  "val" => $args[2]);
1578  } else {
1579  return array("ns" => "DAV:",
1580  "name" => $args[0],
1581  "val" => $args[1]);
1582  }
1583  }
1584 
1585  // {{{ _check_auth
1586 
1593  public function _check_auth()
1594  {
1595  if (method_exists($this, "checkAuth")) {
1596  // PEAR style method name
1597  return $this->checkAuth(
1598  @$_SERVER["AUTH_TYPE"],
1599  @$_SERVER["PHP_AUTH_USER"],
1600  @$_SERVER["PHP_AUTH_PW"]
1601  );
1602  } elseif (method_exists($this, "check_auth")) {
1603  // old (pre 1.0) method name
1604  return $this->check_auth(
1605  @$_SERVER["AUTH_TYPE"],
1606  @$_SERVER["PHP_AUTH_USER"],
1607  @$_SERVER["PHP_AUTH_PW"]
1608  );
1609  } else {
1610  // no method found -> no authentication required
1611  return true;
1612  }
1613  }
1614 
1615  // }}}
1616 
1617  // {{{ UUID stuff
1618 
1625  public function _new_uuid()
1626  {
1627  // use uuid extension from PECL if available
1628  if (function_exists("uuid_create")) {
1629  return uuid_create();
1630  }
1631 
1632  // fallback
1633  $uuid = md5(microtime() . getmypid()); // this should be random enough for now
1634 
1635  // set variant and version fields for 'true' random uuid
1636  $uuid{12} = "4";
1637  $n = 8 + (ord($uuid{16}) & 3);
1638  $hex = "0123456789abcdef";
1639  $uuid{16} = $hex{$n};
1640 
1641  // return formated uuid
1642  return substr($uuid, 0, 8) . "-"
1643  . substr($uuid, 8, 4) . "-"
1644  . substr($uuid, 12, 4) . "-"
1645  . substr($uuid, 16, 4) . "-"
1646  . substr($uuid, 20);
1647  }
1648 
1655  public function _new_locktoken()
1656  {
1657  return "opaquelocktoken:" . $this->_new_uuid();
1658  }
1659 
1660  // }}}
1661 
1662  // {{{ WebDAV If: header parsing
1663 
1671  public function _if_header_lexer($string, &$pos)
1672  {
1673  // skip whitespace
1674  while (ctype_space($string{$pos})) {
1675  ++$pos;
1676  }
1677 
1678  // already at end of string?
1679  if (strlen($string) <= $pos) {
1680  return false;
1681  }
1682 
1683  // get next character
1684  $c = $string{$pos++};
1685 
1686  // now it depends on what we found
1687  switch ($c) {
1688  case "<":
1689  // URIs are enclosed in <...>
1690  $pos2 = strpos($string, ">", $pos);
1691  $uri = substr($string, $pos, $pos2 - $pos);
1692  $pos = $pos2 + 1;
1693  return array("URI", $uri);
1694 
1695  case "[":
1696  //Etags are enclosed in [...]
1697  if ($string{$pos} == "W") {
1698  $type = "ETAG_WEAK";
1699  $pos += 2;
1700  } else {
1701  $type = "ETAG_STRONG";
1702  }
1703  $pos2 = strpos($string, "]", $pos);
1704  $etag = substr($string, $pos + 1, $pos2 - $pos - 2);
1705  $pos = $pos2 + 1;
1706  return array($type, $etag);
1707 
1708  case "N":
1709  // "N" indicates negation
1710  $pos += 2;
1711  return array("NOT", "Not");
1712 
1713  default:
1714  // anything else is passed verbatim char by char
1715  return array("CHAR", $c);
1716  }
1717  }
1718 
1725  public function _if_header_parser($str)
1726  {
1727  $pos = 0;
1728  $len = strlen($str);
1729 
1730  $uris = array();
1731 
1732  // parser loop
1733  while ($pos < $len) {
1734  // get next token
1735  $token = $this->_if_header_lexer($str, $pos);
1736 
1737  // check for URI
1738  if ($token[0] == "URI") {
1739  $uri = $token[1]; // remember URI
1740  $token = $this->_if_header_lexer($str, $pos); // get next token
1741  } else {
1742  $uri = "";
1743  }
1744 
1745  // sanity check
1746  if ($token[0] != "CHAR" || $token[1] != "(") {
1747  return false;
1748  }
1749 
1750  $list = array();
1751  $level = 1;
1752  $not = "";
1753  while ($level) {
1754  $token = $this->_if_header_lexer($str, $pos);
1755  if ($token[0] == "NOT") {
1756  $not = "!";
1757  continue;
1758  }
1759  switch ($token[0]) {
1760  case "CHAR":
1761  switch ($token[1]) {
1762  case "(":
1763  $level++;
1764  break;
1765  case ")":
1766  $level--;
1767  break;
1768  default:
1769  return false;
1770  }
1771  break;
1772 
1773  case "URI":
1774  $list[] = $not . "<$token[1]>";
1775  break;
1776 
1777  case "ETAG_WEAK":
1778  $list[] = $not . "[W/'$token[1]']>";
1779  break;
1780 
1781  case "ETAG_STRONG":
1782  $list[] = $not . "['$token[1]']>";
1783  break;
1784 
1785  default:
1786  return false;
1787  }
1788  $not = "";
1789  }
1790 
1791  if (@is_array($uris[$uri])) {
1792  $uris[$uri] = array_merge($uris[$uri], $list);
1793  } else {
1794  $uris[$uri] = $list;
1795  }
1796  }
1797 
1798  return $uris;
1799  }
1800 
1811  {
1812  if (isset($_SERVER["HTTP_IF"])) {
1813  $this->_if_header_uris =
1814  $this->_if_header_parser($_SERVER["HTTP_IF"]);
1815 
1816  foreach ($this->_if_header_uris as $uri => $conditions) {
1817  if ($uri == "") {
1818  $uri = $this->uri;
1819  }
1820  // all must match
1821  $state = true;
1822  foreach ($conditions as $condition) {
1823  // lock tokens may be free form (RFC2518 6.3)
1824  // but if opaquelocktokens are used (RFC2518 6.4)
1825  // we have to check the format (litmus tests this)
1826  if (!strncmp($condition, "<opaquelocktoken:", strlen("<opaquelocktoken"))) {
1827  if (!preg_match("/^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$/", $condition)) {
1828  return false;
1829  }
1830  }
1831  if (!$this->_check_uri_condition($uri, $condition)) {
1832  $state = false;
1833  break;
1834  }
1835  }
1836 
1837  // any match is ok
1838  if ($state == true) {
1839  return true;
1840  }
1841  }
1842  return false;
1843  }
1844  return true;
1845  }
1846 
1857  public function _check_uri_condition($uri, $condition)
1858  {
1859  // not really implemented here,
1860  // implementations must override
1861  return true;
1862  }
1863 
1864 
1871  public function _check_lock_status($path, $exclusive_only = false)
1872  {
1873  // FIXME depth -> ignored for now
1874  if (method_exists($this, "checkLock")) {
1875  // is locked?
1876  $lock = $this->checkLock($path);
1877 
1878  // ... and lock is not owned?
1879  if (is_array($lock) && count($lock)) {
1880  // FIXME doesn't check uri restrictions yet
1881  if (!strstr($_SERVER["HTTP_IF"], $lock["token"])) {
1882  if (!$exclusive_only || ($lock["scope"] !== "shared")) {
1883  return false;
1884  }
1885  }
1886  }
1887  }
1888  return true;
1889  }
1890 
1891 
1892  // }}}
1893 
1894 
1901  public function lockdiscovery($path)
1902  {
1903  // no lock support without checklock() method
1904  if (!method_exists($this, "checklock")) {
1905  return "";
1906  }
1907 
1908  // collect response here
1909  $activelocks = "";
1910 
1911  // get checklock() reply
1912  $lock = $this->checklock($path);
1913 
1914  // generate <activelock> block for returned data
1915  if (is_array($lock) && count($lock)) {
1916  // check for 'timeout' or 'expires'
1917  if (!empty($lock["expires"])) {
1918  $timeout = "Second-" . ($lock["expires"] - time());
1919  } elseif (!empty($lock["timeout"])) {
1920  $timeout = "Second-$lock[timeout]";
1921  } else {
1922  $timeout = "Infinite";
1923  }
1924 
1925  // genreate response block
1926  $activelocks.= "
1927  <D:activelock>
1928  <D:lockscope><D:$lock[scope]/></D:lockscope>
1929  <D:locktype><D:$lock[type]/></D:locktype>
1930  <D:depth>$lock[depth]</D:depth>
1931  <D:owner>$lock[owner]</D:owner>
1932  <D:timeout>$timeout</D:timeout>
1933  <D:locktoken><D:href>$lock[token]</D:href></D:locktoken>
1934  </D:activelock>
1935  ";
1936  }
1937  //$this->writelog('lockdiscovery('.$path.'):'.$activeclocks);
1938 
1939  // return generated response
1940  return $activelocks;
1941  }
1942 
1949  public function http_status($status)
1950  {
1951  // simplified success case
1952  if ($status === true) {
1953  $status = "200 OK";
1954  }
1955  //$this->writelog('http_status('.$status.')');
1956 
1957  // remember status
1958  $this->_http_status = $status;
1959 
1960  // generate HTTP status response
1961  header("HTTP/1.1 $status");
1962  header("X-WebDAV-Status: $status", true);
1963  }
1964 
1974  public function _urlencode($url)
1975  {
1976  return strtr($url, array(" "=>"%20",
1977  "&"=>"%26",
1978  "<"=>"%3C",
1979  ">"=>"%3E",
1980  ));
1981  }
1982 
1991  public function _urldecode($path)
1992  {
1993  // BEGIN WebDAV
1994  // urldecode wrongly replaces '+' characters by ' ' characters.
1995  // We replace '+' into '%2b' before passing the path through urldecode.
1996  //return urldecode($path);
1997  $result =&urldecode(str_replace('+', '%2b', $path));
1998  //$this->writelog('_urldecode('.$path.'):'.$result);
1999  return $result;
2000  // END PATCH WebDAV
2001  }
2002 
2009  public function _prop_encode($text)
2010  {
2011  switch (strtolower($this->_prop_encoding)) {
2012  case "utf-8":
2013  return $text;
2014  case "iso-8859-1":
2015  case "iso-8859-15":
2016  case "latin-1":
2017  default:
2018  return utf8_encode($text);
2019  }
2020  }
2021 
2028  public function _slashify($path)
2029  {
2030  if ($path[strlen($path)-1] != '/') {
2031  $path = $path . "/";
2032  }
2033  return $path;
2034  }
2035  // BEGIN WebDAV
2042  private function writelog($message)
2043  {
2044  global $DIC;
2045  $log = $DIC['log'];
2046  $ilUser = $DIC['ilUser'];
2047 
2048  $log->write(
2049  $ilUser->getLogin()
2050  . ' DAV Server.' . str_replace("\n", ";", $message)
2051  );
2052  }
2053  // END PATCH WebDAV
2054 }
$files
Definition: add-vimline.php:18
http_OPTIONS()
GET implementation.
Definition: Server.php:471
http_PROPPATCH()
PROPPATCH method handler.
Definition: Server.php:783
_get_ranges(&$options)
parse HTTP Range: header
Definition: Server.php:996
if(isset($_REQUEST['delete'])) $list
Definition: registry.php:41
$size
Definition: RandomTest.php:84
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
_allow()
check for implemented HTTP methods
Definition: Server.php:1530
__construct()
Constructor.
Definition: Server.php:108
_if_header_lexer($string, &$pos)
Definition: Server.php:1671
$result
http_UNLOCK()
UNLOCK method handler.
Definition: Server.php:1438
$type
global $DIC
Definition: saml.php:7
_if_header_parser($str)
parse If: header
Definition: Server.php:1725
http_PUT()
PUT method handler.
Definition: Server.php:1103
_multipart_byterange_header($mimetype=false, $from=false, $to=false, $total=false)
generate separator headers for multipart response
Definition: Server.php:1030
_check_if_header_conditions()
check if conditions from "If:" headers are meat
Definition: Server.php:1810
_urldecode($path)
private version of PHP urldecode
Definition: Server.php:1991
_urlencode($url)
private minimalistic version of PHP urlencode()
Definition: Server.php:1974
http_HEAD()
HEAD method handler.
Definition: Server.php:1069
$end
Definition: saml1-acs.php:18
http_GET()
GET method handler.
Definition: Server.php:861
http_MOVE()
MOVE method handler.
Definition: Server.php:1300
http_DELETE()
DELETE method handler.
Definition: Server.php:1248
http_PROPFIND()
PROPFIND method handler.
Definition: Server.php:505
Virtual base class for implementing WebDAV servers.
Definition: Server.php:23
$from
$stream
PHP stream implementation.
$total
Definition: Utf8Test.php:87
http_COPY()
COPY method handler.
Definition: Server.php:1283
http_status($status)
set HTTP return status and mirror it in a private header
Definition: Server.php:1949
_new_locktoken()
create a new opaque lock token as defined in RFC2518
Definition: Server.php:1655
_check_lock_status($path, $exclusive_only=false)
Definition: Server.php:1871
serveRequest()
Serve WebDAV HTTP request.
Definition: Server.php:125
if(!array_key_exists('stateid', $_REQUEST)) $state
Handle linkback() response from LinkedIn.
Definition: linkback.php:10
catch(Exception $e) $message
mkprop()
helper for property element creation
Definition: Server.php:1571
$text
Definition: errorreport.php:18
$ilUser
Definition: imgupload.php:18
Add a drawing to the header
Definition: 04printing.php:69
_check_auth()
check authentication if check is implemented
Definition: Server.php:1593
_check_uri_condition($uri, $condition)
Check a single URI condition parsed from an if-header.
Definition: Server.php:1857
$n
Definition: RandomTest.php:85
_slashify($path)
Slashify - make sure path ends in a slash.
Definition: Server.php:2028
Create styles array
The data for the language used.
http_MKCOL()
MKCOL method handler.
Definition: Server.php:840
lockdiscovery($path)
Generate lockdiscovery reply from checklock() result.
Definition: Server.php:1901
$url
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
if(!file_exists("$old.txt")) if($old===$new) if(file_exists("$new.txt")) $file
_new_uuid()
generate Unique Universal IDentifier for lock token
Definition: Server.php:1625
_prop_encode($text)
UTF-8 encode property values if not already done so.
Definition: Server.php:2009
$key
Definition: croninfo.php:18
http_LOCK()
LOCK method handler.
Definition: Server.php:1323
if(!isset($_REQUEST['ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options
Definition: as_login.php:20
writelog($message)
Writes a message to the logfile.,.
Definition: Server.php:2042