ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
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  var $uri;
47 
48 
54  var $base_uri;
55 
56 
62  var $path;
63 
69  var $http_auth_realm = "PHP WebDAV";
70 
76  var $dav_powered_by = "";
77 
83  var $_if_header_uris = array();
84 
90  var $_http_status = "200 OK";
91 
97  var $_prop_encoding = "utf-8";
98 
99  // }}}
100 
101  // {{{ Constructor
102 
108  function HTTP_WebDAV_Server()
109  {
110  // PHP messages destroy XML output -> switch them off
111  //ini_set("display_errors", 0);
112  }
113 
114  // }}}
115 
116  // {{{ ServeRequest()
125  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  {
192  $method = "get";
193  $this->writelog(__METHOD__.': Using head emulation by get.');
194  }
195 
196  if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method)))
197  {
198  $this->writelog(__METHOD__.': Calling wrapper: '.$wrapper);
199  $this->$wrapper(); // call method by name
200  }
201  else
202  { // method not found/implemented
203  if ($_SERVER["REQUEST_METHOD"] == "LOCK")
204  {
205  $this->writelog(__METHOD__.': Method not found/implemented. Sending 412');
206  $this->http_status("412 Precondition failed");
207  }
208  else
209  {
210  $this->writelog(__METHOD__.': Method not found/implemented. Sending allowd methods');
211  $this->http_status("405 Method not allowed");
212  header("Allow: ".join(", ", $this->_allow())); // tell client what's allowed
213  }
214  }
215  }
216 
217  // }}}
218 
219  // {{{ abstract WebDAV methods
220 
221  // {{{ GET()
240  /* abstract
241  function GET(&$params)
242  {
243  // dummy entry for PHPDoc
244  }
245  */
246 
247  // }}}
248 
249  // {{{ PUT()
260  /* abstract
261  function PUT()
262  {
263  // dummy entry for PHPDoc
264  }
265  */
266 
267  // }}}
268 
269  // {{{ COPY()
270 
281  /* abstract
282  function COPY()
283  {
284  // dummy entry for PHPDoc
285  }
286  */
287 
288  // }}}
289 
290  // {{{ MOVE()
291 
302  /* abstract
303  function MOVE()
304  {
305  // dummy entry for PHPDoc
306  }
307  */
308 
309  // }}}
310 
311  // {{{ DELETE()
312 
323  /* abstract
324  function DELETE()
325  {
326  // dummy entry for PHPDoc
327  }
328  */
329  // }}}
330 
331  // {{{ PROPFIND()
332 
343  /* abstract
344  function PROPFIND()
345  {
346  // dummy entry for PHPDoc
347  }
348  */
349 
350  // }}}
351 
352  // {{{ PROPPATCH()
353 
364  /* abstract
365  function PROPPATCH()
366  {
367  // dummy entry for PHPDoc
368  }
369  */
370  // }}}
371 
372  // {{{ LOCK()
373 
384  /* abstract
385  function LOCK()
386  {
387  // dummy entry for PHPDoc
388  }
389  */
390  // }}}
391 
392  // {{{ UNLOCK()
393 
404  /* abstract
405  function UNLOCK()
406  {
407  // dummy entry for PHPDoc
408  }
409  */
410  // }}}
411 
412  // }}}
413 
414  // {{{ other abstract methods
415 
416  // {{{ check_auth()
417 
430  /* abstract
431  function checkAuth($type, $username, $password)
432  {
433  // dummy entry for PHPDoc
434  }
435  */
436 
437  // }}}
438 
439  // {{{ checklock()
440 
453  /* abstract
454  function checklock($resource)
455  {
456  // dummy entry for PHPDoc
457  }
458  */
459 
460  // }}}
461 
462  // }}}
463 
464  // {{{ WebDAV HTTP method wrappers
465 
466  // {{{ http_OPTIONS()
467 
478  function http_OPTIONS()
479  {
480  // Microsoft clients default to the Frontpage protocol
481  // unless we tell them to use WebDAV
482  header("MS-Author-Via: DAV");
483 
484  // get allowed methods
485  $allow = $this->_allow();
486 
487  // dav header
488  $dav = array(1); // assume we are always dav class 1 compliant
489  if (isset($allow['LOCK'])) {
490  $dav[] = 2; // dav class 2 requires that locking is supported
491  }
492 
493  // tell clients what we found
494  $this->http_status("200 OK");
495  header("DAV: " .join("," , $dav));
496  header("Allow: ".join(", ", $allow));
497  $this->writelog(__METHOD__.': dav='.var_export($dav,true).' allow='.var_export($allow,true));
498  header("Content-length: 0");
499  }
500 
501  // }}}
502 
503 
504  // {{{ http_PROPFIND()
505 
512  function http_PROPFIND()
513  {
514  $options = Array();
515  $options["path"] = $this->path;
516 
517  // search depth from header (default is "infinity)
518  if (isset($_SERVER['HTTP_DEPTH'])) {
519  $options["depth"] = $_SERVER["HTTP_DEPTH"];
520  } else {
521  $options["depth"] = "infinity";
522  }
523 
524  // analyze request payload
525  $propinfo = new _parse_propfind("php://input");
526  if (!$propinfo->success) {
527  $this->http_status("400 Error");
528  return;
529  }
530  $options['props'] = $propinfo->props;
531 
532  // call user handler
533  $files = array();
534  if (!$this->propfind($options, $files)) {
535  $this->http_status("404 Not Found");
536  return;
537  }
538 
539  // collect namespaces here
540  $ns_hash = array();
541 
542  // Microsoft Clients need this special namespace for date and time values
543  $ns_defs = "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\"";
544 
545  // now we loop over all returned file entries
546  foreach($files["files"] as $filekey => $file) {
547 
548  // nothing to do if no properties were returend for a file
549  if (!isset($file["props"]) || !is_array($file["props"])) {
550  continue;
551  }
552 
553  // now loop over all returned properties
554  foreach($file["props"] as $key => $prop) {
555  // as a convenience feature we do not require that user handlers
556  // restrict returned properties to the requested ones
557  // here we strip all unrequested entries out of the response
558 
559  switch($options['props']) {
560  case "all":
561  // nothing to remove
562  break;
563 
564  case "names":
565  // only the names of all existing properties were requested
566  // so we remove all values
567  unset($files["files"][$filekey]["props"][$key]["val"]);
568  break;
569 
570  default:
571  $found = false;
572 
573  // search property name in requested properties
574  foreach((array)$options["props"] as $reqprop) {
575  if ( $reqprop["name"] == $prop["name"]
576  && $reqprop["xmlns"] == $prop["ns"]) {
577  $found = true;
578  break;
579  }
580  }
581 
582  // unset property and continue with next one if not found/requested
583  if (!$found) {
584  $files["files"][$filekey]["props"][$key]="";
585  continue(2);
586  }
587  break;
588  }
589 
590  // namespace handling
591  if (empty($prop["ns"])) continue; // no namespace
592  $ns = $prop["ns"];
593  if ($ns == "DAV:") continue; // default namespace
594  if (isset($ns_hash[$ns])) continue; // already known
595 
596  // register namespace
597  $ns_name = "ns".(count($ns_hash) + 1);
598  $ns_hash[$ns] = $ns_name;
599  $ns_defs .= " xmlns:$ns_name=\"$ns\"";
600  }
601 
602  // we also need to add empty entries for properties that were requested
603  // but for which no values where returned by the user handler
604  if (is_array($options['props'])) {
605  foreach($options["props"] as $reqprop) {
606  if($reqprop['name']=="") continue; // skip empty entries
607 
608  $found = false;
609 
610  // check if property exists in result
611  foreach($file["props"] as $prop) {
612  if ( $reqprop["name"] == $prop["name"]
613  && $reqprop["xmlns"] == $prop["ns"]) {
614  $found = true;
615  break;
616  }
617  }
618 
619  if (!$found) {
620  if($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
621  // lockdiscovery is handled by the base class
622  $files["files"][$filekey]["props"][]
623  = $this->mkprop("DAV:",
624  "lockdiscovery" ,
625  $this->lockdiscovery($files["files"][$filekey]['path']));
626  } else {
627  // add empty value for this property
628  $files["files"][$filekey]["noprops"][] =
629  $this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
630 
631  // register property namespace if not known yet
632  if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) {
633  $ns_name = "ns".(count($ns_hash) + 1);
634  $ns_hash[$reqprop["xmlns"]] = $ns_name;
635  $ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\"";
636  }
637  }
638  }
639  }
640  }
641  }
642 
643  // now we generate the reply header ...
644  $this->http_status("207 Multi-Status");
645  header('Content-Type: text/xml; charset="utf-8"');
646 
647  // ... and payload
648  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
649  echo "<D:multistatus xmlns:D=\"DAV:\">\n";
650 
651  foreach($files["files"] as $file) {
652  // ignore empty or incomplete entries
653  if(!is_array($file) || empty($file) || !isset($file["path"])) continue;
654  $path = $file['path'];
655  if(!is_string($path) || $path==="") continue;
656 
657  echo " <D:response $ns_defs>\n";
658 
659  // BEGIN WebDAV W. Randelshofer Don't slashify path because it confuses Mac OS X
660  //$href = $this->_slashify($_SERVER['SCRIPT_NAME'] . $path);
661  $href = $_SERVER['SCRIPT_NAME'] . $path;
662  //END PATCH WebDAV W. Randelshofer
663 
664  echo " <D:href>$href</D:href>\n";
665 
666  // report all found properties and their values (if any)
667  if (isset($file["props"]) && is_array($file["props"])) {
668  echo " <D:propstat>\n";
669  echo " <D:prop>\n";
670 
671  foreach($file["props"] as $key => $prop) {
672 
673  if (!is_array($prop)) continue;
674  if (!isset($prop["name"])) continue;
675  if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
676  // empty properties (cannot use empty() for check as "0" is a legal value here)
677  if($prop["ns"]=="DAV:") {
678  echo " <D:$prop[name]/>\n";
679  } else if(!empty($prop["ns"])) {
680  echo " <".$ns_hash[$prop["ns"]].":$prop[name]/>\n";
681  } else {
682  echo " <$prop[name] xmlns=\"\"/>";
683  }
684  } else if ($prop["ns"] == "DAV:") {
685  // some WebDAV properties need special treatment
686  switch ($prop["name"]) {
687  case "creationdate":
688  echo " <D:creationdate ns0:dt=\"dateTime.tz\">"
689  // BEGIN WebDAV W. Randelshofer
690  . gmdate("Y-m-d\\TH:i:s\\Z",$prop['val'])
691  // . gmdate("D, d M Y H:i:s ", $prop['val'])
692  // END PATCH WebDAV W. Randelshofer
693  . "</D:creationdate>\n";
694  break;
695  case "getlastmodified":
696  echo " <D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"
697  . gmdate("D, d M Y H:i:s ", $prop['val'])
698  . "GMT</D:getlastmodified>\n";
699  break;
700  case "resourcetype":
701  echo " <D:resourcetype><D:$prop[val]/></D:resourcetype>\n";
702  break;
703  case "supportedlock":
704  echo " <D:supportedlock>$prop[val]</D:supportedlock>\n";
705  break;
706  case "lockdiscovery":
707  echo " <D:lockdiscovery>\n";
708  echo $prop["val"];
709  echo " </D:lockdiscovery>\n";
710  break;
711  default:
712  echo " <D:$prop[name]>"
713  . $this->_prop_encode(htmlspecialchars($prop['val']))
714  . "</D:$prop[name]>\n";
715  break;
716  }
717  } else {
718  // properties from namespaces != "DAV:" or without any namespace
719  if ($prop["ns"]) {
720  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>"
721  . $this->_prop_encode(htmlspecialchars($prop['val']))
722  . "</" . $ns_hash[$prop["ns"]] . ":$prop[name]>\n";
723  } else {
724  echo " <$prop[name] xmlns=\"\">"
725  . $this->_prop_encode(htmlspecialchars($prop['val']))
726  . "</$prop[name]>\n";
727  }
728  }
729  }
730 
731  echo " </D:prop>\n";
732  echo " <D:status>HTTP/1.1 200 OK</D:status>\n";
733  echo " </D:propstat>\n";
734  }
735 
736  // now report all properties requested but not found
737  if (isset($file["noprops"])) {
738  echo " <D:propstat>\n";
739  echo " <D:prop>\n";
740 
741  foreach($file["noprops"] as $key => $prop) {
742  if ($prop["ns"] == "DAV:") {
743  echo " <D:$prop[name]/>\n";
744  } else if ($prop["ns"] == "") {
745  echo " <$prop[name] xmlns=\"\"/>\n";
746  } else {
747  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
748  }
749  }
750 
751  echo " </D:prop>\n";
752  echo " <D:status>HTTP/1.1 404 Not Found</D:status>\n";
753  echo " </D:propstat>\n";
754  }
755 
756  echo " </D:response>\n";
757  }
758 
759  echo "</D:multistatus>\n";
760  }
761 
762 
763  // }}}
764 
765  // {{{ http_PROPPATCH()
766 
773  function http_PROPPATCH()
774  {
775  if($this->_check_lock_status($this->path)) {
776  $options = Array();
777  $options["path"] = $this->path;
778 
779  $propinfo = new _parse_proppatch("php://input");
780 
781  if (!$propinfo->success) {
782  $this->http_status("400 Error");
783  return;
784  }
785 
786  $options['props'] = $propinfo->props;
787 
788  $responsedescr = $this->proppatch($options);
789 
790  $this->http_status("207 Multi-Status");
791  header('Content-Type: text/xml; charset="utf-8"');
792 
793  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
794 
795  echo "<D:multistatus xmlns:D=\"DAV:\">\n";
796  echo " <D:response>\n";
797  echo " <D:href>".$this->_urlencode($_SERVER["SCRIPT_NAME"].$this->path)."</D:href>\n";
798 
799  foreach($options["props"] as $prop) {
800  echo " <D:propstat>\n";
801  echo " <D:prop><$prop[name] xmlns=\"$prop[ns]\"/></D:prop>\n";
802  echo " <D:status>HTTP/1.1 $prop[status]</D:status>\n";
803  echo " </D:propstat>\n";
804  }
805 
806  if ($responsedescr) {
807  echo " <D:responsedescription>".
808  $this->_prop_encode(htmlspecialchars($responsedescr)).
809  "</D:responsedescription>\n";
810  }
811 
812  echo " </D:response>\n";
813  echo "</D:multistatus>\n";
814  } else {
815  $this->http_status("423 Locked");
816  }
817  }
818 
819  // }}}
820 
821 
822  // {{{ http_MKCOL()
823 
830  function http_MKCOL()
831  {
832  $options = Array();
833  $options["path"] = $this->path;
834 
835  $stat = $this->mkcol($options);
836 
837  $this->http_status($stat);
838  }
839 
840  // }}}
841 
842 
843  // {{{ http_GET()
844 
851  function http_GET()
852  {
853  // TODO check for invalid stream
854  $options = Array();
855  $options["path"] = $this->path;
856 
857  $this->_get_ranges($options);
858 
859  if (true === ($status = $this->get($options))) {
860  if (!headers_sent()) {
861  $status = "200 OK";
862 
863  if (!isset($options['mimetype'])) {
864  $options['mimetype'] = "application/octet-stream";
865  }
866  header("Content-type: $options[mimetype]");
867 
868  if (isset($options['mtime'])) {
869  header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
870  }
871 
872  if (isset($options['stream'])) {
873  // GET handler returned a stream
874  if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
875  // partial request and stream is seekable
876 
877  if (count($options['ranges']) === 1) {
878  $range = $options['ranges'][0];
879 
880  if (isset($range['start'])) {
881  fseek($options['stream'], $range['start'], SEEK_SET);
882  if (feof($options['stream'])) {
883  $this->http_status("416 Requested range not satisfiable");
884  exit;
885  }
886 
887  if (isset($range['end'])) {
888  $size = $range['end']-$range['start']+1;
889  $this->http_status("206 partial");
890  header("Content-length: $size");
891  header("Content-range: $range[start]-$range[end]/"
892  . (isset($options['size']) ? $options['size'] : "*"));
893  while ($size && !feof($options['stream'])) {
894  $buffer = fread($options['stream'], 4096);
895  $size -= strlen($buffer);
896  echo $buffer;
897  }
898  } else {
899  $this->http_status("206 partial");
900  if (isset($options['size'])) {
901  header("Content-length: ".($options['size'] - $range['start']));
902  header("Content-range: $start-$end/"
903  . (isset($options['size']) ? $options['size'] : "*"));
904  }
905  fpassthru($options['stream']);
906  }
907  } else {
908  header("Content-length: ".$range['last']);
909  fseek($options['stream'], -$range['last'], SEEK_END);
910  fpassthru($options['stream']);
911  }
912  } else {
913  $this->_multipart_byterange_header(); // init multipart
914  foreach ($options['ranges'] as $range) {
915  // TODO what if size unknown? 500?
916  if (isset($range['start'])) {
917  $from = $range['start'];
918  $to = !empty($range['end']) ? $range['end'] : $options['size']-1;
919  } else {
920  $from = $options['size'] - $range['last']-1;
921  $to = $options['size'] -1;
922  }
923  $total = isset($options['size']) ? $options['size'] : "*";
924  $size = $to - $from + 1;
925  $this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
926 
927 
928  fseek($options['stream'], $start, SEEK_SET);
929  while ($size && !feof($options['stream'])) {
930  $buffer = fread($options['stream'], 4096);
931  $size -= strlen($buffer);
932  echo $buffer;
933  }
934  }
935  $this->_multipart_byterange_header(); // end multipart
936  }
937  } else {
938  // normal request or stream isn't seekable, return full content
939  if (isset($options['size'])) {
940  header("Content-length: ".$options['size']);
941  }
942 
943  // BEGIN WebDAV W. Randelshofer
944  // fpassthru apparently only delivers up to 2 million bytes.
945  // use fread instead
946  //fpassthru($options['stream']);
947  while (! feof($options['stream'])) {
948  $buffer = fread($options['stream'], 4096);
949  echo $buffer;
950  }
951  // END PATCH WebDAV W. Randelshofer
952 
953  return; // no more headers
954  }
955  } elseif (isset($options['data'])) {
956  if (is_array($options['data'])) {
957  // reply to partial request
958  } else {
959  header("Content-length: ".strlen($options['data']));
960  echo $options['data'];
961  }
962  }
963  }
964  }
965 
966  if (false === $status) {
967  // BEGIN WebDAV Randelshofer
968  $status = '404 Not Found';
969  //$this->http_status("404 not found");
970  // END PATCH WebDAV Randelshofer
971  }
972 
973  if (!headers_sent()) {
974  // TODO: check setting of headers in various code pathes above
975  $this->http_status("$status");
976  }
977  }
978 
979 
986  function _get_ranges(&$options)
987  {
988  // process Range: header if present
989  if (isset($_SERVER['HTTP_RANGE'])) {
990 
991  // we only support standard "bytes" range specifications for now
992  if (ereg("bytes[[:space:]]*=[[:space:]]*(.+)", $_SERVER['HTTP_RANGE'], $matches)) {
993  $options["ranges"] = array();
994 
995  // ranges are comma separated
996  foreach (explode(",", $matches[1]) as $range) {
997  // ranges are either from-to pairs or just end positions
998  list($start, $end) = explode("-", $range);
999  $options["ranges"][] = ($start==="")
1000  ? array("last"=>$end)
1001  : array("start"=>$start, "end"=>$end);
1002  }
1003  }
1004  }
1005  }
1006 
1020  function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false)
1021  {
1022  if ($mimetype === false) {
1023  if (!isset($this->multipart_separator)) {
1024  // initial
1025 
1026  // a little naive, this sequence *might* be part of the content
1027  // but it's really not likely and rather expensive to check
1028  $this->multipart_separator = "SEPARATOR_".md5(microtime());
1029 
1030  // generate HTTP header
1031  header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator);
1032  } else {
1033  // final
1034 
1035  // generate closing multipart sequence
1036  echo "\n--{$this->multipart_separator}--";
1037  }
1038  } else {
1039  // generate separator and header for next part
1040  echo "\n--{$this->multipart_separator}\n";
1041  echo "Content-type: $mimetype\n";
1042  echo "Content-range: $from-$to/". ($total === false ? "*" : $total);
1043  echo "\n\n";
1044  }
1045  }
1046 
1047 
1048 
1049  // }}}
1050 
1051  // {{{ http_HEAD()
1052 
1059  function http_HEAD()
1060  {
1061  $status = false;
1062  $options = Array();
1063  $options["path"] = $this->path;
1064 
1065  if (method_exists($this, "HEAD")) {
1066  $status = $this->head($options);
1067  } else if (method_exists($this, "GET")) {
1068  ob_start();
1069  $status = $this->GET($options);
1070  ob_end_clean();
1071  }
1072 
1073  if($status===true) $status = "200 OK";
1074  if($status===false) $status = "404 Not found";
1075 
1076  $this->http_status($status);
1077  }
1078 
1079  // }}}
1080 
1081  // {{{ http_PUT()
1082 
1089  function http_PUT()
1090  {
1091  if ($this->_check_lock_status($this->path)) {
1092  $options = Array();
1093  $options["path"] = $this->path;
1094  $options["content_length"] = $_SERVER["CONTENT_LENGTH"];
1095 
1096  // get the Content-type
1097  if (isset($_SERVER["CONTENT_TYPE"])) {
1098  // for now we do not support any sort of multipart requests
1099  if (!strncmp($_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
1100  $this->http_status("501 not implemented");
1101  echo "The service does not support mulipart PUT requests";
1102  return;
1103  }
1104  $options["content_type"] = $_SERVER["CONTENT_TYPE"];
1105  } else {
1106  // default content type if none given
1107  $options["content_type"] = "application/octet-stream";
1108  }
1109 
1110  /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1111  ignore any Content-* (e.g. Content-Range) headers that it
1112  does not understand or implement and MUST return a 501
1113  (Not Implemented) response in such cases."
1114  */
1115  foreach ($_SERVER as $key => $val) {
1116  if (strncmp($key, "HTTP_CONTENT", 11)) continue;
1117  switch ($key) {
1118  case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1119  // TODO support this if ext/zlib filters are available
1120  $this->http_status("501 not implemented");
1121  echo "The service does not support '$val' content encoding";
1122  return;
1123 
1124  case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1125  // we assume it is not critical if this one is ignored
1126  // in the actual PUT implementation ...
1127  $options["content_language"] = $value;
1128  break;
1129 
1130  case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1131  /* The meaning of the Content-Location header in PUT
1132  or POST requests is undefined; servers are free
1133  to ignore it in those cases. */
1134  break;
1135 
1136  case 'HTTP_CONTENT_RANGE': // RFC 2616 14.16
1137  // single byte range requests are supported
1138  // the header format is also specified in RFC 2616 14.16
1139  // TODO we have to ensure that implementations support this or send 501 instead
1140  if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $value, $matches)) {
1141  $this->http_status("400 bad request");
1142  echo "The service does only support single byte ranges";
1143  return;
1144  }
1145 
1146  $range = array("start"=>$matches[1], "end"=>$matches[2]);
1147  if (is_numeric($matches[3])) {
1148  $range["total_length"] = $matches[3];
1149  }
1150  $option["ranges"][] = $range;
1151 
1152  // TODO make sure the implementation supports partial PUT
1153  // this has to be done in advance to avoid data being overwritten
1154  // on implementations that do not support this ...
1155  break;
1156 
1157  case 'HTTP_CONTENT_MD5': // RFC 2616 14.15
1158  // TODO: maybe we can just pretend here?
1159  $this->http_status("501 not implemented");
1160  echo "The service does not support content MD5 checksum verification";
1161  return;
1162 
1163  default:
1164  // any other unknown Content-* headers
1165  $this->http_status("501 not implemented");
1166  echo "The service does not support '$key'";
1167  return;
1168  }
1169  }
1170 
1171  $options["stream"] = fopen("php://input", "r");
1172 
1173  $stat = $this->PUT($options);
1174 
1175  if ($stat == false) {
1176  $stat = "403 Forbidden";
1177  } else if (is_resource($stat) && get_resource_type($stat) == "stream") {
1178  $stream = $stat;
1179 
1180  $stat = $options["new"] ? "201 Created" : "204 No Content";
1181 
1182  if (!empty($options["ranges"])) {
1183  // TODO multipart support is missing (see also above)
1184  if (0 == fseek($stream, $range[0]["start"], SEEK_SET)) {
1185  $length = $range[0]["end"]-$range[0]["start"]+1;
1186  if (!fwrite($stream, fread($options["stream"], $length))) {
1187  $stat = "403 Forbidden";
1188  }
1189  } else {
1190  $stat = "403 Forbidden";
1191  }
1192  } else {
1193  while (!feof($options["stream"])) {
1194  // BEGIN WebDAV W. Randelshofer explicitly compare with false.
1195  if (false === ($written = fwrite($stream, fread($options["stream"], 4096)))) {
1196  // END WebDAV W. Randelshofer explicitly compare with false.
1197  $stat = "403 Forbidden";
1198  break;
1199  }
1200  $count += $written;
1201  }
1202  }
1203 
1204  fclose($stream);
1205  //$this->writelog('PUT wrote '.$written.' bytes');
1206  // BEGIN WebDAV W. Randelshofer finish the put-operation
1207  $this->PUTfinished($options);
1208  // END WebDAV W. Randelshofer finish the put-operation
1209  }
1210 
1211  $this->http_status($stat);
1212  } else {
1213  $this->http_status("423 Locked");
1214  }
1215  }
1216 
1217  // }}}
1218 
1219 
1220  // {{{ http_DELETE()
1221 
1228  function http_DELETE()
1229  {
1230  // check RFC 2518 Section 9.2, last paragraph
1231  if (isset($_SERVER["HTTP_DEPTH"])) {
1232  if ($_SERVER["HTTP_DEPTH"] != "infinity") {
1233  $this->http_status("400 Bad Request");
1234  return;
1235  }
1236  }
1237 
1238  // check lock status
1239  if ($this->_check_lock_status($this->path)) {
1240  // ok, proceed
1241  $options = Array();
1242  $options["path"] = $this->path;
1243 
1244  $stat = $this->delete($options);
1245 
1246  $this->http_status($stat);
1247  } else {
1248  // sorry, its locked
1249  $this->http_status("423 Locked");
1250  }
1251  }
1252 
1253  // }}}
1254 
1255  // {{{ http_COPY()
1256 
1263  function http_COPY()
1264  {
1265  // no need to check source lock status here
1266  // destination lock status is always checked by the helper method
1267  $this->_copymove("copy");
1268  }
1269 
1270  // }}}
1271 
1272  // {{{ http_MOVE()
1273 
1280  function http_MOVE()
1281  {
1282  //$this->writelog('MOVE()');
1283  if ($this->_check_lock_status($this->path)) {
1284  // destination lock status is always checked by the helper method
1285  $this->_copymove("move");
1286  } else {
1287  //$this->writelog('MOVE():423 Locked');
1288  $this->http_status("423 Locked");
1289  }
1290  }
1291 
1292  // }}}
1293 
1294 
1295  // {{{ http_LOCK()
1296 
1303  function http_LOCK()
1304  {
1305  $options = Array();
1306  $options["path"] = $this->path;
1307 
1308  if (isset($_SERVER['HTTP_DEPTH'])) {
1309  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1310  } else {
1311  $options["depth"] = "infinity";
1312  }
1313 
1314  if (isset($_SERVER["HTTP_TIMEOUT"])) {
1315  $options["timeout"] = explode(",", $_SERVER["HTTP_TIMEOUT"]);
1316  }
1317 
1318  if(empty($_SERVER['CONTENT_LENGTH']) && !empty($_SERVER['HTTP_IF'])) {
1319  // check if locking is possible
1320  if(!$this->_check_lock_status($this->path)) {
1321  $this->http_status("423 Locked");
1322  return;
1323  }
1324 
1325  // refresh lock
1326  $options["update"] = substr($_SERVER['HTTP_IF'], 2, -2);
1327  $stat = $this->lock($options);
1328  } else {
1329  // extract lock request information from request XML payload
1330  $lockinfo = new _parse_lockinfo("php://input");
1331  if (!$lockinfo->success) {
1332  $this->http_status("400 bad request");
1333  }
1334 
1335  // check if locking is possible
1336  if(!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) {
1337  $this->http_status("423 Locked");
1338  return;
1339  }
1340 
1341  // new lock
1342  $options["scope"] = $lockinfo->lockscope;
1343  $options["type"] = $lockinfo->locktype;
1344  $options["owner"] = $lockinfo->owner;
1345 
1346  $options["locktoken"] = $this->_new_locktoken();
1347 
1348  $stat = $this->lock($options);
1349  }
1350 
1351  if(is_bool($stat)) {
1352  $http_stat = $stat ? "200 OK" : "423 Locked";
1353  } else {
1354  $http_stat = $stat;
1355  }
1356 
1357  $this->http_status($http_stat);
1358 
1359  if ($http_stat{0} == 2) { // 2xx states are ok
1360  if($options["timeout"]) {
1361  // more than a million is considered an absolute timestamp
1362  // less is more likely a relative value
1363  if($options["timeout"]>1000000) {
1364  $timeout = "Second-".($options['timeout']-time());
1365  } else {
1366  $timeout = "Second-$options[timeout]";
1367  }
1368  } else {
1369  $timeout = "Infinite";
1370  }
1371  /*
1372  $this->writelog(
1373  'Content-Type: text/xml; charset="utf-8"'
1374  ."Lock-Token: <$options[locktoken]>"
1375  . "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
1376  . "<D:prop xmlns:D=\"DAV:\">\n"
1377  . " <D:lockdiscovery>\n"
1378  . " <D:activelock>\n"
1379  . " <D:lockscope><D:$options[scope]/></D:lockscope>\n"
1380  . " <D:locktype><D:$options[type]/></D:locktype>\n"
1381  . " <D:depth>$options[depth]</D:depth>\n"
1382  . " <D:owner>$options[owner]</D:owner>\n"
1383  . " <D:timeout>$timeout</D:timeout>\n"
1384  . " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n"
1385  . " </D:activelock>\n"
1386  . " </D:lockdiscovery>\n"
1387  . "</D:prop>\n\n"
1388  );*/
1389  header('Content-Type: text/xml; charset="utf-8"');
1390  header("Lock-Token: <$options[locktoken]>");
1391  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1392  echo "<D:prop xmlns:D=\"DAV:\">\n";
1393  echo " <D:lockdiscovery>\n";
1394  echo " <D:activelock>\n";
1395  echo " <D:lockscope><D:$options[scope]/></D:lockscope>\n";
1396  echo " <D:locktype><D:$options[type]/></D:locktype>\n";
1397  echo " <D:depth>$options[depth]</D:depth>\n";
1398  echo " <D:owner>$options[owner]</D:owner>\n";
1399  echo " <D:timeout>$timeout</D:timeout>\n";
1400  echo " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n";
1401  echo " </D:activelock>\n";
1402  echo " </D:lockdiscovery>\n";
1403  echo "</D:prop>\n\n";
1404  }
1405  }
1406 
1407 
1408  // }}}
1409 
1410  // {{{ http_UNLOCK()
1411 
1418  function http_UNLOCK()
1419  {
1420  $options = Array();
1421  $options["path"] = $this->path;
1422 
1423  if (isset($_SERVER['HTTP_DEPTH'])) {
1424  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1425  } else {
1426  $options["depth"] = "infinity";
1427  }
1428 
1429  // strip surrounding <>
1430  $options["token"] = substr(trim($_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
1431 //$this->writelog('http_UNLOCK HTTP_LOCK_TOKEN='.$_SERVER["HTTP_LOCK_TOKEN"]);
1432  // call user method
1433  $stat = $this->unlock($options);
1434 
1435  $this->http_status($stat);
1436  }
1437 
1438  // }}}
1439 
1440  // }}}
1441 
1442  // {{{ _copymove()
1443 
1444  function _copymove($what)
1445  {
1446  //$this->writelog('_copymove('.$what.')');
1447  $options = Array();
1448  $options["path"] = $this->path;
1449 
1450  if (isset($_SERVER["HTTP_DEPTH"])) {
1451  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1452  } else {
1453  $options["depth"] = "infinity";
1454  }
1455 //$this->writelog('_copymove dest='.$_SERVER["HTTP_DESTINATION"]);
1456  extract(parse_url($_SERVER["HTTP_DESTINATION"]));
1457  // BEGIN WebDAV: decode path (bereits in PEAR CVS gefixt)
1458  // We must decode the target path too.
1459  $path = $this->_urldecode($path);
1460  // END Patch WebDAV: decode path
1461  $http_host = $host;
1462  if (isset($port) && $port != 80)
1463  $http_host.= ":$port";
1464 
1465  list($http_header_host,$http_header_port) = explode(":",$_SERVER["HTTP_HOST"]);
1466  if (isset($http_header_port) && $http_header_port != 80) {
1467  $http_header_host .= ":".$http_header_port;
1468  }
1469 
1470  if ($http_host == $http_header_host &&
1471  !strncmp($_SERVER["SCRIPT_NAME"], $path,
1472  strlen($_SERVER["SCRIPT_NAME"]))) {
1473  $options["dest"] = substr($path, strlen($_SERVER["SCRIPT_NAME"]));
1474  //$this->writelog('_copymove() dest='.$options['dest']);
1475  if (!$this->_check_lock_status($options["dest"])) {
1476  //$this->writelog('_copymove():423 Locked');
1477  $this->http_status("423 Locked");
1478  return;
1479  }
1480  //$this->writelog('_copymove() ...');
1481 
1482  } else {
1483  $options["dest_url"] = $_SERVER["HTTP_DESTINATION"];
1484  }
1485 
1486  // see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3
1487  if (isset($_SERVER["HTTP_OVERWRITE"])) {
1488  $options["overwrite"] = $_SERVER["HTTP_OVERWRITE"] == "T";
1489  } else {
1490  $options["overwrite"] = true;
1491  }
1492 
1493  $stat = $this->$what($options);
1494  $this->http_status($stat);
1495  }
1496 
1497  // }}}
1498 
1499  // {{{ _allow()
1500 
1507  function _allow()
1508  {
1509  // OPTIONS is always there
1510  $allow = array("OPTIONS" =>"OPTIONS");
1511 
1512  // all other METHODS need both a http_method() wrapper
1513  // and a method() implementation
1514  // the base class supplies wrappers only
1515  foreach(get_class_methods($this) as $method) {
1516  if (!strncmp("http_", $method, 5)) {
1517  $method = strtoupper(substr($method, 5));
1518  if (method_exists($this, $method)) {
1519  $allow[$method] = $method;
1520  }
1521  }
1522  }
1523 
1524  // we can emulate a missing HEAD implemetation using GET
1525  if (isset($allow["GET"]))
1526  $allow["HEAD"] = "HEAD";
1527 
1528  // no LOCK without checklok()
1529  if (!method_exists($this, "checklock")) {
1530  unset($allow["LOCK"]);
1531  unset($allow["UNLOCK"]);
1532  }
1533 
1534  return $allow;
1535  }
1536 
1537  // }}}
1538 
1547  function mkprop()
1548  {
1549  $args = func_get_args();
1550  if (count($args) == 3) {
1551  return array("ns" => $args[0],
1552  "name" => $args[1],
1553  "val" => $args[2]);
1554  } else {
1555  return array("ns" => "DAV:",
1556  "name" => $args[0],
1557  "val" => $args[1]);
1558  }
1559  }
1560 
1561  // {{{ _check_auth
1562 
1569  function _check_auth()
1570  {
1571  if (method_exists($this, "checkAuth")) {
1572  // PEAR style method name
1573  return $this->checkAuth(@$_SERVER["AUTH_TYPE"],
1574  @$_SERVER["PHP_AUTH_USER"],
1575  @$_SERVER["PHP_AUTH_PW"]);
1576  } else if (method_exists($this, "check_auth")) {
1577  // old (pre 1.0) method name
1578  return $this->check_auth(@$_SERVER["AUTH_TYPE"],
1579  @$_SERVER["PHP_AUTH_USER"],
1580  @$_SERVER["PHP_AUTH_PW"]);
1581  } else {
1582  // no method found -> no authentication required
1583  return true;
1584  }
1585  }
1586 
1587  // }}}
1588 
1589  // {{{ UUID stuff
1590 
1597  function _new_uuid()
1598  {
1599  // use uuid extension from PECL if available
1600  if (function_exists("uuid_create")) {
1601  return uuid_create();
1602  }
1603 
1604  // fallback
1605  $uuid = md5(microtime().getmypid()); // this should be random enough for now
1606 
1607  // set variant and version fields for 'true' random uuid
1608  $uuid{12} = "4";
1609  $n = 8 + (ord($uuid{16}) & 3);
1610  $hex = "0123456789abcdef";
1611  $uuid{16} = $hex{$n};
1612 
1613  // return formated uuid
1614  return substr($uuid, 0, 8)."-"
1615  . substr($uuid, 8, 4)."-"
1616  . substr($uuid, 12, 4)."-"
1617  . substr($uuid, 16, 4)."-"
1618  . substr($uuid, 20);
1619  }
1620 
1627  function _new_locktoken()
1628  {
1629  return "opaquelocktoken:".$this->_new_uuid();
1630  }
1631 
1632  // }}}
1633 
1634  // {{{ WebDAV If: header parsing
1635 
1643  function _if_header_lexer($string, &$pos)
1644  {
1645  // skip whitespace
1646  while (ctype_space($string{$pos})) {
1647  ++$pos;
1648  }
1649 
1650  // already at end of string?
1651  if (strlen($string) <= $pos) {
1652  return false;
1653  }
1654 
1655  // get next character
1656  $c = $string{$pos++};
1657 
1658  // now it depends on what we found
1659  switch ($c) {
1660  case "<":
1661  // URIs are enclosed in <...>
1662  $pos2 = strpos($string, ">", $pos);
1663  $uri = substr($string, $pos, $pos2 - $pos);
1664  $pos = $pos2 + 1;
1665  return array("URI", $uri);
1666 
1667  case "[":
1668  //Etags are enclosed in [...]
1669  if ($string{$pos} == "W") {
1670  $type = "ETAG_WEAK";
1671  $pos += 2;
1672  } else {
1673  $type = "ETAG_STRONG";
1674  }
1675  $pos2 = strpos($string, "]", $pos);
1676  $etag = substr($string, $pos + 1, $pos2 - $pos - 2);
1677  $pos = $pos2 + 1;
1678  return array($type, $etag);
1679 
1680  case "N":
1681  // "N" indicates negation
1682  $pos += 2;
1683  return array("NOT", "Not");
1684 
1685  default:
1686  // anything else is passed verbatim char by char
1687  return array("CHAR", $c);
1688  }
1689  }
1690 
1697  function _if_header_parser($str)
1698  {
1699  $pos = 0;
1700  $len = strlen($str);
1701 
1702  $uris = array();
1703 
1704  // parser loop
1705  while ($pos < $len) {
1706  // get next token
1707  $token = $this->_if_header_lexer($str, $pos);
1708 
1709  // check for URI
1710  if ($token[0] == "URI") {
1711  $uri = $token[1]; // remember URI
1712  $token = $this->_if_header_lexer($str, $pos); // get next token
1713  } else {
1714  $uri = "";
1715  }
1716 
1717  // sanity check
1718  if ($token[0] != "CHAR" || $token[1] != "(") {
1719  return false;
1720  }
1721 
1722  $list = array();
1723  $level = 1;
1724  $not = "";
1725  while ($level) {
1726  $token = $this->_if_header_lexer($str, $pos);
1727  if ($token[0] == "NOT") {
1728  $not = "!";
1729  continue;
1730  }
1731  switch ($token[0]) {
1732  case "CHAR":
1733  switch ($token[1]) {
1734  case "(":
1735  $level++;
1736  break;
1737  case ")":
1738  $level--;
1739  break;
1740  default:
1741  return false;
1742  }
1743  break;
1744 
1745  case "URI":
1746  $list[] = $not."<$token[1]>";
1747  break;
1748 
1749  case "ETAG_WEAK":
1750  $list[] = $not."[W/'$token[1]']>";
1751  break;
1752 
1753  case "ETAG_STRONG":
1754  $list[] = $not."['$token[1]']>";
1755  break;
1756 
1757  default:
1758  return false;
1759  }
1760  $not = "";
1761  }
1762 
1763  if (@is_array($uris[$uri])) {
1764  $uris[$uri] = array_merge($uris[$uri],$list);
1765  } else {
1766  $uris[$uri] = $list;
1767  }
1768  }
1769 
1770  return $uris;
1771  }
1772 
1783  {
1784  if (isset($_SERVER["HTTP_IF"])) {
1785  $this->_if_header_uris =
1786  $this->_if_header_parser($_SERVER["HTTP_IF"]);
1787 
1788  foreach($this->_if_header_uris as $uri => $conditions) {
1789  if ($uri == "") {
1790  $uri = $this->uri;
1791  }
1792  // all must match
1793  $state = true;
1794  foreach($conditions as $condition) {
1795  // lock tokens may be free form (RFC2518 6.3)
1796  // but if opaquelocktokens are used (RFC2518 6.4)
1797  // we have to check the format (litmus tests this)
1798  if (!strncmp($condition, "<opaquelocktoken:", strlen("<opaquelocktoken"))) {
1799  if (!ereg("^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$", $condition)) {
1800  return false;
1801  }
1802  }
1803  if (!$this->_check_uri_condition($uri, $condition)) {
1804  $state = false;
1805  break;
1806  }
1807  }
1808 
1809  // any match is ok
1810  if ($state == true) {
1811  return true;
1812  }
1813  }
1814  return false;
1815  }
1816  return true;
1817  }
1818 
1829  function _check_uri_condition($uri, $condition)
1830  {
1831  // not really implemented here,
1832  // implementations must override
1833  return true;
1834  }
1835 
1836 
1843  function _check_lock_status($path, $exclusive_only = false)
1844  {
1845  // FIXME depth -> ignored for now
1846  if (method_exists($this, "checkLock")) {
1847  // is locked?
1848  $lock = $this->checkLock($path);
1849 
1850  // ... and lock is not owned?
1851  if (is_array($lock) && count($lock)) {
1852  // FIXME doesn't check uri restrictions yet
1853  if (!strstr($_SERVER["HTTP_IF"], $lock["token"])) {
1854  if (!$exclusive_only || ($lock["scope"] !== "shared"))
1855  return false;
1856  }
1857  }
1858  }
1859  return true;
1860  }
1861 
1862 
1863  // }}}
1864 
1865 
1873  {
1874  // no lock support without checklock() method
1875  if (!method_exists($this, "checklock")) {
1876  return "";
1877  }
1878 
1879  // collect response here
1880  $activelocks = "";
1881 
1882  // get checklock() reply
1883  $lock = $this->checklock($path);
1884 
1885  // generate <activelock> block for returned data
1886  if (is_array($lock) && count($lock)) {
1887  // check for 'timeout' or 'expires'
1888  if (!empty($lock["expires"])) {
1889  $timeout = "Second-".($lock["expires"] - time());
1890  } else if (!empty($lock["timeout"])) {
1891  $timeout = "Second-$lock[timeout]";
1892  } else {
1893  $timeout = "Infinite";
1894  }
1895 
1896  // genreate response block
1897  $activelocks.= "
1898  <D:activelock>
1899  <D:lockscope><D:$lock[scope]/></D:lockscope>
1900  <D:locktype><D:$lock[type]/></D:locktype>
1901  <D:depth>$lock[depth]</D:depth>
1902  <D:owner>$lock[owner]</D:owner>
1903  <D:timeout>$timeout</D:timeout>
1904  <D:locktoken><D:href>$lock[token]</D:href></D:locktoken>
1905  </D:activelock>
1906  ";
1907  }
1908  //$this->writelog('lockdiscovery('.$path.'):'.$activeclocks);
1909 
1910  // return generated response
1911  return $activelocks;
1912  }
1913 
1920  function http_status($status)
1921  {
1922  // simplified success case
1923  if($status === true) {
1924  $status = "200 OK";
1925  }
1926  //$this->writelog('http_status('.$status.')');
1927 
1928  // remember status
1929  $this->_http_status = $status;
1930 
1931  // generate HTTP status response
1932  header("HTTP/1.1 $status");
1933  header("X-WebDAV-Status: $status", true);
1934  }
1935 
1945  function _urlencode($url)
1946  {
1947  return strtr($url, array(" "=>"%20",
1948  "&"=>"%26",
1949  "<"=>"%3C",
1950  ">"=>"%3E",
1951  ));
1952  }
1953 
1962  function _urldecode($path)
1963  {
1964  // BEGIN WebDAV
1965  // urldecode wrongly replaces '+' characters by ' ' characters.
1966  // We replace '+' into '%2b' before passing the path through urldecode.
1967  //return urldecode($path);
1968  $result =& urldecode(str_replace('+','%2b',$path));
1969  //$this->writelog('_urldecode('.$path.'):'.$result);
1970  return $result;
1971  // END PATCH WebDAV
1972  }
1973 
1980  function _prop_encode($text)
1981  {
1982  switch (strtolower($this->_prop_encoding)) {
1983  case "utf-8":
1984  return $text;
1985  case "iso-8859-1":
1986  case "iso-8859-15":
1987  case "latin-1":
1988  default:
1989  return utf8_encode($text);
1990  }
1991  }
1992 
1999  function _slashify($path) {
2000  if ($path[strlen($path)-1] != '/') {
2001  $path = $path."/";
2002  }
2003  return $path;
2004  }
2005 // BEGIN WebDAV
2012  private function writelog($message)
2013  {
2014  global $log, $ilUser;
2015 
2016  $log->write(
2017  $ilUser->getLogin()
2018  .' DAV Server.'.str_replace("\n",";",$message)
2019  );
2020  /*
2021  if ($this->logFile)
2022  {
2023  $fh = fopen($this->logFile, 'a');
2024  fwrite($fh, date('Y-m-d h:i:s '));
2025  fwrite($fh, str_replace("\n",";",$message));
2026  fwrite($fh, "\n\n");
2027  fclose($fh);
2028  }*/
2029  }
2030 // END PATCH WebDAV
2031 }
2032 
2033  /*
2034  * Local variables:
2035  * tab-width: 4
2036  * c-basic-offset: 4
2037  * End:
2038  */
2039 ?>