diff options
Diffstat (limited to 'lib/HTTP/WebDAV/Server/Filesystem.php')
-rw-r--r-- | lib/HTTP/WebDAV/Server/Filesystem.php | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/lib/HTTP/WebDAV/Server/Filesystem.php b/lib/HTTP/WebDAV/Server/Filesystem.php new file mode 100644 index 00000000000..0615c600e07 --- /dev/null +++ b/lib/HTTP/WebDAV/Server/Filesystem.php @@ -0,0 +1,760 @@ +<?php +/* + +----------------------------------------------------------------------+ + | Copyright (c) 2002-2007 Christian Stocker, Hartmut Holzgraefe | + | All rights reserved | + | | + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the following conditions | + | are met: | + | | + | 1. Redistributions of source code must retain the above copyright | + | notice, this list of conditions and the following disclaimer. | + | 2. Redistributions in binary form must reproduce the above copyright | + | notice, this list of conditions and the following disclaimer in | + | the documentation and/or other materials provided with the | + | distribution. | + | 3. The names of the authors may not be used to endorse or promote | + | products derived from this software without specific prior | + | written permission. | + | | + | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | + | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | + | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | + | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | + | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, | + | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, | + | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | + | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | + | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | + | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN | + | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | + | POSSIBILITY OF SUCH DAMAGE. | + +----------------------------------------------------------------------+ + --- modified for ownCloud --- +*/ + require_once("lib/base.php"); + oc_require_once("HTTP/WebDAV/Server.php"); + oc_require_once("System.php"); + + /** + * Filesystem access using WebDAV + * + * @access public + * @author Hartmut Holzgraefe <hartmut@php.net> + * @version @package-version@ + */ + class HTTP_WebDAV_Server_Filesystem extends HTTP_WebDAV_Server + { + /** + * Root directory for WebDAV access + * + * Defaults to webserver document root (set by ServeRequest) + * + * @access private + * @var string + */ + var $base = ""; + + /** + * Serve a webdav request + * + * @access public + * @param string + */ + function ServeRequest($base = false) + { + // special treatment for litmus compliance test + // reply on its identifier header + // not needed for the test itself but eases debugging + if (isset($this->_SERVER['HTTP_X_LITMUS'])) { + error_log("Litmus test ".$this->_SERVER['HTTP_X_LITMUS']); + header("X-Litmus-reply: ".$this->_SERVER['HTTP_X_LITMUS']); + } + + // set root directory, defaults to webserver document root if not set + if ($base) { + $this->base = realpath($base); // TODO throw if not a directory + } else if (!$this->base) { + $this->base = $this->_SERVER['DOCUMENT_ROOT']; + } + + // let the base class do all the work + parent::ServeRequest(); + } + + /** + * No authentication is needed here + * + * @access private + * @param string HTTP Authentication type (Basic, Digest, ...) + * @param string Username + * @param string Password + * @return bool true on successful authentication + */ + function check_auth($type, $user, $pass) + { + return true; + } + + + /** + * PROPFIND method handler + * + * @param array general parameter passing array + * @param array return array for file properties + * @return bool true on success + */ + function PROPFIND(&$options, &$files) + { + // get absolute fs path to requested resource + $fspath = $options["path"]; + + // sanity check + if (!OC_FILESYSTEM::file_exists($fspath)) { + return false; + } + + // prepare property array + $files["files"] = array(); + // store information for the requested path itself + $files["files"][] = $this->fileinfo($options["path"]); + // information for contained resources requested? + if (!empty($options["depth"]) && OC_FILESYSTEM::is_dir($fspath) && OC_FILESYSTEM::is_readable($fspath)) { + // make sure path ends with '/' + $options["path"] = $this->_slashify($options["path"]); + + // try to open directory + $handle = @OC_FILESYSTEM::opendir($fspath); + + if ($handle) { + // ok, now get all its contents + while ($filename = readdir($handle)) { + if ($filename != "." && $filename != "..") { + $files["files"][] = $this->fileinfo($options["path"].$filename); + } + } + // TODO recursion needed if "Depth: infinite" + } + } + + // ok, all done + return true; + } + + /** + * Get properties for a single file/resource + * + * @param string resource path + * @return array resource properties + */ + function fileinfo($path) + { + global $CONFIG_DBTABLEPREFIX; + + // map URI path to filesystem path + $fspath =$path; + + // create result array + $info = array(); + // TODO remove slash append code when base clase is able to do it itself + $info["path"] = OC_FILESYSTEM::is_dir($fspath) ? $this->_slashify($path) : $path; + $info["props"] = array(); + // no special beautified displayname here ... + $info["props"][] = $this->mkprop("displayname", strtoupper($path)); + + // creation and modification time + $info["props"][] = $this->mkprop("creationdate", OC_FILESYSTEM::filectime($fspath)); + $info["props"][] = $this->mkprop("getlastmodified", OC_FILESYSTEM::filemtime($fspath)); + // Microsoft extensions: last access time and 'hidden' status + $info["props"][] = $this->mkprop("lastaccessed", OC_FILESYSTEM::fileatime($fspath)); + $info["props"][] = $this->mkprop("ishidden", ('.' === substr(basename($fspath), 0, 1))); + // type and size (caller already made sure that path exists) + if ( OC_FILESYSTEM::is_dir($fspath)) { + // directory (WebDAV collection) + $info["props"][] = $this->mkprop("resourcetype", "collection"); + $info["props"][] = $this->mkprop("getcontenttype", "httpd/unix-directory"); + } else { + // plain file (WebDAV resource) + $info["props"][] = $this->mkprop("resourcetype", ""); + if ( OC_FILESYSTEM::is_readable($fspath)) { + $info["props"][] = $this->mkprop("getcontenttype", $this->_mimetype($fspath)); + } else { + $info["props"][] = $this->mkprop("getcontenttype", "application/x-non-readable"); + } + $info["props"][] = $this->mkprop("getcontentlength", OC_FILESYSTEM::filesize($fspath)); + } + // get additional properties from database + $query = "SELECT ns, name, value FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$path'"; + $res = OC_DB::select($query); + foreach($res as $row){ + $info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]); + } + return $info; + } + + /** + * try to detect the mime type of a file + * + * @param string file path + * @return string guessed mime type + */ + function _mimetype($fspath) + { + return OC_FILESYSTEM::getMimeType($fspath); + } + + /** + * HEAD method handler + * + * @param array parameter passing array + * @return bool true on success + */ + function HEAD(&$options) + { + // get absolute fs path to requested resource + $fspath = $options["path"]; + + // sanity check + if (! OC_FILESYSTEM::file_exists($fspath)) return false; + + // detect resource type + $options['mimetype'] = $this->_mimetype($fspath); + + // detect modification time + // see rfc2518, section 13.7 + // some clients seem to treat this as a reverse rule + // requiering a Last-Modified header if the getlastmodified header was set + $options['mtime'] = OC_FILESYSTEM::filemtime($fspath); + + // detect resource size + $options['size'] = OC_FILESYSTEM::filesize($fspath); + + return true; + } + + /** + * GET method handler + * + * @param array parameter passing array + * @return bool true on success + */ + function GET(&$options) + { + // get absolute fs path to requested resource) + $fspath = $options["path"]; + // is this a collection? + if (OC_FILESYSTEM::is_dir($fspath)) { + return $this->GetDir($fspath, $options); + } + + // the header output is the same as for HEAD + if (!$this->HEAD($options)) { + return false; + } + + // no need to check result here, it is handled by the base class + $options['stream'] = OC_FILESYSTEM::fopen($fspath, "r"); + + return true; + } + + /** + * GET method handler for directories + * + * This is a very simple mod_index lookalike. + * See RFC 2518, Section 8.4 on GET/HEAD for collections + * + * @param string directory path + * @return void function has to handle HTTP response itself + */ + function GetDir($fspath, &$options) + { + $path = $this->_slashify($options["path"]); + if ($path != $options["path"]) { + header("Location: ".$this->base_uri.$path); + exit; + } + + // fixed width directory column format + $format = "%15s %-19s %-s\n"; + + if (!OC_FILESYSTEM::is_readable($fspath)) { + return false; + } + + $handle = OC_FILESYSTEM::opendir($fspath); + if (!$handle) { + return false; + } + + echo "<html><head><title>Index of ".htmlspecialchars($options['path'])."</title></head>\n"; + + echo "<h1>Index of ".htmlspecialchars($options['path'])."</h1>\n"; + + echo "<pre>"; + printf($format, "Size", "Last modified", "Filename"); + echo "<hr>"; + + while ($filename = readdir($handle)) { + if ($filename != "." && $filename != "..") { + $fullpath = $fspath."/".$filename; + $name = htmlspecialchars($filename); + printf($format, + number_format(filesize($fullpath)), + strftime("%Y-%m-%d %H:%M:%S", filemtime($fullpath)), + "<a href='$name'>$name</a>"); + } + } + + echo "</pre>"; + + closedir($handle); + + echo "</html>\n"; + + exit; + } + + /** + * PUT method handler + * + * @param array parameter passing array + * @return bool true on success + */ + function PUT(&$options) + { + $fspath = $options["path"]; + $dir = dirname($fspath); + if (!OC_FILESYSTEM::file_exists($dir) || !OC_FILESYSTEM::is_dir($dir)) { + return "409 Conflict"; // TODO right status code for both? + } + + $options["new"] = ! OC_FILESYSTEM::file_exists($fspath); + + if ($options["new"] && !OC_FILESYSTEM::is_writeable($dir)) { + return "403 Forbidden"; + } + if (!$options["new"] && !OC_FILESYSTEM::is_writeable($fspath)) { + return "403 Forbidden"; + } + if (!$options["new"] && OC_FILESYSTEM::is_dir($fspath)) { + return "403 Forbidden"; + } + $fp = OC_FILESYSTEM::fopen($fspath, "w"); + + return $fp; + } + + + /** + * MKCOL method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function MKCOL($options) + { + $path = $options["path"]; + $parent = dirname($path); + $name = basename($path); + if (!OC_FILESYSTEM::file_exists($parent)) { + return "409 Conflict"; + } + + if (!OC_FILESYSTEM::is_dir($parent)) { + return "403 Forbidden"; + } + + if ( OC_FILESYSTEM::file_exists($parent."/".$name) ) { + return "405 Method not allowed"; + } + + if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet + return "415 Unsupported media type"; + } + + $stat = OC_FILESYSTEM::mkdir($parent."/".$name, 0777); + if (!$stat) { + return "403 Forbidden"; + } + + return ("201 Created"); + } + + + /** + * DELETE method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function DELETE($options) + { + global $CONFIG_DBTABLEPREFIX; + $path =$options["path"]; + if (!OC_FILESYSTEM::file_exists($path)) { + return "404 Not found"; + } + $lock=self::checkLock($path); + if(is_array($lock)){ + $owner=$options['owner']; + $lockOwner=$lock['owner']; + if($owner==$lockOwner){ + return "423 Locked"; + } + } + if (OC_FILESYSTEM::is_dir($path)) { + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path LIKE '".$this->_slashify($options["path"])."%'"; + OC_DB::query($query); + OC_FILESYSTEM::delTree($path); + } else { + OC_FILESYSTEM::unlink($path); + } + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$options[path]'"; + OC_DB::query($query); + + return "204 No Content"; + } + + + /** + * MOVE method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function MOVE($options) + { + return $this->COPY($options, true); + } + + /** + * COPY method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function COPY($options, $del=false) + { + // TODO Property updates still broken (Litmus should detect this?) + global $CONFIG_DBTABLEPREFIX; + + if (!empty($this->_SERVER["CONTENT_LENGTH"])) { // no body parsing yet + return "415 Unsupported media type"; + } + + // no copying to different WebDAV Servers yet + if (isset($options["dest_url"])) { + return "502 bad gateway"; + } + + $source = $options["path"]; + if (!OC_FILESYSTEM::file_exists($source)) { + return "404 Not found"; + } + + if (OC_FILESYSTEM::is_dir($source)) { // resource is a collection + switch ($options["depth"]) { + case "infinity": // valid + break; + case "0": // valid for COPY only + if ($del) { // MOVE? + return "400 Bad request"; + } + break; + case "1": // invalid for both COPY and MOVE + default: + return "400 Bad request"; + } + } + + $dest = $options["dest"]; + $destdir = dirname($dest); + + if (!OC_FILESYSTEM::file_exists($destdir) || !OC_FILESYSTEM::is_dir($destdir)) { + return "409 Conflict"; + } + + + $new = !OC_FILESYSTEM::file_exists($dest); + $existing_col = false; + + if (!$new) { + if ($del && OC_FILESYSTEM::is_dir($dest)) { + if (!$options["overwrite"]) { + return "412 precondition failed"; + } + $dest .= basename($source); + if (OC_FILESYSTEM::file_exists($dest)) { + $options["dest"] .= basename($source); + } else { + $new = true; + $existing_col = true; + } + } + } + + if (!$new) { + if ($options["overwrite"]) { + $stat = $this->DELETE(array("path" => $options["dest"])); + if (($stat{0} != "2") && (substr($stat, 0, 3) != "404")) { + return $stat; + } + } else { + return "412 precondition failed"; + } + } + + if ($del) { + if (!OC_FILESYSTEM::rename($source, $dest)) { + return "500 Internal server error"; + } + $destpath = $this->_unslashify($options["dest"]); + if (is_dir($source)) { + $dpath=OC_DB::escape($destpath); + $path=OC_DB::escape($options["path"]); + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}properties + SET path = REPLACE(path, '$path', '$dpath') + WHERE path LIKE '$path%'"; + OC_DB::query($query); + } + + $query = "UPDATE {$CONFIG_DBTABLEPREFIX}properties + SET path = '$dpath' + WHERE path = '$path'"; + OC_DB::query($query); + } else { + if (OC_FILESYSTEM::is_dir($source)) { + $files = OC_FILESYSTEM::getTree($source); + } else { + $files = array($source); + } + + if (!is_array($files) || empty($files)) { + return "500 Internal server error"; + } + + + foreach ($files as $file) { + if (OC_FILESYSTEM::is_dir($file)) { + $file = $this->_slashify($file); + } + $destfile = str_replace($source, $dest, $file); + + if (OC_FILESYSTEM::is_dir($file)) { + if (!OC_FILESYSTEM::file_exists($destfile)) { + if (!OC_FILESYSTEM::is_writeable(dirname($destfile))) { + return "403 Forbidden"; + } + if (!OC_FILESYSTEM::mkdir($destfile)) { + return "409 Conflict"; + } + } else if (!OC_FILESYSTEM::is_dir($destfile)) { + return "409 Conflict"; + } + } else { + if (!OC_FILESYSTEM::copy($file, $destfile)) { + return "409 Conflict($source) $file --> $destfile ".implode('::',$files); + } + } + } + } + return ($new && !$existing_col) ? "201 Created" : "204 No Content"; + } + + /** + * PROPPATCH method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function PROPPATCH(&$options) + { + global $prefs, $tab; + global $CONFIG_DBTABLEPREFIX; + + $msg = ""; + $path = $options["path"]; + $dir = dirname($path)."/"; + $base = basename($path); + + foreach ($options["props"] as $key => $prop) { + if ($prop["ns"] == "DAV:") { + $options["props"][$key]['status'] = "403 Forbidden"; + } else { + $path=OC_DB::escape($options['path']); + $name=OC_DB::escape($prop['name']); + $ns=OC_DB::escape($prop['ns']); + $val=OC_DB::escape($prop['val']); + if (isset($prop["val"])) { + $query = "REPLACE INTO {$CONFIG_DBTABLEPREFIX}properties (path,name,ns,value) VALUES('$path','$name','$ns','$val')"; + } else { + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}properties WHERE path = '$path' AND name = '$name' AND ns = '$ns'"; + } + OC_DB::query($query); + } + } + + return ""; + } + + + /** + * LOCK method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function LOCK(&$options) + { + global $CONFIG_DBTABLEPREFIX; + + // get absolute fs path to requested resource + $fspath = $options["path"]; + // TODO recursive locks on directories not supported yet + // makes litmus test "32. lock_collection" fail + if (OC_FILESYSTEM::is_dir($fspath) && !empty($options["depth"])) { + switch($options["depth"]){ + case 'infinity': + $recursion=1; + break; + case '0': + $recursion=0; + break; + } + }else{ + $recursion=0; + } + + $options["timeout"] = time()+300; // 5min. hardcoded + + if (isset($options["update"])) { // Lock Update + $where = "WHERE path = '$options[path]' AND token = '$options[update]'"; + + $query = "SELECT owner, exclusivelock FROM {$CONFIG_DBTABLEPREFIX}locks $where"; + $res = OC_DB::select($query); + + if (is_array($res) and isset($res[0])) { + $row=$res[0]; + $query = "UPDATE `{$CONFIG_DBTABLEPREFIX}locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + OC_DB::query($query); + + $options['owner'] = $row['owner']; + $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared"; + $options['type'] = $row["exclusivelock"] ? "write" : "read"; + + return true; + } else {//check for indirect refresh + $query = "SELECT * + FROM {$CONFIG_DBTABLEPREFIX}locks + WHERE recursive = 1 + "; + $res = OC_DB::select($query); + foreach($res as $row){ + if(strpos($options['path'],$row['path'])==0){//are we a child of a folder with an recursive lock + $where = "WHERE path = '$row[path]' AND token = '$options[update]'"; + $query = "UPDATE `{$CONFIG_DBTABLEPREFIX}locks` SET `expires` = '$options[timeout]', `modified` = ".time()." $where"; + OC_DB::query($query); + $options['owner'] = $row['owner']; + $options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared"; + $options['type'] = $row["exclusivelock"] ? "write" : "read"; + return true; + } + } + } + } + + $locktoken=OC_DB::escape($options['locktoken']); + $path=OC_DB::escape($options['path']); + $time=time(); + $owner=OC_DB::escape($options['owner']); + $timeout=OC_DB::escape($options['timeout']); + $exclusive=($options['scope'] === "exclusive" ? "1" : "0"); + $query = "INSERT INTO `{$CONFIG_DBTABLEPREFIX}locks` +(`token`,`path`,`created`,`modified`,`owner`,`expires`,`exclusivelock`,`recursive`) +VALUES ('$locktoken','$path',$time,$time,'$owner','timeout',$exclusive,$recursion)"; + OC_DB::query($query); + $rows=OC_DB::affected_rows(); + if(!OC_FILESYSTEM::file_exists($fspath) and $rows>0) { + return "201 Created"; + } + return OC_DB::affected_rows($rows) ? "200 OK" : "409 Conflict"; + } + + /** + * UNLOCK method handler + * + * @param array general parameter passing array + * @return bool true on success + */ + function UNLOCK(&$options) + { + global $CONFIG_DBTABLEPREFIX; + $query = "DELETE FROM {$CONFIG_DBTABLEPREFIX}locks + WHERE path = '$options[path]' + AND token = '$options[token]'"; + OC_DB::query($query); + + return OC_DB::affected_rows() ? "204 No Content" : "409 Conflict"; + } + + /** + * checkLock() helper + * + * @param string resource path to check for locks + * @return bool true on success + */ + function checkLock($path) + { + global $CONFIG_DBTABLEPREFIX; + + $result = false; + $query = "SELECT * + FROM {$CONFIG_DBTABLEPREFIX}locks + WHERE path = '$path' + "; + $res = OC_DB::select($query); + if (is_array($res) and isset($res[0])) { + $row=$res[0]; + + if ($row) { + $result = array( "type" => "write", + "scope" => $row["exclusivelock"] ? "exclusive" : "shared", + "depth" => 0, + "owner" => $row['owner'], + "token" => $row['token'], + "created" => $row['created'], + "modified" => $row['modified'], + "expires" => $row['expires'], + "recursive" => $row['recursive'] + ); + } + }else{ + //check for recursive locks; + $query = "SELECT * + FROM {$CONFIG_DBTABLEPREFIX}locks + WHERE recursive = 1 + "; + $res = OC_DB::select($query); + foreach($res as $row){ + if(strpos($path,$row['path'])==0){//are we a child of a folder with an recursive lock + $result = array( "type" => "write", + "scope" => $row["exclusivelock"] ? "exclusive" : "shared", + "depth" => 0, + "owner" => $row['owner'], + "token" => $row['token'], + "created" => $row['created'], + "modified" => $row['modified'], + "expires" => $row['expires'], + "recursive" => $row['recursive'] + ); + } + } + } + + return $result; + } +} + +?>
\ No newline at end of file |