ILIAS  Release_3_10_x_branch Revision 61812
 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 require_once "Services/WebDAV/classes/Tools/_parse_propfind.php";
23 require_once "Services/WebDAV/classes/Tools/_parse_proppatch.php";
24 require_once "Services/WebDAV/classes/Tools/_parse_lockinfo.php";
25 
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  $uri = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:");
129  $uri.= "//$_SERVER[HTTP_HOST]$_SERVER[SCRIPT_NAME]";
130 
131  $this->base_uri = $uri;
132  $this->uri = $uri . $_SERVER[PATH_INFO];
133 
134  // identify ourselves
135  if (empty($this->dav_powered_by)) {
136  header("X-Dav-Powered-By: PHP class: ".get_class($this));
137  } else {
138  header("X-Dav-Powered-By: ".$this->dav_powered_by );
139  }
140 
141  // check authentication
142  if (!$this->_check_auth()) {
143  // RFC2518 says we must use Digest instead of Basic
144  // but Microsoft Clients do not support Digest
145  // and we don't support NTLM and Kerberos
146  // so we are stuck with Basic here
147  header('WWW-Authenticate: Basic realm="'.($this->http_auth_realm).'"');
148 
149  // Windows seems to require this being the last header sent
150  // (changed according to PECL bug #3138)
151  $this->http_status('401 Unauthorized');
152 
153  return;
154  }
155 
156  // check
157  if(! $this->_check_if_header_conditions()) {
158  $this->http_status("412 Precondition failed");
159  return;
160  }
161 
162  // set path
163  $this->path = $this->_urldecode($_SERVER["PATH_INFO"]);
164  if (!strlen($this->path)) {
165  header("Location: ".$this->base_uri."/");
166  //$this->writelog('HTTP_WebDAV_Server.ServeRequest() missing path info');
167  $this->path = '/';
168  //exit;
169  }
170  // BEGIN WebDAV: Don't strip backslashes. Backslashes are a valid part of a unix filename!
171  /*
172  if(ini_get("magic_quotes_gpc")) {
173  $this->path = stripslashes($this->path);
174  }*/
175  // END PATCH WebDAV: Don't strip backslashes. Backslashes are a valid part of a unix filename!
176 
177 
178  // detect requested method names
179  $method = strtolower($_SERVER["REQUEST_METHOD"]);
180  $wrapper = "http_".$method;
181 
182  // activate HEAD emulation by GET if no HEAD method found
183  if ($method == "head" && !method_exists($this, "head")) {
184  $method = "get";
185  }
186 
187  if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) {
188  $this->$wrapper(); // call method by name
189  } else { // method not found/implemented
190  if ($_SERVER["REQUEST_METHOD"] == "LOCK") {
191  $this->http_status("412 Precondition failed");
192  } else {
193  $this->http_status("405 Method not allowed");
194  header("Allow: ".join(", ", $this->_allow())); // tell client what's allowed
195  }
196  }
197  }
198 
199  // }}}
200 
201  // {{{ abstract WebDAV methods
202 
203  // {{{ GET()
222  /* abstract
223  function GET(&$params)
224  {
225  // dummy entry for PHPDoc
226  }
227  */
228 
229  // }}}
230 
231  // {{{ PUT()
242  /* abstract
243  function PUT()
244  {
245  // dummy entry for PHPDoc
246  }
247  */
248 
249  // }}}
250 
251  // {{{ COPY()
252 
263  /* abstract
264  function COPY()
265  {
266  // dummy entry for PHPDoc
267  }
268  */
269 
270  // }}}
271 
272  // {{{ MOVE()
273 
284  /* abstract
285  function MOVE()
286  {
287  // dummy entry for PHPDoc
288  }
289  */
290 
291  // }}}
292 
293  // {{{ DELETE()
294 
305  /* abstract
306  function DELETE()
307  {
308  // dummy entry for PHPDoc
309  }
310  */
311  // }}}
312 
313  // {{{ PROPFIND()
314 
325  /* abstract
326  function PROPFIND()
327  {
328  // dummy entry for PHPDoc
329  }
330  */
331 
332  // }}}
333 
334  // {{{ PROPPATCH()
335 
346  /* abstract
347  function PROPPATCH()
348  {
349  // dummy entry for PHPDoc
350  }
351  */
352  // }}}
353 
354  // {{{ LOCK()
355 
366  /* abstract
367  function LOCK()
368  {
369  // dummy entry for PHPDoc
370  }
371  */
372  // }}}
373 
374  // {{{ UNLOCK()
375 
386  /* abstract
387  function UNLOCK()
388  {
389  // dummy entry for PHPDoc
390  }
391  */
392  // }}}
393 
394  // }}}
395 
396  // {{{ other abstract methods
397 
398  // {{{ check_auth()
399 
412  /* abstract
413  function checkAuth($type, $username, $password)
414  {
415  // dummy entry for PHPDoc
416  }
417  */
418 
419  // }}}
420 
421  // {{{ checklock()
422 
435  /* abstract
436  function checklock($resource)
437  {
438  // dummy entry for PHPDoc
439  }
440  */
441 
442  // }}}
443 
444  // }}}
445 
446  // {{{ WebDAV HTTP method wrappers
447 
448  // {{{ http_OPTIONS()
449 
460  function http_OPTIONS()
461  {
462  // Microsoft clients default to the Frontpage protocol
463  // unless we tell them to use WebDAV
464  header("MS-Author-Via: DAV");
465 
466  // get allowed methods
467  $allow = $this->_allow();
468 
469  // dav header
470  $dav = array(1); // assume we are always dav class 1 compliant
471  if (isset($allow['LOCK'])) {
472  $dav[] = 2; // dav class 2 requires that locking is supported
473  }
474 
475  // tell clients what we found
476  $this->http_status("200 OK");
477  header("DAV: " .join("," , $dav));
478  header("Allow: ".join(", ", $allow));
479 //$this->writelog('http_Options(): dav='.var_export($dav,true).' allow='.var_export($allow,true));
480  header("Content-length: 0");
481  }
482 
483  // }}}
484 
485 
486  // {{{ http_PROPFIND()
487 
494  function http_PROPFIND()
495  {
496  $options = Array();
497  $options["path"] = $this->path;
498 
499  // search depth from header (default is "infinity)
500  if (isset($_SERVER['HTTP_DEPTH'])) {
501  $options["depth"] = $_SERVER["HTTP_DEPTH"];
502  } else {
503  $options["depth"] = "infinity";
504  }
505 
506  // analyze request payload
507  $propinfo = new _parse_propfind("php://input");
508  if (!$propinfo->success) {
509  $this->http_status("400 Error");
510  return;
511  }
512  $options['props'] = $propinfo->props;
513 
514  // call user handler
515  $files = array();
516  if (!$this->propfind($options, $files)) {
517  $this->http_status("404 Not Found");
518  return;
519  }
520 
521  // collect namespaces here
522  $ns_hash = array();
523 
524  // Microsoft Clients need this special namespace for date and time values
525  $ns_defs = "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\"";
526 
527  // now we loop over all returned file entries
528  foreach($files["files"] as $filekey => $file) {
529 
530  // nothing to do if no properties were returend for a file
531  if (!isset($file["props"]) || !is_array($file["props"])) {
532  continue;
533  }
534 
535  // now loop over all returned properties
536  foreach($file["props"] as $key => $prop) {
537  // as a convenience feature we do not require that user handlers
538  // restrict returned properties to the requested ones
539  // here we strip all unrequested entries out of the response
540 
541  switch($options['props']) {
542  case "all":
543  // nothing to remove
544  break;
545 
546  case "names":
547  // only the names of all existing properties were requested
548  // so we remove all values
549  unset($files["files"][$filekey]["props"][$key]["val"]);
550  break;
551 
552  default:
553  $found = false;
554 
555  // search property name in requested properties
556  foreach((array)$options["props"] as $reqprop) {
557  if ( $reqprop["name"] == $prop["name"]
558  && $reqprop["xmlns"] == $prop["ns"]) {
559  $found = true;
560  break;
561  }
562  }
563 
564  // unset property and continue with next one if not found/requested
565  if (!$found) {
566  $files["files"][$filekey]["props"][$key]="";
567  continue(2);
568  }
569  break;
570  }
571 
572  // namespace handling
573  if (empty($prop["ns"])) continue; // no namespace
574  $ns = $prop["ns"];
575  if ($ns == "DAV:") continue; // default namespace
576  if (isset($ns_hash[$ns])) continue; // already known
577 
578  // register namespace
579  $ns_name = "ns".(count($ns_hash) + 1);
580  $ns_hash[$ns] = $ns_name;
581  $ns_defs .= " xmlns:$ns_name=\"$ns\"";
582  }
583 
584  // we also need to add empty entries for properties that were requested
585  // but for which no values where returned by the user handler
586  if (is_array($options['props'])) {
587  foreach($options["props"] as $reqprop) {
588  if($reqprop['name']=="") continue; // skip empty entries
589 
590  $found = false;
591 
592  // check if property exists in result
593  foreach($file["props"] as $prop) {
594  if ( $reqprop["name"] == $prop["name"]
595  && $reqprop["xmlns"] == $prop["ns"]) {
596  $found = true;
597  break;
598  }
599  }
600 
601  if (!$found) {
602  if($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
603  // lockdiscovery is handled by the base class
604  $files["files"][$filekey]["props"][]
605  = $this->mkprop("DAV:",
606  "lockdiscovery" ,
607  $this->lockdiscovery($files["files"][$filekey]['path']));
608  } else {
609  // add empty value for this property
610  $files["files"][$filekey]["noprops"][] =
611  $this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
612 
613  // register property namespace if not known yet
614  if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) {
615  $ns_name = "ns".(count($ns_hash) + 1);
616  $ns_hash[$reqprop["xmlns"]] = $ns_name;
617  $ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\"";
618  }
619  }
620  }
621  }
622  }
623  }
624 
625  // now we generate the reply header ...
626  $this->http_status("207 Multi-Status");
627  header('Content-Type: text/xml; charset="utf-8"');
628 
629  // ... and payload
630  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
631  echo "<D:multistatus xmlns:D=\"DAV:\">\n";
632 
633  foreach($files["files"] as $file) {
634  // ignore empty or incomplete entries
635  if(!is_array($file) || empty($file) || !isset($file["path"])) continue;
636  $path = $file['path'];
637  if(!is_string($path) || $path==="") continue;
638 
639  echo " <D:response $ns_defs>\n";
640 
641  // BEGIN WebDAV W. Randelshofer Don't slashify path because it confuses Mac OS X
642  //$href = $this->_slashify($_SERVER['SCRIPT_NAME'] . $path);
643  $href = $_SERVER['SCRIPT_NAME'] . $path;
644  //END PATCH WebDAV W. Randelshofer
645 
646  echo " <D:href>$href</D:href>\n";
647 
648  // report all found properties and their values (if any)
649  if (isset($file["props"]) && is_array($file["props"])) {
650  echo " <D:propstat>\n";
651  echo " <D:prop>\n";
652 
653  foreach($file["props"] as $key => $prop) {
654 
655  if (!is_array($prop)) continue;
656  if (!isset($prop["name"])) continue;
657  if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
658  // empty properties (cannot use empty() for check as "0" is a legal value here)
659  if($prop["ns"]=="DAV:") {
660  echo " <D:$prop[name]/>\n";
661  } else if(!empty($prop["ns"])) {
662  echo " <".$ns_hash[$prop["ns"]].":$prop[name]/>\n";
663  } else {
664  echo " <$prop[name] xmlns=\"\"/>";
665  }
666  } else if ($prop["ns"] == "DAV:") {
667  // some WebDAV properties need special treatment
668  switch ($prop["name"]) {
669  case "creationdate":
670  echo " <D:creationdate ns0:dt=\"dateTime.tz\">"
671  // BEGIN WebDAV W. Randelshofer
672  . gmdate("Y-m-d\\TH:i:s\\Z",$prop['val'])
673  // . gmdate("D, d M Y H:i:s ", $prop['val'])
674  // END PATCH WebDAV W. Randelshofer
675  . "</D:creationdate>\n";
676  break;
677  case "getlastmodified":
678  echo " <D:getlastmodified ns0:dt=\"dateTime.rfc1123\">"
679  . gmdate("D, d M Y H:i:s ", $prop['val'])
680  . "GMT</D:getlastmodified>\n";
681  break;
682  case "resourcetype":
683  echo " <D:resourcetype><D:$prop[val]/></D:resourcetype>\n";
684  break;
685  case "supportedlock":
686  echo " <D:supportedlock>$prop[val]</D:supportedlock>\n";
687  break;
688  case "lockdiscovery":
689  echo " <D:lockdiscovery>\n";
690  echo $prop["val"];
691  echo " </D:lockdiscovery>\n";
692  break;
693  default:
694  echo " <D:$prop[name]>"
695  . $this->_prop_encode(htmlspecialchars($prop['val']))
696  . "</D:$prop[name]>\n";
697  break;
698  }
699  } else {
700  // properties from namespaces != "DAV:" or without any namespace
701  if ($prop["ns"]) {
702  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>"
703  . $this->_prop_encode(htmlspecialchars($prop['val']))
704  . "</" . $ns_hash[$prop["ns"]] . ":$prop[name]>\n";
705  } else {
706  echo " <$prop[name] xmlns=\"\">"
707  . $this->_prop_encode(htmlspecialchars($prop['val']))
708  . "</$prop[name]>\n";
709  }
710  }
711  }
712 
713  echo " </D:prop>\n";
714  echo " <D:status>HTTP/1.1 200 OK</D:status>\n";
715  echo " </D:propstat>\n";
716  }
717 
718  // now report all properties requested but not found
719  if (isset($file["noprops"])) {
720  echo " <D:propstat>\n";
721  echo " <D:prop>\n";
722 
723  foreach($file["noprops"] as $key => $prop) {
724  if ($prop["ns"] == "DAV:") {
725  echo " <D:$prop[name]/>\n";
726  } else if ($prop["ns"] == "") {
727  echo " <$prop[name] xmlns=\"\"/>\n";
728  } else {
729  echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
730  }
731  }
732 
733  echo " </D:prop>\n";
734  echo " <D:status>HTTP/1.1 404 Not Found</D:status>\n";
735  echo " </D:propstat>\n";
736  }
737 
738  echo " </D:response>\n";
739  }
740 
741  echo "</D:multistatus>\n";
742  }
743 
744 
745  // }}}
746 
747  // {{{ http_PROPPATCH()
748 
755  function http_PROPPATCH()
756  {
757  if($this->_check_lock_status($this->path)) {
758  $options = Array();
759  $options["path"] = $this->path;
760 
761  $propinfo = new _parse_proppatch("php://input");
762 
763  if (!$propinfo->success) {
764  $this->http_status("400 Error");
765  return;
766  }
767 
768  $options['props'] = $propinfo->props;
769 
770  $responsedescr = $this->proppatch($options);
771 
772  $this->http_status("207 Multi-Status");
773  header('Content-Type: text/xml; charset="utf-8"');
774 
775  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
776 
777  echo "<D:multistatus xmlns:D=\"DAV:\">\n";
778  echo " <D:response>\n";
779  echo " <D:href>".$this->_urlencode($_SERVER["SCRIPT_NAME"].$this->path)."</D:href>\n";
780 
781  foreach($options["props"] as $prop) {
782  echo " <D:propstat>\n";
783  echo " <D:prop><$prop[name] xmlns=\"$prop[ns]\"/></D:prop>\n";
784  echo " <D:status>HTTP/1.1 $prop[status]</D:status>\n";
785  echo " </D:propstat>\n";
786  }
787 
788  if ($responsedescr) {
789  echo " <D:responsedescription>".
790  $this->_prop_encode(htmlspecialchars($responsedescr)).
791  "</D:responsedescription>\n";
792  }
793 
794  echo " </D:response>\n";
795  echo "</D:multistatus>\n";
796  } else {
797  $this->http_status("423 Locked");
798  }
799  }
800 
801  // }}}
802 
803 
804  // {{{ http_MKCOL()
805 
812  function http_MKCOL()
813  {
814  $options = Array();
815  $options["path"] = $this->path;
816 
817  $stat = $this->mkcol($options);
818 
819  $this->http_status($stat);
820  }
821 
822  // }}}
823 
824 
825  // {{{ http_GET()
826 
833  function http_GET()
834  {
835  // TODO check for invalid stream
836  $options = Array();
837  $options["path"] = $this->path;
838 
839  $this->_get_ranges($options);
840 
841  if (true === ($status = $this->get($options))) {
842  if (!headers_sent()) {
843  $status = "200 OK";
844 
845  if (!isset($options['mimetype'])) {
846  $options['mimetype'] = "application/octet-stream";
847  }
848  header("Content-type: $options[mimetype]");
849 
850  if (isset($options['mtime'])) {
851  header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
852  }
853 
854  if (isset($options['stream'])) {
855  // GET handler returned a stream
856  if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
857  // partial request and stream is seekable
858 
859  if (count($options['ranges']) === 1) {
860  $range = $options['ranges'][0];
861 
862  if (isset($range['start'])) {
863  fseek($options['stream'], $range['start'], SEEK_SET);
864  if (feof($options['stream'])) {
865  $this->http_status("416 Requested range not satisfiable");
866  exit;
867  }
868 
869  if (isset($range['end'])) {
870  $size = $range['end']-$range['start']+1;
871  $this->http_status("206 partial");
872  header("Content-length: $size");
873  header("Content-range: $range[start]-$range[end]/"
874  . (isset($options['size']) ? $options['size'] : "*"));
875  while ($size && !feof($options['stream'])) {
876  $buffer = fread($options['stream'], 4096);
877  $size -= strlen($buffer);
878  echo $buffer;
879  }
880  } else {
881  $this->http_status("206 partial");
882  if (isset($options['size'])) {
883  header("Content-length: ".($options['size'] - $range['start']));
884  header("Content-range: $start-$end/"
885  . (isset($options['size']) ? $options['size'] : "*"));
886  }
887  fpassthru($options['stream']);
888  }
889  } else {
890  header("Content-length: ".$range['last']);
891  fseek($options['stream'], -$range['last'], SEEK_END);
892  fpassthru($options['stream']);
893  }
894  } else {
895  $this->_multipart_byterange_header(); // init multipart
896  foreach ($options['ranges'] as $range) {
897  // TODO what if size unknown? 500?
898  if (isset($range['start'])) {
899  $from = $range['start'];
900  $to = !empty($range['end']) ? $range['end'] : $options['size']-1;
901  } else {
902  $from = $options['size'] - $range['last']-1;
903  $to = $options['size'] -1;
904  }
905  $total = isset($options['size']) ? $options['size'] : "*";
906  $size = $to - $from + 1;
907  $this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
908 
909 
910  fseek($options['stream'], $start, SEEK_SET);
911  while ($size && !feof($options['stream'])) {
912  $buffer = fread($options['stream'], 4096);
913  $size -= strlen($buffer);
914  echo $buffer;
915  }
916  }
917  $this->_multipart_byterange_header(); // end multipart
918  }
919  } else {
920  // normal request or stream isn't seekable, return full content
921  if (isset($options['size'])) {
922  header("Content-length: ".$options['size']);
923  }
924 
925  // BEGIN WebDAV W. Randelshofer
926  // fpassthru apparently only delivers up to 2 million bytes.
927  // use fread instead
928  //fpassthru($options['stream']);
929  while (! feof($options['stream'])) {
930  $buffer = fread($options['stream'], 4096);
931  echo $buffer;
932  }
933  // END PATCH WebDAV W. Randelshofer
934 
935  return; // no more headers
936  }
937  } elseif (isset($options['data'])) {
938  if (is_array($options['data'])) {
939  // reply to partial request
940  } else {
941  header("Content-length: ".strlen($options['data']));
942  echo $options['data'];
943  }
944  }
945  }
946  }
947 
948  if (false === $status) {
949  // BEGIN WebDAV Randelshofer
950  $status = '404 Not Found';
951  //$this->http_status("404 not found");
952  // END PATCH WebDAV Randelshofer
953  }
954 
955  if (!headers_sent()) {
956  // TODO: check setting of headers in various code pathes above
957  $this->http_status("$status");
958  }
959  }
960 
961 
968  function _get_ranges(&$options)
969  {
970  // process Range: header if present
971  if (isset($_SERVER['HTTP_RANGE'])) {
972 
973  // we only support standard "bytes" range specifications for now
974  if (ereg("bytes[[:space:]]*=[[:space:]]*(.+)", $_SERVER['HTTP_RANGE'], $matches)) {
975  $options["ranges"] = array();
976 
977  // ranges are comma separated
978  foreach (explode(",", $matches[1]) as $range) {
979  // ranges are either from-to pairs or just end positions
980  list($start, $end) = explode("-", $range);
981  $options["ranges"][] = ($start==="")
982  ? array("last"=>$end)
983  : array("start"=>$start, "end"=>$end);
984  }
985  }
986  }
987  }
988 
1002  function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false)
1003  {
1004  if ($mimetype === false) {
1005  if (!isset($this->multipart_separator)) {
1006  // initial
1007 
1008  // a little naive, this sequence *might* be part of the content
1009  // but it's really not likely and rather expensive to check
1010  $this->multipart_separator = "SEPARATOR_".md5(microtime());
1011 
1012  // generate HTTP header
1013  header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator);
1014  } else {
1015  // final
1016 
1017  // generate closing multipart sequence
1018  echo "\n--{$this->multipart_separator}--";
1019  }
1020  } else {
1021  // generate separator and header for next part
1022  echo "\n--{$this->multipart_separator}\n";
1023  echo "Content-type: $mimetype\n";
1024  echo "Content-range: $from-$to/". ($total === false ? "*" : $total);
1025  echo "\n\n";
1026  }
1027  }
1028 
1029 
1030 
1031  // }}}
1032 
1033  // {{{ http_HEAD()
1034 
1041  function http_HEAD()
1042  {
1043  $status = false;
1044  $options = Array();
1045  $options["path"] = $this->path;
1046 
1047  if (method_exists($this, "HEAD")) {
1048  $status = $this->head($options);
1049  } else if (method_exists($this, "GET")) {
1050  ob_start();
1051  $status = $this->GET($options);
1052  ob_end_clean();
1053  }
1054 
1055  if($status===true) $status = "200 OK";
1056  if($status===false) $status = "404 Not found";
1057 
1058  $this->http_status($status);
1059  }
1060 
1061  // }}}
1062 
1063  // {{{ http_PUT()
1064 
1071  function http_PUT()
1072  {
1073  if ($this->_check_lock_status($this->path)) {
1074  $options = Array();
1075  $options["path"] = $this->path;
1076  $options["content_length"] = $_SERVER["CONTENT_LENGTH"];
1077 
1078  // get the Content-type
1079  if (isset($_SERVER["CONTENT_TYPE"])) {
1080  // for now we do not support any sort of multipart requests
1081  if (!strncmp($_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
1082  $this->http_status("501 not implemented");
1083  echo "The service does not support mulipart PUT requests";
1084  return;
1085  }
1086  $options["content_type"] = $_SERVER["CONTENT_TYPE"];
1087  } else {
1088  // default content type if none given
1089  $options["content_type"] = "application/octet-stream";
1090  }
1091 
1092  /* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
1093  ignore any Content-* (e.g. Content-Range) headers that it
1094  does not understand or implement and MUST return a 501
1095  (Not Implemented) response in such cases."
1096  */
1097  foreach ($_SERVER as $key => $val) {
1098  if (strncmp($key, "HTTP_CONTENT", 11)) continue;
1099  switch ($key) {
1100  case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
1101  // TODO support this if ext/zlib filters are available
1102  $this->http_status("501 not implemented");
1103  echo "The service does not support '$val' content encoding";
1104  return;
1105 
1106  case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
1107  // we assume it is not critical if this one is ignored
1108  // in the actual PUT implementation ...
1109  $options["content_language"] = $value;
1110  break;
1111 
1112  case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
1113  /* The meaning of the Content-Location header in PUT
1114  or POST requests is undefined; servers are free
1115  to ignore it in those cases. */
1116  break;
1117 
1118  case 'HTTP_CONTENT_RANGE': // RFC 2616 14.16
1119  // single byte range requests are supported
1120  // the header format is also specified in RFC 2616 14.16
1121  // TODO we have to ensure that implementations support this or send 501 instead
1122  if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $value, $matches)) {
1123  $this->http_status("400 bad request");
1124  echo "The service does only support single byte ranges";
1125  return;
1126  }
1127 
1128  $range = array("start"=>$matches[1], "end"=>$matches[2]);
1129  if (is_numeric($matches[3])) {
1130  $range["total_length"] = $matches[3];
1131  }
1132  $option["ranges"][] = $range;
1133 
1134  // TODO make sure the implementation supports partial PUT
1135  // this has to be done in advance to avoid data being overwritten
1136  // on implementations that do not support this ...
1137  break;
1138 
1139  case 'HTTP_CONTENT_MD5': // RFC 2616 14.15
1140  // TODO: maybe we can just pretend here?
1141  $this->http_status("501 not implemented");
1142  echo "The service does not support content MD5 checksum verification";
1143  return;
1144 
1145  default:
1146  // any other unknown Content-* headers
1147  $this->http_status("501 not implemented");
1148  echo "The service does not support '$key'";
1149  return;
1150  }
1151  }
1152 
1153  $options["stream"] = fopen("php://input", "r");
1154 
1155  $stat = $this->PUT($options);
1156 
1157  if ($stat == false) {
1158  $stat = "403 Forbidden";
1159  } else if (is_resource($stat) && get_resource_type($stat) == "stream") {
1160  $stream = $stat;
1161 
1162  $stat = $options["new"] ? "201 Created" : "204 No Content";
1163 
1164  if (!empty($options["ranges"])) {
1165  // TODO multipart support is missing (see also above)
1166  if (0 == fseek($stream, $range[0]["start"], SEEK_SET)) {
1167  $length = $range[0]["end"]-$range[0]["start"]+1;
1168  if (!fwrite($stream, fread($options["stream"], $length))) {
1169  $stat = "403 Forbidden";
1170  }
1171  } else {
1172  $stat = "403 Forbidden";
1173  }
1174  } else {
1175  while (!feof($options["stream"])) {
1176  // BEGIN WebDAV W. Randelshofer explicitly compare with false.
1177  if (false === ($written = fwrite($stream, fread($options["stream"], 4096)))) {
1178  // END WebDAV W. Randelshofer explicitly compare with false.
1179  $stat = "403 Forbidden";
1180  break;
1181  }
1182  $count += $written;
1183  }
1184  }
1185 
1186  fclose($stream);
1187  //$this->writelog('PUT wrote '.$written.' bytes');
1188  // BEGIN WebDAV W. Randelshofer finish the put-operation
1189  $this->PUTfinished($options);
1190  // END WebDAV W. Randelshofer finish the put-operation
1191  }
1192 
1193  $this->http_status($stat);
1194  } else {
1195  $this->http_status("423 Locked");
1196  }
1197  }
1198 
1199  // }}}
1200 
1201 
1202  // {{{ http_DELETE()
1203 
1210  function http_DELETE()
1211  {
1212  // check RFC 2518 Section 9.2, last paragraph
1213  if (isset($_SERVER["HTTP_DEPTH"])) {
1214  if ($_SERVER["HTTP_DEPTH"] != "infinity") {
1215  $this->http_status("400 Bad Request");
1216  return;
1217  }
1218  }
1219 
1220  // check lock status
1221  if ($this->_check_lock_status($this->path)) {
1222  // ok, proceed
1223  $options = Array();
1224  $options["path"] = $this->path;
1225 
1226  $stat = $this->delete($options);
1227 
1228  $this->http_status($stat);
1229  } else {
1230  // sorry, its locked
1231  $this->http_status("423 Locked");
1232  }
1233  }
1234 
1235  // }}}
1236 
1237  // {{{ http_COPY()
1238 
1245  function http_COPY()
1246  {
1247  // no need to check source lock status here
1248  // destination lock status is always checked by the helper method
1249  $this->_copymove("copy");
1250  }
1251 
1252  // }}}
1253 
1254  // {{{ http_MOVE()
1255 
1262  function http_MOVE()
1263  {
1264  //$this->writelog('MOVE()');
1265  if ($this->_check_lock_status($this->path)) {
1266  // destination lock status is always checked by the helper method
1267  $this->_copymove("move");
1268  } else {
1269  //$this->writelog('MOVE():423 Locked');
1270  $this->http_status("423 Locked");
1271  }
1272  }
1273 
1274  // }}}
1275 
1276 
1277  // {{{ http_LOCK()
1278 
1285  function http_LOCK()
1286  {
1287  $options = Array();
1288  $options["path"] = $this->path;
1289 
1290  if (isset($_SERVER['HTTP_DEPTH'])) {
1291  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1292  } else {
1293  $options["depth"] = "infinity";
1294  }
1295 
1296  if (isset($_SERVER["HTTP_TIMEOUT"])) {
1297  $options["timeout"] = explode(",", $_SERVER["HTTP_TIMEOUT"]);
1298  }
1299 
1300  if(empty($_SERVER['CONTENT_LENGTH']) && !empty($_SERVER['HTTP_IF'])) {
1301  // check if locking is possible
1302  if(!$this->_check_lock_status($this->path)) {
1303  $this->http_status("423 Locked");
1304  return;
1305  }
1306 
1307  // refresh lock
1308  $options["update"] = substr($_SERVER['HTTP_IF'], 2, -2);
1309  $stat = $this->lock($options);
1310  } else {
1311  // extract lock request information from request XML payload
1312  $lockinfo = new _parse_lockinfo("php://input");
1313  if (!$lockinfo->success) {
1314  $this->http_status("400 bad request");
1315  }
1316 
1317  // check if locking is possible
1318  if(!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) {
1319  $this->http_status("423 Locked");
1320  return;
1321  }
1322 
1323  // new lock
1324  $options["scope"] = $lockinfo->lockscope;
1325  $options["type"] = $lockinfo->locktype;
1326  $options["owner"] = $lockinfo->owner;
1327 
1328  $options["locktoken"] = $this->_new_locktoken();
1329 
1330  $stat = $this->lock($options);
1331  }
1332 
1333  if(is_bool($stat)) {
1334  $http_stat = $stat ? "200 OK" : "423 Locked";
1335  } else {
1336  $http_stat = $stat;
1337  }
1338 
1339  $this->http_status($http_stat);
1340 
1341  if ($http_stat{0} == 2) { // 2xx states are ok
1342  if($options["timeout"]) {
1343  // more than a million is considered an absolute timestamp
1344  // less is more likely a relative value
1345  if($options["timeout"]>1000000) {
1346  $timeout = "Second-".($options['timeout']-time());
1347  } else {
1348  $timeout = "Second-$options[timeout]";
1349  }
1350  } else {
1351  $timeout = "Infinite";
1352  }
1353  /*
1354  $this->writelog(
1355  'Content-Type: text/xml; charset="utf-8"'
1356  ."Lock-Token: <$options[locktoken]>"
1357  . "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
1358  . "<D:prop xmlns:D=\"DAV:\">\n"
1359  . " <D:lockdiscovery>\n"
1360  . " <D:activelock>\n"
1361  . " <D:lockscope><D:$options[scope]/></D:lockscope>\n"
1362  . " <D:locktype><D:$options[type]/></D:locktype>\n"
1363  . " <D:depth>$options[depth]</D:depth>\n"
1364  . " <D:owner>$options[owner]</D:owner>\n"
1365  . " <D:timeout>$timeout</D:timeout>\n"
1366  . " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n"
1367  . " </D:activelock>\n"
1368  . " </D:lockdiscovery>\n"
1369  . "</D:prop>\n\n"
1370  );*/
1371  header('Content-Type: text/xml; charset="utf-8"');
1372  header("Lock-Token: <$options[locktoken]>");
1373  echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1374  echo "<D:prop xmlns:D=\"DAV:\">\n";
1375  echo " <D:lockdiscovery>\n";
1376  echo " <D:activelock>\n";
1377  echo " <D:lockscope><D:$options[scope]/></D:lockscope>\n";
1378  echo " <D:locktype><D:$options[type]/></D:locktype>\n";
1379  echo " <D:depth>$options[depth]</D:depth>\n";
1380  echo " <D:owner>$options[owner]</D:owner>\n";
1381  echo " <D:timeout>$timeout</D:timeout>\n";
1382  echo " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n";
1383  echo " </D:activelock>\n";
1384  echo " </D:lockdiscovery>\n";
1385  echo "</D:prop>\n\n";
1386  }
1387  }
1388 
1389 
1390  // }}}
1391 
1392  // {{{ http_UNLOCK()
1393 
1400  function http_UNLOCK()
1401  {
1402  $options = Array();
1403  $options["path"] = $this->path;
1404 
1405  if (isset($_SERVER['HTTP_DEPTH'])) {
1406  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1407  } else {
1408  $options["depth"] = "infinity";
1409  }
1410 
1411  // strip surrounding <>
1412  $options["token"] = substr(trim($_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
1413 //$this->writelog('http_UNLOCK HTTP_LOCK_TOKEN='.$_SERVER["HTTP_LOCK_TOKEN"]);
1414  // call user method
1415  $stat = $this->unlock($options);
1416 
1417  $this->http_status($stat);
1418  }
1419 
1420  // }}}
1421 
1422  // }}}
1423 
1424  // {{{ _copymove()
1425 
1426  function _copymove($what)
1427  {
1428  //$this->writelog('_copymove('.$what.')');
1429  $options = Array();
1430  $options["path"] = $this->path;
1431 
1432  if (isset($_SERVER["HTTP_DEPTH"])) {
1433  $options["depth"] = $_SERVER["HTTP_DEPTH"];
1434  } else {
1435  $options["depth"] = "infinity";
1436  }
1437 //$this->writelog('_copymove dest='.$_SERVER["HTTP_DESTINATION"]);
1438  extract(parse_url($_SERVER["HTTP_DESTINATION"]));
1439  // BEGIN WebDAV: decode path (bereits in PEAR CVS gefixt)
1440  // We must decode the target path too.
1441  $path = $this->_urldecode($path);
1442  // END Patch WebDAV: decode path
1443  $http_host = $host;
1444  if (isset($port) && $port != 80)
1445  $http_host.= ":$port";
1446 
1447  list($http_header_host,$http_header_port) = explode(":",$_SERVER["HTTP_HOST"]);
1448  if (isset($http_header_port) && $http_header_port != 80) {
1449  $http_header_host .= ":".$http_header_port;
1450  }
1451 
1452  if ($http_host == $http_header_host &&
1453  !strncmp($_SERVER["SCRIPT_NAME"], $path,
1454  strlen($_SERVER["SCRIPT_NAME"]))) {
1455  $options["dest"] = substr($path, strlen($_SERVER["SCRIPT_NAME"]));
1456  //$this->writelog('_copymove() dest='.$options['dest']);
1457  if (!$this->_check_lock_status($options["dest"])) {
1458  //$this->writelog('_copymove():423 Locked');
1459  $this->http_status("423 Locked");
1460  return;
1461  }
1462  //$this->writelog('_copymove() ...');
1463 
1464  } else {
1465  $options["dest_url"] = $_SERVER["HTTP_DESTINATION"];
1466  }
1467 
1468  // see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3
1469  if (isset($_SERVER["HTTP_OVERWRITE"])) {
1470  $options["overwrite"] = $_SERVER["HTTP_OVERWRITE"] == "T";
1471  } else {
1472  $options["overwrite"] = true;
1473  }
1474 
1475  $stat = $this->$what($options);
1476  $this->http_status($stat);
1477  }
1478 
1479  // }}}
1480 
1481  // {{{ _allow()
1482 
1489  function _allow()
1490  {
1491  // OPTIONS is always there
1492  $allow = array("OPTIONS" =>"OPTIONS");
1493 
1494  // all other METHODS need both a http_method() wrapper
1495  // and a method() implementation
1496  // the base class supplies wrappers only
1497  foreach(get_class_methods($this) as $method) {
1498  if (!strncmp("http_", $method, 5)) {
1499  $method = strtoupper(substr($method, 5));
1500  if (method_exists($this, $method)) {
1501  $allow[$method] = $method;
1502  }
1503  }
1504  }
1505 
1506  // we can emulate a missing HEAD implemetation using GET
1507  if (isset($allow["GET"]))
1508  $allow["HEAD"] = "HEAD";
1509 
1510  // no LOCK without checklok()
1511  if (!method_exists($this, "checklock")) {
1512  unset($allow["LOCK"]);
1513  unset($allow["UNLOCK"]);
1514  }
1515 
1516  return $allow;
1517  }
1518 
1519  // }}}
1520 
1529  function mkprop()
1530  {
1531  $args = func_get_args();
1532  if (count($args) == 3) {
1533  return array("ns" => $args[0],
1534  "name" => $args[1],
1535  "val" => $args[2]);
1536  } else {
1537  return array("ns" => "DAV:",
1538  "name" => $args[0],
1539  "val" => $args[1]);
1540  }
1541  }
1542 
1543  // {{{ _check_auth
1544 
1551  function _check_auth()
1552  {
1553  if (method_exists($this, "checkAuth")) {
1554  // PEAR style method name
1555  return $this->checkAuth(@$_SERVER["AUTH_TYPE"],
1556  @$_SERVER["PHP_AUTH_USER"],
1557  @$_SERVER["PHP_AUTH_PW"]);
1558  } else if (method_exists($this, "check_auth")) {
1559  // old (pre 1.0) method name
1560  return $this->check_auth(@$_SERVER["AUTH_TYPE"],
1561  @$_SERVER["PHP_AUTH_USER"],
1562  @$_SERVER["PHP_AUTH_PW"]);
1563  } else {
1564  // no method found -> no authentication required
1565  return true;
1566  }
1567  }
1568 
1569  // }}}
1570 
1571  // {{{ UUID stuff
1572 
1579  function _new_uuid()
1580  {
1581  // use uuid extension from PECL if available
1582  if (function_exists("uuid_create")) {
1583  return uuid_create();
1584  }
1585 
1586  // fallback
1587  $uuid = md5(microtime().getmypid()); // this should be random enough for now
1588 
1589  // set variant and version fields for 'true' random uuid
1590  $uuid{12} = "4";
1591  $n = 8 + (ord($uuid{16}) & 3);
1592  $hex = "0123456789abcdef";
1593  $uuid{16} = $hex{$n};
1594 
1595  // return formated uuid
1596  return substr($uuid, 0, 8)."-"
1597  . substr($uuid, 8, 4)."-"
1598  . substr($uuid, 12, 4)."-"
1599  . substr($uuid, 16, 4)."-"
1600  . substr($uuid, 20);
1601  }
1602 
1609  function _new_locktoken()
1610  {
1611  return "opaquelocktoken:".$this->_new_uuid();
1612  }
1613 
1614  // }}}
1615 
1616  // {{{ WebDAV If: header parsing
1617 
1625  function _if_header_lexer($string, &$pos)
1626  {
1627  // skip whitespace
1628  while (ctype_space($string{$pos})) {
1629  ++$pos;
1630  }
1631 
1632  // already at end of string?
1633  if (strlen($string) <= $pos) {
1634  return false;
1635  }
1636 
1637  // get next character
1638  $c = $string{$pos++};
1639 
1640  // now it depends on what we found
1641  switch ($c) {
1642  case "<":
1643  // URIs are enclosed in <...>
1644  $pos2 = strpos($string, ">", $pos);
1645  $uri = substr($string, $pos, $pos2 - $pos);
1646  $pos = $pos2 + 1;
1647  return array("URI", $uri);
1648 
1649  case "[":
1650  //Etags are enclosed in [...]
1651  if ($string{$pos} == "W") {
1652  $type = "ETAG_WEAK";
1653  $pos += 2;
1654  } else {
1655  $type = "ETAG_STRONG";
1656  }
1657  $pos2 = strpos($string, "]", $pos);
1658  $etag = substr($string, $pos + 1, $pos2 - $pos - 2);
1659  $pos = $pos2 + 1;
1660  return array($type, $etag);
1661 
1662  case "N":
1663  // "N" indicates negation
1664  $pos += 2;
1665  return array("NOT", "Not");
1666 
1667  default:
1668  // anything else is passed verbatim char by char
1669  return array("CHAR", $c);
1670  }
1671  }
1672 
1679  function _if_header_parser($str)
1680  {
1681  $pos = 0;
1682  $len = strlen($str);
1683 
1684  $uris = array();
1685 
1686  // parser loop
1687  while ($pos < $len) {
1688  // get next token
1689  $token = $this->_if_header_lexer($str, $pos);
1690 
1691  // check for URI
1692  if ($token[0] == "URI") {
1693  $uri = $token[1]; // remember URI
1694  $token = $this->_if_header_lexer($str, $pos); // get next token
1695  } else {
1696  $uri = "";
1697  }
1698 
1699  // sanity check
1700  if ($token[0] != "CHAR" || $token[1] != "(") {
1701  return false;
1702  }
1703 
1704  $list = array();
1705  $level = 1;
1706  $not = "";
1707  while ($level) {
1708  $token = $this->_if_header_lexer($str, $pos);
1709  if ($token[0] == "NOT") {
1710  $not = "!";
1711  continue;
1712  }
1713  switch ($token[0]) {
1714  case "CHAR":
1715  switch ($token[1]) {
1716  case "(":
1717  $level++;
1718  break;
1719  case ")":
1720  $level--;
1721  break;
1722  default:
1723  return false;
1724  }
1725  break;
1726 
1727  case "URI":
1728  $list[] = $not."<$token[1]>";
1729  break;
1730 
1731  case "ETAG_WEAK":
1732  $list[] = $not."[W/'$token[1]']>";
1733  break;
1734 
1735  case "ETAG_STRONG":
1736  $list[] = $not."['$token[1]']>";
1737  break;
1738 
1739  default:
1740  return false;
1741  }
1742  $not = "";
1743  }
1744 
1745  if (@is_array($uris[$uri])) {
1746  $uris[$uri] = array_merge($uris[$uri],$list);
1747  } else {
1748  $uris[$uri] = $list;
1749  }
1750  }
1751 
1752  return $uris;
1753  }
1754 
1765  {
1766  if (isset($_SERVER["HTTP_IF"])) {
1767  $this->_if_header_uris =
1768  $this->_if_header_parser($_SERVER["HTTP_IF"]);
1769 
1770  foreach($this->_if_header_uris as $uri => $conditions) {
1771  if ($uri == "") {
1772  $uri = $this->uri;
1773  }
1774  // all must match
1775  $state = true;
1776  foreach($conditions as $condition) {
1777  // lock tokens may be free form (RFC2518 6.3)
1778  // but if opaquelocktokens are used (RFC2518 6.4)
1779  // we have to check the format (litmus tests this)
1780  if (!strncmp($condition, "<opaquelocktoken:", strlen("<opaquelocktoken"))) {
1781  if (!ereg("^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$", $condition)) {
1782  return false;
1783  }
1784  }
1785  if (!$this->_check_uri_condition($uri, $condition)) {
1786  $state = false;
1787  break;
1788  }
1789  }
1790 
1791  // any match is ok
1792  if ($state == true) {
1793  return true;
1794  }
1795  }
1796  return false;
1797  }
1798  return true;
1799  }
1800 
1811  function _check_uri_condition($uri, $condition)
1812  {
1813  // not really implemented here,
1814  // implementations must override
1815  return true;
1816  }
1817 
1818 
1825  function _check_lock_status($path, $exclusive_only = false)
1826  {
1827  // FIXME depth -> ignored for now
1828  if (method_exists($this, "checkLock")) {
1829  // is locked?
1830  $lock = $this->checkLock($path);
1831 
1832  // ... and lock is not owned?
1833  if (is_array($lock) && count($lock)) {
1834  // FIXME doesn't check uri restrictions yet
1835  if (!strstr($_SERVER["HTTP_IF"], $lock["token"])) {
1836  if (!$exclusive_only || ($lock["scope"] !== "shared"))
1837  return false;
1838  }
1839  }
1840  }
1841  return true;
1842  }
1843 
1844 
1845  // }}}
1846 
1847 
1855  {
1856  // no lock support without checklock() method
1857  if (!method_exists($this, "checklock")) {
1858  return "";
1859  }
1860 
1861  // collect response here
1862  $activelocks = "";
1863 
1864  // get checklock() reply
1865  $lock = $this->checklock($path);
1866 
1867  // generate <activelock> block for returned data
1868  if (is_array($lock) && count($lock)) {
1869  // check for 'timeout' or 'expires'
1870  if (!empty($lock["expires"])) {
1871  $timeout = "Second-".($lock["expires"] - time());
1872  } else if (!empty($lock["timeout"])) {
1873  $timeout = "Second-$lock[timeout]";
1874  } else {
1875  $timeout = "Infinite";
1876  }
1877 
1878  // genreate response block
1879  $activelocks.= "
1880  <D:activelock>
1881  <D:lockscope><D:$lock[scope]/></D:lockscope>
1882  <D:locktype><D:$lock[type]/></D:locktype>
1883  <D:depth>$lock[depth]</D:depth>
1884  <D:owner>$lock[owner]</D:owner>
1885  <D:timeout>$timeout</D:timeout>
1886  <D:locktoken><D:href>$lock[token]</D:href></D:locktoken>
1887  </D:activelock>
1888  ";
1889  }
1890  //$this->writelog('lockdiscovery('.$path.'):'.$activeclocks);
1891 
1892  // return generated response
1893  return $activelocks;
1894  }
1895 
1902  function http_status($status)
1903  {
1904  // simplified success case
1905  if($status === true) {
1906  $status = "200 OK";
1907  }
1908  //$this->writelog('http_status('.$status.')');
1909 
1910  // remember status
1911  $this->_http_status = $status;
1912 
1913  // generate HTTP status response
1914  header("HTTP/1.1 $status");
1915  header("X-WebDAV-Status: $status", true);
1916  }
1917 
1927  function _urlencode($url)
1928  {
1929  return strtr($url, array(" "=>"%20",
1930  "&"=>"%26",
1931  "<"=>"%3C",
1932  ">"=>"%3E",
1933  ));
1934  }
1935 
1944  function _urldecode($path)
1945  {
1946  // BEGIN WebDAV
1947  // urldecode wrongly replaces '+' characters by ' ' characters.
1948  // We replace '+' into '%2b' before passing the path through urldecode.
1949  //return urldecode($path);
1950  $result =& urldecode(str_replace('+','%2b',$path));
1951  //$this->writelog('_urldecode('.$path.'):'.$result);
1952  return $result;
1953  // END PATCH WebDAV
1954  }
1955 
1962  function _prop_encode($text)
1963  {
1964  switch (strtolower($this->_prop_encoding)) {
1965  case "utf-8":
1966  return $text;
1967  case "iso-8859-1":
1968  case "iso-8859-15":
1969  case "latin-1":
1970  default:
1971  return utf8_encode($text);
1972  }
1973  }
1974 
1981  function _slashify($path) {
1982  if ($path[strlen($path)-1] != '/') {
1983  $path = $path."/";
1984  }
1985  return $path;
1986  }
1987 // BEGIN WebDAV
1994  private function writelog($message)
1995  {
1996  global $log, $ilias;
1997  $log->write(
1998  $ilias->account->getLogin()
1999  .' DAV Server.'.str_replace("\n",";",$message)
2000  );
2001  /*
2002  if ($this->logFile)
2003  {
2004  $fh = fopen($this->logFile, 'a');
2005  fwrite($fh, date('Y-m-d h:i:s '));
2006  fwrite($fh, str_replace("\n",";",$message));
2007  fwrite($fh, "\n\n");
2008  fclose($fh);
2009  }*/
2010  }
2011 // END PATCH WebDAV
2012 }
2013 
2014  /*
2015  * Local variables:
2016  * tab-width: 4
2017  * c-basic-offset: 4
2018  * End:
2019  */
2020 ?>