HTTP_WebDAV_Server
[ class tree: HTTP_WebDAV_Server ] [ index: HTTP_WebDAV_Server ] [ all elements ]

Source for file Filesystem.php

Documentation is available at Filesystem.php

  1. <?php // $Id$
  2. /*
  3.    +----------------------------------------------------------------------+
  4.    | Copyright (c) 2002-2007 Christian Stocker, Hartmut Holzgraefe        |
  5.    | All rights reserved                                                  |
  6.    |                                                                      |
  7.    | Redistribution and use in source and binary forms, with or without   |
  8.    | modification, are permitted provided that the following conditions   |
  9.    | are met:                                                             |
  10.    |                                                                      |
  11.    | 1. Redistributions of source code must retain the above copyright    |
  12.    |    notice, this list of conditions and the following disclaimer.     |
  13.    | 2. Redistributions in binary form must reproduce the above copyright |
  14.    |    notice, this list of conditions and the following disclaimer in   |
  15.    |    the documentation and/or other materials provided with the        |
  16.    |    distribution.                                                     |
  17.    | 3. The names of the authors may not be used to endorse or promote    |
  18.    |    products derived from this software without specific prior        |
  19.    |    written permission.                                               |
  20.    |                                                                      |
  21.    | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  |
  22.    | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT    |
  23.    | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS    |
  24.    | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE       |
  25.    | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,  |
  26.    | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
  27.    | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;     |
  28.    | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER     |
  29.    | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT   |
  30.    | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN    |
  31.    | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE      |
  32.    | POSSIBILITY OF SUCH DAMAGE.                                          |
  33.    +----------------------------------------------------------------------+
  34. */
  35.  
  36. require_once "HTTP/WebDAV/Server.php";
  37. require_once "System.php";
  38.     
  39. /**
  40.  * Filesystem access using WebDAV
  41.  *
  42.  * @access  public
  43.  * @author  Hartmut Holzgraefe <hartmut@php.net>
  44.  * @version @package-version@
  45.  */
  46. {
  47.     /**
  48.      * Root directory for WebDAV access
  49.      *
  50.      * Defaults to webserver document root (set by ServeRequest)
  51.      *
  52.      * @access private
  53.      * @var    string 
  54.      */
  55.     var $base "";
  56.  
  57.     /** 
  58.      * MySQL Host where property and locking information is stored
  59.      *
  60.      * @access private
  61.      * @var    string 
  62.      */
  63.     var $db_host "localhost";
  64.  
  65.     /**
  66.      * MySQL database for property/locking information storage
  67.      *
  68.      * @access private
  69.      * @var    string 
  70.      */
  71.     var $db_name "webdav";
  72.  
  73.     /**
  74.      * MySQL table name prefix
  75.      *
  76.      * @access private
  77.      * @var    string 
  78.      */
  79.     var $db_prefix "";
  80.  
  81.     /**
  82.      * MySQL user for property/locking db access
  83.      *
  84.      * @access private
  85.      * @var    string 
  86.      */
  87.     var $db_user "root";
  88.  
  89.     /**
  90.      * MySQL password for property/locking db access
  91.      *
  92.      * @access private
  93.      * @var    string 
  94.      */
  95.     var $db_passwd "";
  96.  
  97.     /**
  98.      * Serve a webdav request
  99.      *
  100.      * @access public
  101.      * @param  string 
  102.      */
  103.     function ServeRequest($base = false
  104.     {
  105.         // special treatment for litmus compliance test
  106.         // reply on its identifier header
  107.         // not needed for the test itself but eases debugging
  108.         if (isset($this->_SERVER['HTTP_X_LITMUS'])) {
  109.             error_log("Litmus test ".$this->_SERVER['HTTP_X_LITMUS']);
  110.             header("X-Litmus-reply: ".$this->_SERVER['HTTP_X_LITMUS']);
  111.         }
  112.  
  113.         // set root directory, defaults to webserver document root if not set
  114.         if ($base{
  115.             $this->base realpath($base)// TODO throw if not a directory
  116.         else if (!$this->base{
  117.             $this->base $this->_SERVER['DOCUMENT_ROOT'];
  118.         }
  119.                 
  120.         // establish connection to property/locking db
  121.         mysql_connect($this->db_host$this->db_user$this->db_passwdor die(mysql_error());
  122.         mysql_select_db($this->db_nameor die(mysql_error());
  123.         // TODO throw on connection problems
  124.  
  125.         // let the base class do all the work
  126.         parent::ServeRequest();
  127.     }
  128.  
  129.     /**
  130.      * No authentication is needed here
  131.      *
  132.      * @access private
  133.      * @param  string  HTTP Authentication type (Basic, Digest, ...)
  134.      * @param  string  Username
  135.      * @param  string  Password
  136.      * @return bool    true on successful authentication
  137.      */
  138.     function check_auth($type$user$pass
  139.     {
  140.         return true;
  141.     }
  142.  
  143.  
  144.     /**
  145.      * PROPFIND method handler
  146.      *
  147.      * @param  array  general parameter passing array
  148.      * @param  array  return array for file properties
  149.      * @return bool   true on success
  150.      */
  151.     function PROPFIND(&$options&$files
  152.     {
  153.         // get absolute fs path to requested resource
  154.         $fspath $this->base $options["path"];
  155.             
  156.         // sanity check
  157.         if (!file_exists($fspath)) {
  158.             return false;
  159.         }
  160.  
  161.         // prepare property array
  162.         $files["files"= array();
  163.  
  164.         // store information for the requested path itself
  165.         $files["files"][$this->fileinfo($options["path"]);
  166.  
  167.         // information for contained resources requested?
  168.         if (!empty($options["depth"]&& is_dir($fspath&& is_readable($fspath)) {                
  169.             // make sure path ends with '/'
  170.             $options["path"$this->_slashify($options["path"]);
  171.  
  172.             // try to open directory
  173.             $handle opendir($fspath);
  174.                 
  175.             if ($handle{
  176.                 // ok, now get all its contents
  177.                 while ($filename readdir($handle)) {
  178.                     if ($filename != "." && $filename != ".."{
  179.                         $files["files"][$this->fileinfo($options["path"].$filename);
  180.                     }
  181.                 }
  182.                 // TODO recursion needed if "Depth: infinite"
  183.             }
  184.         }
  185.  
  186.         // ok, all done
  187.         return true;
  188.     
  189.         
  190.     /**
  191.      * Get properties for a single file/resource
  192.      *
  193.      * @param  string  resource path
  194.      * @return array   resource properties
  195.      */
  196.     function fileinfo($path
  197.     {
  198.         // map URI path to filesystem path
  199.         $fspath $this->base $path;
  200.  
  201.         // create result array
  202.         $info = array();
  203.         // TODO remove slash append code when base clase is able to do it itself
  204.         $info["path"]  is_dir($fspath$this->_slashify($path$path
  205.         $info["props"= array();
  206.             
  207.         // no special beautified displayname here ...
  208.         $info["props"][$this->mkprop("displayname"strtoupper($path));
  209.             
  210.         // creation and modification time
  211.         $info["props"][$this->mkprop("creationdate",    filectime($fspath));
  212.         $info["props"][$this->mkprop("getlastmodified"filemtime($fspath));
  213.  
  214.         // Microsoft extensions: last access time and 'hidden' status
  215.         $info["props"][$this->mkprop("lastaccessed",    fileatime($fspath));
  216.         $info["props"][$this->mkprop("ishidden"('.' === substr(basename($fspath)01)));
  217.  
  218.         // type and size (caller already made sure that path exists)
  219.         if (is_dir($fspath)) {
  220.             // directory (WebDAV collection)
  221.             $info["props"][$this->mkprop("resourcetype""collection");
  222.             $info["props"][$this->mkprop("getcontenttype""httpd/unix-directory");             
  223.         else {
  224.             // plain file (WebDAV resource)
  225.             $info["props"][$this->mkprop("resourcetype""");
  226.             if (is_readable($fspath)) {
  227.                 $info["props"][$this->mkprop("getcontenttype"$this->_mimetype($fspath));
  228.             else {
  229.                 $info["props"][$this->mkprop("getcontenttype""application/x-non-readable");
  230.             }               
  231.             $info["props"][$this->mkprop("getcontentlength"filesize($fspath));
  232.         }
  233.  
  234.         // get additional properties from database
  235.         $query = "SELECT ns, name, value 
  236.                         FROM {$this->db_prefix}properties 
  237.                        WHERE path = '$path'";
  238.         $res = mysql_query($query);
  239.         while ($row = mysql_fetch_assoc($res)) {
  240.             $info["props"][] = $this->mkprop($row["ns"]$row["name"]$row["value"]);
  241.         }
  242.         mysql_free_result($res);
  243.  
  244.         return $info;
  245.     }
  246.  
  247.     /**
  248.      * detect if a given program is found in the search PATH
  249.      *
  250.      * helper function used by _mimetype() to detect if the 
  251.      * external 'file' utility is available
  252.      *
  253.      * @param  string  program name
  254.      * @param  string  optional search path, defaults to $PATH
  255.      * @return bool    true if executable program found in path
  256.      */
  257.     function _can_execute($name, $path = false) 
  258.     {
  259.         // path defaults to PATH from environment if not set
  260.         if ($path === false) {
  261.             $path = getenv("PATH");
  262.         }
  263.             
  264.         // check method depends on operating system
  265.         if (!strncmp(PHP_OS, "WIN", 3)) {
  266.             // on Windows an appropriate COM or EXE file needs to exist
  267.             $exts     = array(".exe", ".com");
  268.             $check_fn = "file_exists";
  269.         } else {
  270.             // anywhere else we look for an executable file of that name
  271.             $exts     = array("");
  272.             $check_fn = "is_executable";
  273.         }
  274.             
  275.         // now check the directories in the path for the program
  276.         foreach (explode(PATH_SEPARATOR, $path) as $dir) {
  277.             // skip invalid path entries
  278.             if (!file_exists($dir)) continue;
  279.             if (!is_dir($dir)) continue;
  280.  
  281.             // and now look for the file
  282.             foreach ($exts as $ext) {
  283.                 if ($check_fn("$dir/$name".$ext)) return true;
  284.             }
  285.         }
  286.  
  287.         return false;
  288.     }
  289.  
  290.         
  291.     /**
  292.      * try to detect the mime type of a file
  293.      *
  294.      * @param  string  file path
  295.      * @return string  guessed mime type
  296.      */
  297.     function _mimetype($fspath) 
  298.     {
  299.         if (is_dir($fspath)) {
  300.             // directories are easy
  301.             return "httpd/unix-directory"
  302.         } else if (function_exists("mime_content_type")) {
  303.             // use mime magic extension if available
  304.             $mime_type = mime_content_type($fspath);
  305.         } else if ($this->_can_execute("file")) {
  306.             // it looks like we have a 'file' command, 
  307.             // lets see it it does have mime support
  308.             $fp    = popen("file -i '$fspath' 2>/dev/null", "r");
  309.             $reply = fgets($fp);
  310.             pclose($fp);
  311.                 
  312.             // popen will not return an error if the binary was not found
  313.             // and find may not have mime support using "-i"
  314.             // so we test the format of the returned string 
  315.                 
  316.             // the reply begins with the requested filename
  317.             if (!strncmp($reply, "$fspath: ", strlen($fspath)+2)) {                     
  318.                 $reply = substr($reply, strlen($fspath)+2);
  319.                 // followed by the mime type (maybe including options)
  320.                 if (preg_match('|^[[:alnum:]_-]+/[[:alnum:]_-]+;?.*|', $reply, $matches)) {
  321.                     $mime_type = $matches[0];
  322.                 }
  323.             }
  324.         } 
  325.             
  326.         if (empty($mime_type)) {
  327.             // Fallback solution: try to guess the type by the file extension
  328.             // TODO: add more ...
  329.             // TODO: it has been suggested to delegate mimetype detection 
  330.             //       to apache but this has at least three issues:
  331.             //       - works only with apache
  332.             //       - needs file to be within the document tree
  333.             //       - requires apache mod_magic 
  334.             // TODO: can we use the registry for this on Windows?
  335.             //       OTOH if the server is Windos the clients are likely to 
  336.             //       be Windows, too, and tend do ignore the Content-Type
  337.             //       anyway (overriding it with information taken from
  338.             //       the registry)
  339.             // TODO: have a seperate PEAR class for mimetype detection?
  340.             switch (strtolower(strrchr(basename($fspath), "."))) {
  341.             case ".html":
  342.                 $mime_type = "text/html";
  343.                 break;
  344.             case ".gif":
  345.                 $mime_type = "image/gif";
  346.                 break;
  347.             case ".jpg":
  348.                 $mime_type = "image/jpeg";
  349.                 break;
  350.             default: 
  351.                 $mime_type = "application/octet-stream";
  352.                 break;
  353.             }
  354.         }
  355.             
  356.         return $mime_type;
  357.     }
  358.  
  359.     /**
  360.      * HEAD method handler
  361.      * 
  362.      * @param  array  parameter passing array
  363.      * @return bool   true on success
  364.      */
  365.     function HEAD(&$options) 
  366.     {
  367.         // get absolute fs path to requested resource
  368.         $fspath = $this->base $options["path"];
  369.  
  370.         // sanity check
  371.         if (!file_exists($fspath)) return false;
  372.             
  373.         // detect resource type
  374.         $options['mimetype'$this->_mimetype($fspath)
  375.                 
  376.         // detect modification time
  377.         // see rfc2518, section 13.7
  378.         // some clients seem to treat this as a reverse rule
  379.         // requiering a Last-Modified header if the getlastmodified header was set
  380.         $options['mtime'= filemtime($fspath);
  381.             
  382.         // detect resource size
  383.         $options['size'= filesize($fspath);
  384.             
  385.         return true;
  386.     }
  387.  
  388.     /**
  389.      * GET method handler
  390.      * 
  391.      * @param  array  parameter passing array
  392.      * @return bool   true on success
  393.      */
  394.     function GET(&$options) 
  395.     {
  396.         // get absolute fs path to requested resource
  397.         $fspath = $this->base $options["path"];
  398.  
  399.         // is this a collection?
  400.         if (is_dir($fspath)) {
  401.             return $this->GetDir($fspath$options);
  402.         }
  403.  
  404.         // the header output is the same as for HEAD
  405.         if (!$this->HEAD($options)) {
  406.             return false;
  407.         }
  408.  
  409.         // no need to check result here, it is handled by the base class
  410.         $options['stream'] = fopen($fspath, "r");
  411.             
  412.         return true;
  413.     }
  414.  
  415.     /**
  416.      * GET method handler for directories
  417.      *
  418.      * This is a very simple mod_index lookalike.
  419.      * See RFC 2518, Section 8.4 on GET/HEAD for collections
  420.      *
  421.      * @param  string  directory path
  422.      * @return void    function has to handle HTTP response itself
  423.      */
  424.     function GetDir($fspath, &$options) 
  425.     {
  426.         $path = $this->_slashify($options["path"]);
  427.         if ($path != $options["path"]{
  428.             header("Location: ".$this->base_uri.$path);
  429.             exit;
  430.         }
  431.  
  432.         // fixed width directory column format
  433.         $format = "%15s  %-19s  %-s\n";
  434.  
  435.         if (!is_readable($fspath)) {
  436.             return false;
  437.         }
  438.  
  439.         $handle = opendir($fspath);
  440.         if (!$handle) {
  441.             return false;
  442.         }
  443.  
  444.         echo "<html><head><title>Index of ".htmlspecialchars($options['path'])."</title></head>\n";
  445.             
  446.         echo "<h1>Index of ".htmlspecialchars($options['path'])."</h1>\n";
  447.             
  448.         echo "<pre>";
  449.         printf($format, "Size", "Last modified", "Filename");
  450.         echo "<hr>";
  451.  
  452.         while ($filename = readdir($handle)) {
  453.             if ($filename != "." && $filename != "..") {
  454.                 $fullpath = $fspath."/".$filename;
  455.                 $name     = htmlspecialchars($filename);
  456.                 printf($format, 
  457.                        number_format(filesize($fullpath)),
  458.                        strftime("%Y-%m-%d %H:%M:%S", filemtime($fullpath)), 
  459.                        '<a href="' . $name . '">' . $name . '</a>');
  460.             }
  461.         }
  462.  
  463.         echo "</pre>";
  464.  
  465.         closedir($handle);
  466.  
  467.         echo "</html>\n";
  468.  
  469.         exit;
  470.     }
  471.  
  472.     /**
  473.      * PUT method handler
  474.      * 
  475.      * @param  array  parameter passing array
  476.      * @return bool   true on success
  477.      */
  478.     function PUT(&$options) 
  479.     {
  480.         $fspath = $this->base $options["path"];
  481.  
  482.         $dir = dirname($fspath);
  483.         if (!file_exists($dir|| !is_dir($dir)) {
  484.             return "409 Conflict"// TODO right status code for both?
  485.         }
  486.  
  487.         $options["new"] = ! file_exists($fspath);
  488.  
  489.         if ($options["new"] && !is_writeable($dir)) {
  490.             return "403 Forbidden";
  491.         }
  492.         if (!$options["new"] && !is_writeable($fspath)) {
  493.             return "403 Forbidden";
  494.         }
  495.         if (!$options["new"] && is_dir($fspath)) {
  496.             return "403 Forbidden";
  497.         }
  498.  
  499.         $fp = fopen($fspath, "w");
  500.  
  501.         return $fp;
  502.     }
  503.  
  504.  
  505.     /**
  506.      * MKCOL method handler
  507.      *
  508.      * @param  array  general parameter passing array
  509.      * @return bool   true on success
  510.      */
  511.     function MKCOL($options) 
  512.     {           
  513.         $path   = $this->base .$options["path"];
  514.         $parent = dirname($path);
  515.         $name   = basename($path);
  516.  
  517.         if (!file_exists($parent)) {
  518.             return "409 Conflict";
  519.         }
  520.  
  521.         if (!is_dir($parent)) {
  522.             return "403 Forbidden";
  523.         }
  524.  
  525.         if ( file_exists($parent."/".$name) ) {
  526.             return "405 Method not allowed";
  527.         }
  528.  
  529.         if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet
  530.             return "415 Unsupported media type";
  531.         }
  532.             
  533.         $stat = mkdir($parent."/".$name, 0777);
  534.         if (!$stat) {
  535.             return "403 Forbidden";                 
  536.         }
  537.  
  538.         return ("201 Created");
  539.     }
  540.         
  541.         
  542.     /**
  543.      * DELETE method handler
  544.      *
  545.      * @param  array  general parameter passing array
  546.      * @return bool   true on success
  547.      */
  548.     function DELETE($options) 
  549.     {
  550.         $path = $this->base "/" .$options["path"];
  551.  
  552.         if (!file_exists($path)) {
  553.             return "404 Not found";
  554.         }
  555.  
  556.         if (is_dir($path)) {
  557.             $query = "DELETE FROM {$this->db_prefix}properties 
  558.                            WHERE path LIKE '".$this->_slashify($options["path"])."%'";
  559.             mysql_query($query);
  560.             System::rm(array("-rf"$path));
  561.         } else {
  562.             unlink($path);
  563.         }
  564.         $query = "DELETE FROM {$this->db_prefix}properties 
  565.                        WHERE path = '$options[path]'";
  566.         mysql_query($query);
  567.  
  568.         return "204 No Content";
  569.     }
  570.  
  571.  
  572.     /**
  573.      * MOVE method handler
  574.      *
  575.      * @param  array  general parameter passing array
  576.      * @return bool   true on success
  577.      */
  578.     function MOVE($options) 
  579.     {
  580.         return $this->COPY($optionstrue);
  581.     }
  582.  
  583.     /**
  584.      * COPY method handler
  585.      *
  586.      * @param  array  general parameter passing array
  587.      * @return bool   true on success
  588.      */
  589.     function COPY($options, $del=false) 
  590.     {
  591.         // TODO Property updates still broken (Litmus should detect this?)
  592.         if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet
  593.             return "415 Unsupported media type";
  594.         }
  595.  
  596.         // no copying to different WebDAV Servers yet
  597.         if (isset($options["dest_url"])) {
  598.             return "502 bad gateway";
  599.         }
  600.  
  601.         $source = $this->base $options["path"];
  602.         if (!file_exists($source)) {
  603.             return "404 Not found";
  604.         }
  605.  
  606.         if (is_dir($source)) { // resource is a collection
  607.             switch ($options["depth"]) {
  608.             case "infinity"// valid 
  609.                 break;
  610.             case "0"// valid for COPY only
  611.                 if ($del) { // MOVE?
  612.                     return "400 Bad request";
  613.                 }
  614.                 break;
  615.             case "1"// invalid for both COPY and MOVE
  616.             default: 
  617.                 return "400 Bad request";
  618.             }
  619.         }
  620.  
  621.         $dest         = $this->base $options["dest"];
  622.         $destdir      = dirname($dest);
  623.         
  624.         if (!file_exists($destdir|| !is_dir($destdir)) {
  625.             return "409 Conflict";
  626.         }
  627.  
  628.  
  629.         $new          = !file_exists($dest);
  630.         $existing_col = false;
  631.  
  632.         if (!$new) {
  633.             if ($del && is_dir($dest)) {
  634.                 if (!$options["overwrite"]) {
  635.                     return "412 precondition failed";
  636.                 }
  637.                 $dest .= basename($source);
  638.                 if (file_exists($dest)) {
  639.                     $options["dest"] .= basename($source);
  640.                 } else {
  641.                     $new          = true;
  642.                     $existing_col = true;
  643.                 }
  644.             }
  645.         }
  646.  
  647.         if (!$new) {
  648.             if ($options["overwrite"]) {
  649.                 $stat = $this->DELETE(array("path" => $options["dest"]));
  650.                 if (($stat{0} != "2") && (substr($stat, 0, 3) != "404")) {
  651.                     return $stat
  652.                 }
  653.             } else {
  654.                 return "412 precondition failed";
  655.             }
  656.         }
  657.  
  658.         if ($del) {
  659.             if (!rename($source, $dest)) {
  660.                 return "500 Internal server error";
  661.             }
  662.             $destpath = $this->_unslashify($options["dest"]);
  663.             if (is_dir($source)) {
  664.                 $query = "UPDATE {$this->db_prefix}properties 
  665.                                  SET path = REPLACE(path, '".$options["path"]."', '".$destpath."') 
  666.                                WHERE path LIKE '".$this->_slashify($options["path"])."%'";
  667.                 mysql_query($query);
  668.             }
  669.  
  670.             $query = "UPDATE {$this->db_prefix}properties 
  671.                              SET path = '".$destpath."'
  672.                            WHERE path = '".$options["path"]."'";
  673.             mysql_query($query);
  674.         } else {
  675.             if (is_dir($source)) {
  676.                 $files = System::find($source);
  677.                 $files = array_reverse($files);
  678.             } else {
  679.                 $files = array($source);
  680.             }
  681.  
  682.             if (!is_array($files) || empty($files)) {
  683.                 return "500 Internal server error";
  684.             }
  685.                     
  686.                 
  687.             foreach ($files as $file) {
  688.                 if (is_dir($file)) {
  689.                     $file = $this->_slashify($file);
  690.                 }
  691.  
  692.                 $destfile = str_replace($source, $dest, $file);
  693.                     
  694.                 if (is_dir($file)) {
  695.                     if (!file_exists($destfile)) {
  696.                         if (!is_writeable(dirname($destfile))) {
  697.                             return "403 Forbidden";
  698.                         }
  699.                         if (!mkdir($destfile)) {
  700.                             return "409 Conflict";
  701.                         }
  702.                     } else if (!is_dir($destfile)) {
  703.                         return "409 Conflict";
  704.                     }
  705.                 } else {
  706.                     
  707.                     if (!copy($file, $destfile)) {
  708.                         return "409 Conflict";
  709.                     }
  710.                 }
  711.             }
  712.  
  713.             $query = "INSERT INTO {$this->db_prefix}properties 
  714.                                SELECT *
  715.                                  FROM {$this->db_prefix}properties 
  716.                                 WHERE path = '".$options['path']."'";
  717.         }
  718.  
  719.         return ($new && !$existing_col) ? "201 Created" : "204 No Content";         
  720.     }
  721.  
  722.     /**
  723.      * PROPPATCH method handler
  724.      *
  725.      * @param  array  general parameter passing array
  726.      * @return bool   true on success
  727.      */
  728.     function PROPPATCH(&$options) 
  729.     {
  730.         global $prefs, $tab;
  731.  
  732.         $msg  = "";
  733.         $path = $options["path"];
  734.         $dir  = dirname($path)."/";
  735.         $base = basename($path);
  736.             
  737.         foreach ($options["props"] as $key => $prop) {
  738.             if ($prop["ns"] == "DAV:") {
  739.                 $options["props"][$key]['status'] = "403 Forbidden";
  740.             } else {
  741.                 if (isset($prop["val"])) {
  742.                     $query = "REPLACE INTO {$this->db_prefix}properties 
  743.                                            SET path = '$options[path]'
  744.                                              , name = '$prop[name]'
  745.                                              , ns= '$prop[ns]'
  746.                                              , value = '$prop[val]'";
  747.                 } else {
  748.                     $query = "DELETE FROM {$this->db_prefix}properties 
  749.                                         WHERE path = '$options[path]
  750.                                           AND name = '$prop[name]
  751.                                           AND ns = '$prop[ns]'";
  752.                 }       
  753.                 mysql_query($query);
  754.             }
  755.         }
  756.                         
  757.         return "";
  758.     }
  759.  
  760.  
  761.     /**
  762.      * LOCK method handler
  763.      *
  764.      * @param  array  general parameter passing array
  765.      * @return bool   true on success
  766.      */
  767.     function LOCK(&$options) 
  768.     {
  769.         // get absolute fs path to requested resource
  770.         $fspath = $this->base $options["path"];
  771.  
  772.         // TODO recursive locks on directories not supported yet
  773.         // makes litmus test "32. lock_collection" fail
  774.         if (is_dir($fspath&& !empty($options["depth"])) {
  775.             return "409 Conflict";
  776.         }
  777.  
  778.         $options["timeout"] = time()+300; // 5min. hardcoded
  779.         if (isset($options["update"])) { // Lock Update
  780.             $where = "WHERE path = '$options[path]' AND token = '$options[update]'";
  781.  
  782.             $query = "SELECT owner, exclusivelock FROM {$this->db_prefix}locks $where";
  783.             $res   = mysql_query($query);
  784.             $row   = mysql_fetch_assoc($res);
  785.             mysql_free_result($res);
  786.  
  787.             if (is_array($row)) {
  788.                 $query = "UPDATE {$this->db_prefix}locks 
  789.                                  SET expires = '$options[timeout]
  790.                                    , modified = ".time()."
  791.                               $where";
  792.                 mysql_query($query);
  793.  
  794.                 $options['owner'] = $row['owner'];
  795.                 $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared";
  796.                 $options['type']  = $row["exclusivelock"] ? "write"     : "read";
  797.  
  798.                 return true;
  799.             } else {
  800.                 return false;
  801.             }
  802.         }
  803.             
  804.         $query = "INSERT INTO {$this->db_prefix}locks
  805.                         SET token   = '$options[locktoken]'
  806.                           , path    = '$options[path]'
  807.                           , created = ".time()."
  808.                           , modified = ".time()."
  809.                           , owner   = '$options[owner]'
  810.                           , expires = '$options[timeout]'
  811.                           , exclusivelock  = " .($options['scope'] === "exclusive" ? "1" : "0")
  812.             ;
  813.         mysql_query($query);
  814.  
  815.         return mysql_affected_rows() ? "200 OK" : "409 Conflict";
  816.     }
  817.  
  818.     /**
  819.      * UNLOCK method handler
  820.      *
  821.      * @param  array  general parameter passing array
  822.      * @return bool   true on success
  823.      */
  824.     function UNLOCK(&$options) 
  825.     {
  826.         $query = "DELETE FROM {$this->db_prefix}locks
  827.                       WHERE path = '$options[path]'
  828.                         AND token = '$options[token]'";
  829.         mysql_query($query);
  830.  
  831.         return mysql_affected_rows() ? "204 No Content" : "409 Conflict";
  832.     }
  833.  
  834.     /**
  835.      * checkLock() helper
  836.      *
  837.      * @param  string resource path to check for locks
  838.      * @return bool   true on success
  839.      */
  840.     function checkLock($path) 
  841.     {
  842.         $result = false;
  843.             
  844.         $query = "SELECT owner, token, created, modified, expires, exclusivelock
  845.                   FROM {$this->db_prefix}locks
  846.                  WHERE path = '$path'
  847.                ";
  848.         $res = mysql_query($query);
  849.  
  850.         if ($res) {
  851.             $row = mysql_fetch_array($res);
  852.             mysql_free_result($res);
  853.  
  854.             if ($row) {
  855.                 $result = array( "type"    => "write",
  856.                                  "scope"   => $row["exclusivelock"] ? "exclusive" : "shared",
  857.                                  "depth"   => 0,
  858.                                  "owner"   => $row['owner'],
  859.                                  "token"   => $row['token'],
  860.                                  "created" => $row['created'],   
  861.                                  "modified" => $row['modified'],   
  862.                                  "expires" => $row['expires']
  863.                                  );
  864.             }
  865.         }
  866.  
  867.         return $result;
  868.     }
  869.  
  870.  
  871.     /**
  872.      * create database tables for property and lock storage
  873.      *
  874.      * @param  void
  875.      * @return bool   true on success
  876.      */
  877.     function create_database() 
  878.     {
  879.         // TODO
  880.         return false;
  881.     }
  882. }
  883.  
  884.  
  885. /*
  886.  * Local variables:
  887.  * tab-width: 4
  888.  * c-basic-offset: 4
  889.  * indent-tabs-mode:nil
  890.  * End:

Documentation generated on Mon, 11 Mar 2019 15:50:55 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.