package.xml 0000644 0001750 0001750 00000025476 12041241525 012521 0 ustar taffit taffit
HTTP_WebDAV_Serverpear.php.netWebDAV Server Baseclass.RFC2518 compliant helper class for WebDAV server implementation.Hartmut Holzgraefehholzgrahholzgra@php.netyesChristian Stockerchreguchregu@bitflux.chyes2012-10-221.0.0RC81.0.0RC5betabetaNew BSD License
QA release
Bug #19664 PHP Parse error: syntax error, unexpected '"' on line 559 (Filesystem.php)
4.41.4.0b10.90.9alphaalpha2003-02-18PHP
usable (complies to RFC 2518 in all but shared locks afaik)
but still in developement
0.9.10.9.1alphaalpha2003-05-28PHP
usable (complies to RFC 2518 in all but shared locks afaik)
but still in developement
0.990.99betabeta2003-11-18PHP
almost all TODO features and issues for 1.0 are now implemented,
there might be some additional API cleanups in PROPFIND and PROPPATCH
and some small issues in the Fileserver example still exist
as soon as this is done i think the packe is ready for
a 1.0RC1 release
Starting with this release it is now possible to return
streams from GET and PUT. IF you return a readable stream
from GET or a writable stream from PUT the base class will
take care of any further action including HTTP header
generation and handling of partial GETs and PUTs (if the
returned streams are seekable).
The only things you should return in addition to an
appropriate open stream are the current size of the
resource in $options['size'] for both GET and PUT
and the mimetype in $options['mimetype'] and modification
date in $options['mtime'] for GET.
0.99.10.99.1betabeta2004-04-22PHP
Some serious stuff showed up that needs to be added/fixed before
we go for 1.0. This release doesn't really address any of these,
it only fixes some small issues with the existing code and adds
comments in various places.
1.0.0rc11.0.0rc1betabeta2005-07-05PHP
Preparing for 1.0 release ...
1.0.0rc21.0.0rc2betabeta2006-01-15PHP
Still preparing for 1.0 release after some bug fixes ...
1.0.0RC31.0.0RC3betabeta2006-03-03PHP
More bug fixes, getting nearer to 1.0 release ...
- there are still known charset encoding issues
- some litmus locking tests are not passed yet
1.0.0RC41.0.0RC4betabeta2006-11-10PHP
More bug fixes:
- prevent warnings
- fixed failing litmus tests:
- lock_refresh
- fail_cond_put_unlocked
- fail hard on unimplemented recursive lock
- $_SERVER contents are now copied to the private _SERVER array
in the constructor, derived classes can extend the constructor
to modify $_SERVER contents
- some headers were missing from HEAD replies (Bug #7240)
- fixed variable name typos (Bug #7328)
- added support for configurable table name prefixes (Bug #8366)
- use @package-version@ placeholder in class headers (Bug #8811)
- PROPFIND now returns null resources for resources locked
but not yet created (Bug #8570)
1.0.0RC51.0.0RC5betabeta2010-10-05New BSD License
QA Release
Bug #1949 $_prop_encoding not used for $file['path'] hholzgra
Bug #2464 Missing return in http_GET() hholzgra
Bug #6189 Various international filename issues hholzgra
Bug #9367 Example Fileserver shows wrong uri for folders hholzgra
Bug #10107 WebDAV Server only works with mod_php hholzgra
Bug #10238 DELETE method in Filesystem.php breaks when path contains space character hholzgra
Bug #10614 $uri variable gets set wrongly hholzgra
Bug #10632 support D:lastaccessed as date field hholzgra
Bug #10637 HTTP_WebDav_Server is throwing E_NOTICE's jorrit
Bug #11069 download broken if mbstring.func_overload & 2 and utf-8 charset hholzgra
Bug #11070 broken GET with mbstring.func_overload & 2 hholzgra
Bug #11816 Fatal error when locking hholzgra
Bug #11902 Implement HEAD in Filesystem and Reuse in GET hholzgra
Bug #11903 CVS revision in fileheader hholzgra
Bug #12073 Issues with filenames containing spaces hholzgra
Bug #12282 MOVE (rename) not properly urldecoded hholzgra
Bug #12283 Double urldecode causes problems with filenames that have % or + hholzgra
Bug #12500 Undefined variable: host in HTTP/WebDAV/Server.php line 1470 hholzgra
Bug #12602 Irrelevant NA Misspellings in API: pathes > paths hholzgra
Bug #13372 script name is appended to path hholzgra
Bug #13809 Unhandled HTTP_CONTENT_* Headers hholzgra
Bug #13920 http_LOCK() expects string, requires int hholzgra
- License Change to the new BSD License
- reverting wrong namespace default logic
- whitespace fix in XML output
- Getting rid of warning suppression by @ prefixes (PEAR Bug #10637)
- added handling for Microsoft specific 'lastaccessed' and 'ishidden'
- Fix for $uri variable gets set wrongly (PEAR Bug #10614)
1.0.0RC61.0.0RC5betabeta2011-06-19New BSD License
QA Release
Fix dir structure
Bug #14163 Content-range is not processed (Hiroaki Kawai)
Bug #14242 Uploaded file will be broken (Hiroaki Kawai)
1.0.0RC71.0.0RC5betabeta2012-02-15New BSD License
QA release
Bug #18694 Filesystem.php GetDir wrongly encoded filesnames containing single quotes
1.0.0RC81.0.0RC5betabeta2012-10-22New BSD License
QA release
Bug #19664 PHP Parse error: syntax error, unexpected '"' on line 559 (Filesystem.php)
HTTP_WebDAV_Server-1.0.0RC8/ 0000755 0001750 0001750 00000000000 12671653173 014611 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/README 0000644 0001750 0001750 00000000761 12041241525 015456 0 ustar taffit taffit This code depends on code introduced into the developement branch for
PHP 4.3, so it will not run with PHP releases before 4.3.0
preliminary documentation is available in the dav.txt file,
although it is currently a little outdated ...
Server/Filesystem.php contains a sample implementation for a simple
file server (including property and lock info storage in a mySQL
database, see db/Fileserver.sql). This sample should give you a good
clue about how to use this class for your own purpose.
HTTP_WebDAV_Server-1.0.0RC8/COPYING 0000644 0001750 0001750 00000003756 12041241525 015640 0 ustar taffit taffit 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.
HTTP_WebDAV_Server-1.0.0RC8/file.php 0000644 0001750 0001750 00000000367 12041241525 016230 0 ustar taffit taffit ServeRequest($_SERVER["DOCUMENT_ROOT"]);
?> HTTP_WebDAV_Server-1.0.0RC8/HTTP/ 0000755 0001750 0001750 00000000000 12671653173 015370 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/HTTP/WebDAV/ 0000755 0001750 0001750 00000000000 12671653173 016440 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/HTTP/WebDAV/Server/ 0000755 0001750 0001750 00000000000 12671653173 017706 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/HTTP/WebDAV/Server/Filesystem.php 0000644 0001750 0001750 00000071322 12041241525 022531 0 ustar taffit taffit
* @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 = "";
/**
* MySQL Host where property and locking information is stored
*
* @access private
* @var string
*/
var $db_host = "localhost";
/**
* MySQL database for property/locking information storage
*
* @access private
* @var string
*/
var $db_name = "webdav";
/**
* MySQL table name prefix
*
* @access private
* @var string
*/
var $db_prefix = "";
/**
* MySQL user for property/locking db access
*
* @access private
* @var string
*/
var $db_user = "root";
/**
* MySQL password for property/locking db access
*
* @access private
* @var string
*/
var $db_passwd = "";
/**
* 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'];
}
// establish connection to property/locking db
mysql_connect($this->db_host, $this->db_user, $this->db_passwd) or die(mysql_error());
mysql_select_db($this->db_name) or die(mysql_error());
// TODO throw on connection problems
// 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 = $this->base . $options["path"];
// sanity check
if (!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"]) && is_dir($fspath) && is_readable($fspath)) {
// make sure path ends with '/'
$options["path"] = $this->_slashify($options["path"]);
// try to open directory
$handle = 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)
{
// map URI path to filesystem path
$fspath = $this->base . $path;
// create result array
$info = array();
// TODO remove slash append code when base clase is able to do it itself
$info["path"] = 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", filectime($fspath));
$info["props"][] = $this->mkprop("getlastmodified", filemtime($fspath));
// Microsoft extensions: last access time and 'hidden' status
$info["props"][] = $this->mkprop("lastaccessed", fileatime($fspath));
$info["props"][] = $this->mkprop("ishidden", ('.' === substr(basename($fspath), 0, 1)));
// type and size (caller already made sure that path exists)
if (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 (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", filesize($fspath));
}
// get additional properties from database
$query = "SELECT ns, name, value
FROM {$this->db_prefix}properties
WHERE path = '$path'";
$res = mysql_query($query);
while ($row = mysql_fetch_assoc($res)) {
$info["props"][] = $this->mkprop($row["ns"], $row["name"], $row["value"]);
}
mysql_free_result($res);
return $info;
}
/**
* detect if a given program is found in the search PATH
*
* helper function used by _mimetype() to detect if the
* external 'file' utility is available
*
* @param string program name
* @param string optional search path, defaults to $PATH
* @return bool true if executable program found in path
*/
function _can_execute($name, $path = false)
{
// path defaults to PATH from environment if not set
if ($path === false) {
$path = getenv("PATH");
}
// check method depends on operating system
if (!strncmp(PHP_OS, "WIN", 3)) {
// on Windows an appropriate COM or EXE file needs to exist
$exts = array(".exe", ".com");
$check_fn = "file_exists";
} else {
// anywhere else we look for an executable file of that name
$exts = array("");
$check_fn = "is_executable";
}
// now check the directories in the path for the program
foreach (explode(PATH_SEPARATOR, $path) as $dir) {
// skip invalid path entries
if (!file_exists($dir)) continue;
if (!is_dir($dir)) continue;
// and now look for the file
foreach ($exts as $ext) {
if ($check_fn("$dir/$name".$ext)) return true;
}
}
return false;
}
/**
* try to detect the mime type of a file
*
* @param string file path
* @return string guessed mime type
*/
function _mimetype($fspath)
{
if (is_dir($fspath)) {
// directories are easy
return "httpd/unix-directory";
} else if (function_exists("mime_content_type")) {
// use mime magic extension if available
$mime_type = mime_content_type($fspath);
} else if ($this->_can_execute("file")) {
// it looks like we have a 'file' command,
// lets see it it does have mime support
$fp = popen("file -i '$fspath' 2>/dev/null", "r");
$reply = fgets($fp);
pclose($fp);
// popen will not return an error if the binary was not found
// and find may not have mime support using "-i"
// so we test the format of the returned string
// the reply begins with the requested filename
if (!strncmp($reply, "$fspath: ", strlen($fspath)+2)) {
$reply = substr($reply, strlen($fspath)+2);
// followed by the mime type (maybe including options)
if (preg_match('|^[[:alnum:]_-]+/[[:alnum:]_-]+;?.*|', $reply, $matches)) {
$mime_type = $matches[0];
}
}
}
if (empty($mime_type)) {
// Fallback solution: try to guess the type by the file extension
// TODO: add more ...
// TODO: it has been suggested to delegate mimetype detection
// to apache but this has at least three issues:
// - works only with apache
// - needs file to be within the document tree
// - requires apache mod_magic
// TODO: can we use the registry for this on Windows?
// OTOH if the server is Windos the clients are likely to
// be Windows, too, and tend do ignore the Content-Type
// anyway (overriding it with information taken from
// the registry)
// TODO: have a seperate PEAR class for mimetype detection?
switch (strtolower(strrchr(basename($fspath), "."))) {
case ".html":
$mime_type = "text/html";
break;
case ".gif":
$mime_type = "image/gif";
break;
case ".jpg":
$mime_type = "image/jpeg";
break;
default:
$mime_type = "application/octet-stream";
break;
}
}
return $mime_type;
}
/**
* HEAD method handler
*
* @param array parameter passing array
* @return bool true on success
*/
function HEAD(&$options)
{
// get absolute fs path to requested resource
$fspath = $this->base . $options["path"];
// sanity check
if (!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'] = filemtime($fspath);
// detect resource size
$options['size'] = 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 = $this->base . $options["path"];
// is this a collection?
if (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'] = 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 (!is_readable($fspath)) {
return false;
}
$handle = opendir($fspath);
if (!$handle) {
return false;
}
echo "
Index of ".htmlspecialchars($options['path'])."\n";
echo "
";
closedir($handle);
echo "\n";
exit;
}
/**
* PUT method handler
*
* @param array parameter passing array
* @return bool true on success
*/
function PUT(&$options)
{
$fspath = $this->base . $options["path"];
$dir = dirname($fspath);
if (!file_exists($dir) || !is_dir($dir)) {
return "409 Conflict"; // TODO right status code for both?
}
$options["new"] = ! file_exists($fspath);
if ($options["new"] && !is_writeable($dir)) {
return "403 Forbidden";
}
if (!$options["new"] && !is_writeable($fspath)) {
return "403 Forbidden";
}
if (!$options["new"] && is_dir($fspath)) {
return "403 Forbidden";
}
$fp = fopen($fspath, "w");
return $fp;
}
/**
* MKCOL method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function MKCOL($options)
{
$path = $this->base .$options["path"];
$parent = dirname($path);
$name = basename($path);
if (!file_exists($parent)) {
return "409 Conflict";
}
if (!is_dir($parent)) {
return "403 Forbidden";
}
if ( 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 = 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)
{
$path = $this->base . "/" .$options["path"];
if (!file_exists($path)) {
return "404 Not found";
}
if (is_dir($path)) {
$query = "DELETE FROM {$this->db_prefix}properties
WHERE path LIKE '".$this->_slashify($options["path"])."%'";
mysql_query($query);
System::rm(array("-rf", $path));
} else {
unlink($path);
}
$query = "DELETE FROM {$this->db_prefix}properties
WHERE path = '$options[path]'";
mysql_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?)
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 = $this->base . $options["path"];
if (!file_exists($source)) {
return "404 Not found";
}
if (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 = $this->base . $options["dest"];
$destdir = dirname($dest);
if (!file_exists($destdir) || !is_dir($destdir)) {
return "409 Conflict";
}
$new = !file_exists($dest);
$existing_col = false;
if (!$new) {
if ($del && is_dir($dest)) {
if (!$options["overwrite"]) {
return "412 precondition failed";
}
$dest .= basename($source);
if (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 (!rename($source, $dest)) {
return "500 Internal server error";
}
$destpath = $this->_unslashify($options["dest"]);
if (is_dir($source)) {
$query = "UPDATE {$this->db_prefix}properties
SET path = REPLACE(path, '".$options["path"]."', '".$destpath."')
WHERE path LIKE '".$this->_slashify($options["path"])."%'";
mysql_query($query);
}
$query = "UPDATE {$this->db_prefix}properties
SET path = '".$destpath."'
WHERE path = '".$options["path"]."'";
mysql_query($query);
} else {
if (is_dir($source)) {
$files = System::find($source);
$files = array_reverse($files);
} else {
$files = array($source);
}
if (!is_array($files) || empty($files)) {
return "500 Internal server error";
}
foreach ($files as $file) {
if (is_dir($file)) {
$file = $this->_slashify($file);
}
$destfile = str_replace($source, $dest, $file);
if (is_dir($file)) {
if (!file_exists($destfile)) {
if (!is_writeable(dirname($destfile))) {
return "403 Forbidden";
}
if (!mkdir($destfile)) {
return "409 Conflict";
}
} else if (!is_dir($destfile)) {
return "409 Conflict";
}
} else {
if (!copy($file, $destfile)) {
return "409 Conflict";
}
}
}
$query = "INSERT INTO {$this->db_prefix}properties
SELECT *
FROM {$this->db_prefix}properties
WHERE path = '".$options['path']."'";
}
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;
$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 {
if (isset($prop["val"])) {
$query = "REPLACE INTO {$this->db_prefix}properties
SET path = '$options[path]'
, name = '$prop[name]'
, ns= '$prop[ns]'
, value = '$prop[val]'";
} else {
$query = "DELETE FROM {$this->db_prefix}properties
WHERE path = '$options[path]'
AND name = '$prop[name]'
AND ns = '$prop[ns]'";
}
mysql_query($query);
}
}
return "";
}
/**
* LOCK method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function LOCK(&$options)
{
// get absolute fs path to requested resource
$fspath = $this->base . $options["path"];
// TODO recursive locks on directories not supported yet
// makes litmus test "32. lock_collection" fail
if (is_dir($fspath) && !empty($options["depth"])) {
return "409 Conflict";
}
$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 {$this->db_prefix}locks $where";
$res = mysql_query($query);
$row = mysql_fetch_assoc($res);
mysql_free_result($res);
if (is_array($row)) {
$query = "UPDATE {$this->db_prefix}locks
SET expires = '$options[timeout]'
, modified = ".time()."
$where";
mysql_query($query);
$options['owner'] = $row['owner'];
$options['scope'] = $row["exclusivelock"] ? "exclusive" : "shared";
$options['type'] = $row["exclusivelock"] ? "write" : "read";
return true;
} else {
return false;
}
}
$query = "INSERT INTO {$this->db_prefix}locks
SET token = '$options[locktoken]'
, path = '$options[path]'
, created = ".time()."
, modified = ".time()."
, owner = '$options[owner]'
, expires = '$options[timeout]'
, exclusivelock = " .($options['scope'] === "exclusive" ? "1" : "0")
;
mysql_query($query);
return mysql_affected_rows() ? "200 OK" : "409 Conflict";
}
/**
* UNLOCK method handler
*
* @param array general parameter passing array
* @return bool true on success
*/
function UNLOCK(&$options)
{
$query = "DELETE FROM {$this->db_prefix}locks
WHERE path = '$options[path]'
AND token = '$options[token]'";
mysql_query($query);
return mysql_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)
{
$result = false;
$query = "SELECT owner, token, created, modified, expires, exclusivelock
FROM {$this->db_prefix}locks
WHERE path = '$path'
";
$res = mysql_query($query);
if ($res) {
$row = mysql_fetch_array($res);
mysql_free_result($res);
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']
);
}
}
return $result;
}
/**
* create database tables for property and lock storage
*
* @param void
* @return bool true on success
*/
function create_database()
{
// TODO
return false;
}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode:nil
* End:
*/
HTTP_WebDAV_Server-1.0.0RC8/HTTP/WebDAV/Server.php 0000755 0001750 0001750 00000210655 12041241525 020414 0 ustar taffit taffit
* @version @package_version@
*/
class HTTP_WebDAV_Server
{
// {{{ Member Variables
/**
* complete URI for this request
*
* @var string
*/
var $uri;
/**
* base URI for this request
*
* @var string
*/
var $base_uri;
/**
* URI path for this request
*
* @var string
*/
var $path;
/**
* Realm string to be used in authentification popups
*
* @var string
*/
var $http_auth_realm = "PHP WebDAV";
/**
* String to be used in "X-Dav-Powered-By" header
*
* @var string
*/
var $dav_powered_by = "";
/**
* Remember parsed If: (RFC2518/9.4) header conditions
*
* @var array
*/
var $_if_header_uris = array();
/**
* HTTP response status/message
*
* @var string
*/
var $_http_status = "200 OK";
/**
* encoding of property values passed in
*
* @var string
*/
var $_prop_encoding = "utf-8";
/**
* Copy of $_SERVER superglobal array
*
* Derived classes may extend the constructor to
* modify its contents
*
* @var array
*/
var $_SERVER;
// }}}
// {{{ Constructor
/**
* Constructor
*
* @param void
*/
function HTTP_WebDAV_Server()
{
// PHP messages destroy XML output -> switch them off
ini_set("display_errors", 0);
// copy $_SERVER variables to local _SERVER array
// so that derived classes can simply modify these
$this->_SERVER = $_SERVER;
}
// }}}
// {{{ ServeRequest()
/**
* Serve WebDAV HTTP request
*
* dispatch WebDAV HTTP request to the apropriate method handler
*
* @param void
* @return void
*/
function ServeRequest()
{
// prevent warning in litmus check 'delete_fragment'
if (strstr($this->_SERVER["REQUEST_URI"], '#')) {
$this->http_status("400 Bad Request");
return;
}
// default uri is the complete request uri
$uri = "http";
if (isset($this->_SERVER["HTTPS"]) && $this->_SERVER["HTTPS"] === "on") {
$uri = "https";
}
$uri.= "://".$this->_SERVER["HTTP_HOST"].$this->_SERVER["SCRIPT_NAME"];
// WebDAV has no concept of a query string and clients (including cadaver)
// seem to pass '?' unencoded, so we need to extract the path info out
// of the request URI ourselves
$path_info = substr($this->_SERVER["REQUEST_URI"], strlen($this->_SERVER["SCRIPT_NAME"]));
// just in case the path came in empty ...
if (empty($path_info)) {
$path_info = "/";
}
$this->base_uri = $uri;
$this->uri = $uri . $path_info;
// set path
$this->path = $this->_urldecode($path_info);
if (!strlen($this->path)) {
if ($this->_SERVER["REQUEST_METHOD"] == "GET") {
// redirect clients that try to GET a collection
// WebDAV clients should never try this while
// regular HTTP clients might ...
header("Location: ".$this->base_uri."/");
return;
} else {
// if a WebDAV client didn't give a path we just assume '/'
$this->path = "/";
}
}
if (ini_get("magic_quotes_gpc")) {
$this->path = stripslashes($this->path);
}
// identify ourselves
if (empty($this->dav_powered_by)) {
header("X-Dav-Powered-By: PHP class: ".get_class($this));
} else {
header("X-Dav-Powered-By: ".$this->dav_powered_by);
}
// check authentication
// for the motivation for not checking OPTIONS requests on / see
// http://pear.php.net/bugs/bug.php?id=5363
if ( ( !(($this->_SERVER['REQUEST_METHOD'] == 'OPTIONS') && ($this->path == "/")))
&& (!$this->_check_auth())) {
// RFC2518 says we must use Digest instead of Basic
// but Microsoft Clients do not support Digest
// and we don't support NTLM and Kerberos
// so we are stuck with Basic here
header('WWW-Authenticate: Basic realm="'.($this->http_auth_realm).'"');
// Windows seems to require this being the last header sent
// (changed according to PECL bug #3138)
$this->http_status('401 Unauthorized');
return;
}
// check
if (! $this->_check_if_header_conditions()) {
return;
}
// detect requested method names
$method = strtolower($this->_SERVER["REQUEST_METHOD"]);
$wrapper = "http_".$method;
// activate HEAD emulation by GET if no HEAD method found
if ($method == "head" && !method_exists($this, "head")) {
$method = "get";
}
if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) {
$this->$wrapper(); // call method by name
} else { // method not found/implemented
if ($this->_SERVER["REQUEST_METHOD"] == "LOCK") {
$this->http_status("412 Precondition failed");
} else {
$this->http_status("405 Method not allowed");
header("Allow: ".join(", ", $this->_allow())); // tell client what's allowed
}
}
}
// }}}
// {{{ abstract WebDAV methods
// {{{ GET()
/**
* GET implementation
*
* overload this method to retrieve resources from your server
*
*
*
* @abstract
* @param array &$params Array of input and output parameters
* input
*
path -
*
* output
*
size -
*
* @returns int HTTP-Statuscode
*/
/* abstract
function GET(&$params)
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ PUT()
/**
* PUT implementation
*
* PUT implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function PUT()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ COPY()
/**
* COPY implementation
*
* COPY implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function COPY()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ MOVE()
/**
* MOVE implementation
*
* MOVE implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function MOVE()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ DELETE()
/**
* DELETE implementation
*
* DELETE implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function DELETE()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ PROPFIND()
/**
* PROPFIND implementation
*
* PROPFIND implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function PROPFIND()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ PROPPATCH()
/**
* PROPPATCH implementation
*
* PROPPATCH implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function PROPPATCH()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ LOCK()
/**
* LOCK implementation
*
* LOCK implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function LOCK()
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ UNLOCK()
/**
* UNLOCK implementation
*
* UNLOCK implementation
*
* @abstract
* @param array &$params
* @returns int HTTP-Statuscode
*/
/* abstract
function UNLOCK()
{
// dummy entry for PHPDoc
}
*/
// }}}
// }}}
// {{{ other abstract methods
// {{{ check_auth()
/**
* check authentication
*
* overload this method to retrieve and confirm authentication information
*
* @abstract
* @param string type Authentication type, e.g. "basic" or "digest"
* @param string username Transmitted username
* @param string passwort Transmitted password
* @returns bool Authentication status
*/
/* abstract
function checkAuth($type, $username, $password)
{
// dummy entry for PHPDoc
}
*/
// }}}
// {{{ checklock()
/**
* check lock status for a resource
*
* overload this method to return shared and exclusive locks
* active for this resource
*
* @abstract
* @param string resource Resource path to check
* @returns array An array of lock entries each consisting
* of 'type' ('shared'/'exclusive'), 'token' and 'timeout'
*/
/* abstract
function checklock($resource)
{
// dummy entry for PHPDoc
}
*/
// }}}
// }}}
// {{{ WebDAV HTTP method wrappers
// {{{ http_OPTIONS()
/**
* OPTIONS method handler
*
* The OPTIONS method handler creates a valid OPTIONS reply
* including Dav: and Allowed: headers
* based on the implemented methods found in the actual instance
*
* @param void
* @return void
*/
function http_OPTIONS()
{
// Microsoft clients default to the Frontpage protocol
// unless we tell them to use WebDAV
header("MS-Author-Via: DAV");
// get allowed methods
$allow = $this->_allow();
// dav header
$dav = array(1); // assume we are always dav class 1 compliant
if (isset($allow['LOCK'])) {
$dav[] = 2; // dav class 2 requires that locking is supported
}
// tell clients what we found
$this->http_status("200 OK");
header("DAV: " .join(", ", $dav));
header("Allow: ".join(", ", $allow));
header("Content-length: 0");
}
// }}}
// {{{ http_PROPFIND()
/**
* PROPFIND method handler
*
* @param void
* @return void
*/
function http_PROPFIND()
{
$options = Array();
$files = Array();
$options["path"] = $this->path;
// search depth from header (default is "infinity)
if (isset($this->_SERVER['HTTP_DEPTH'])) {
$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
} else {
$options["depth"] = "infinity";
}
// analyze request payload
$propinfo = new _parse_propfind("php://input");
if (!$propinfo->success) {
$this->http_status("400 Error");
return;
}
$options['props'] = $propinfo->props;
// call user handler
if (!$this->PROPFIND($options, $files)) {
$files = array("files" => array());
if (method_exists($this, "checkLock")) {
// is locked?
$lock = $this->checkLock($this->path);
if (is_array($lock) && count($lock)) {
$created = isset($lock['created']) ? $lock['created'] : time();
$modified = isset($lock['modified']) ? $lock['modified'] : time();
$files['files'][] = array("path" => $this->_slashify($this->path),
"props" => array($this->mkprop("displayname", $this->path),
$this->mkprop("creationdate", $created),
$this->mkprop("getlastmodified", $modified),
$this->mkprop("resourcetype", ""),
$this->mkprop("getcontenttype", ""),
$this->mkprop("getcontentlength", 0))
);
}
}
if (empty($files['files'])) {
$this->http_status("404 Not Found");
return;
}
}
// collect namespaces here
$ns_hash = array();
// Microsoft Clients need this special namespace for date and time values
$ns_defs = "xmlns:ns0=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\"";
// now we loop over all returned file entries
foreach ($files["files"] as $filekey => $file) {
// nothing to do if no properties were returend for a file
if (!isset($file["props"]) || !is_array($file["props"])) {
continue;
}
// now loop over all returned properties
foreach ($file["props"] as $key => $prop) {
// as a convenience feature we do not require that user handlers
// restrict returned properties to the requested ones
// here we strip all unrequested entries out of the response
switch($options['props']) {
case "all":
// nothing to remove
break;
case "names":
// only the names of all existing properties were requested
// so we remove all values
unset($files["files"][$filekey]["props"][$key]["val"]);
break;
default:
$found = false;
// search property name in requested properties
foreach ((array)$options["props"] as $reqprop) {
if (!isset($reqprop["xmlns"])) {
$reqprop["xmlns"] = "";
}
if ( $reqprop["name"] == $prop["name"]
&& $reqprop["xmlns"] == $prop["ns"]) {
$found = true;
break;
}
}
// unset property and continue with next one if not found/requested
if (!$found) {
$files["files"][$filekey]["props"][$key]="";
continue(2);
}
break;
}
// namespace handling
if (empty($prop["ns"])) continue; // no namespace
$ns = $prop["ns"];
if ($ns == "DAV:") continue; // default namespace
if (isset($ns_hash[$ns])) continue; // already known
// register namespace
$ns_name = "ns".(count($ns_hash) + 1);
$ns_hash[$ns] = $ns_name;
$ns_defs .= " xmlns:$ns_name=\"$ns\"";
}
// we also need to add empty entries for properties that were requested
// but for which no values where returned by the user handler
if (is_array($options['props'])) {
foreach ($options["props"] as $reqprop) {
if ($reqprop['name']=="") continue; // skip empty entries
$found = false;
if (!isset($reqprop["xmlns"])) {
$reqprop["xmlns"] = "";
}
// check if property exists in result
foreach ($file["props"] as $prop) {
if ( $reqprop["name"] == $prop["name"]
&& $reqprop["xmlns"] == $prop["ns"]) {
$found = true;
break;
}
}
if (!$found) {
if ($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
// lockdiscovery is handled by the base class
$files["files"][$filekey]["props"][]
= $this->mkprop("DAV:",
"lockdiscovery",
$this->lockdiscovery($files["files"][$filekey]['path']));
} else {
// add empty value for this property
$files["files"][$filekey]["noprops"][] =
$this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
// register property namespace if not known yet
if ($reqprop["xmlns"] != "DAV:" && !isset($ns_hash[$reqprop["xmlns"]])) {
$ns_name = "ns".(count($ns_hash) + 1);
$ns_hash[$reqprop["xmlns"]] = $ns_name;
$ns_defs .= " xmlns:$ns_name=\"$reqprop[xmlns]\"";
}
}
}
}
}
}
// now we generate the reply header ...
$this->http_status("207 Multi-Status");
header('Content-Type: text/xml; charset="utf-8"');
// ... and payload
echo "\n";
echo "\n";
foreach ($files["files"] as $file) {
// ignore empty or incomplete entries
if (!is_array($file) || empty($file) || !isset($file["path"])) continue;
$path = $file['path'];
if (!is_string($path) || $path==="") continue;
echo " \n";
/* TODO right now the user implementation has to make sure
collections end in a slash, this should be done in here
by checking the resource attribute */
$href = $this->_mergePaths($this->_SERVER['SCRIPT_NAME'], $path);
/* minimal urlencoding is needed for the resource path */
$href = $this->_urlencode($href);
echo " $href\n";
// report all found properties and their values (if any)
if (isset($file["props"]) && is_array($file["props"])) {
echo " \n";
echo " \n";
foreach ($file["props"] as $key => $prop) {
if (!is_array($prop)) continue;
if (!isset($prop["name"])) continue;
if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
// empty properties (cannot use empty() for check as "0" is a legal value here)
if ($prop["ns"]=="DAV:") {
echo " \n";
} else if (!empty($prop["ns"])) {
echo " <".$ns_hash[$prop["ns"]].":$prop[name]/>\n";
} else {
echo " <$prop[name] xmlns=\"\"/>";
}
} else if ($prop["ns"] == "DAV:") {
// some WebDAV properties need special treatment
switch ($prop["name"]) {
case "creationdate":
echo " "
. gmdate("Y-m-d\\TH:i:s\\Z", $prop['val'])
. "\n";
break;
case "getlastmodified":
echo " "
. gmdate("D, d M Y H:i:s ", $prop['val'])
. "GMT\n";
break;
case "resourcetype":
echo " \n";
break;
case "supportedlock":
echo " $prop[val]\n";
break;
case "lockdiscovery":
echo " \n";
echo $prop["val"];
echo " \n";
break;
// the following are non-standard Microsoft extensions to the DAV namespace
case "lastaccessed":
echo " "
. gmdate("D, d M Y H:i:s ", $prop['val'])
. "GMT\n";
break;
case "ishidden":
echo " "
. is_string($prop['val']) ? $prop['val'] : ($prop['val'] ? 'true' : 'false')
. "\n";
break;
default:
echo " "
. $this->_prop_encode(htmlspecialchars($prop['val']))
. "\n";
break;
}
} else {
// properties from namespaces != "DAV:" or without any namespace
if ($prop["ns"]) {
echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>"
. $this->_prop_encode(htmlspecialchars($prop['val']))
. "" . $ns_hash[$prop["ns"]] . ":$prop[name]>\n";
} else {
echo " <$prop[name] xmlns=\"\">"
. $this->_prop_encode(htmlspecialchars($prop['val']))
. "$prop[name]>\n";
}
}
}
echo " \n";
echo " HTTP/1.1 200 OK\n";
echo " \n";
}
// now report all properties requested but not found
if (isset($file["noprops"])) {
echo " \n";
echo " \n";
foreach ($file["noprops"] as $key => $prop) {
if ($prop["ns"] == "DAV:") {
echo " \n";
} else if ($prop["ns"] == "") {
echo " <$prop[name] xmlns=\"\"/>\n";
} else {
echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]/>\n";
}
}
echo " \n";
echo " HTTP/1.1 404 Not Found\n";
echo " \n";
}
echo " \n";
}
echo "\n";
}
// }}}
// {{{ http_PROPPATCH()
/**
* PROPPATCH method handler
*
* @param void
* @return void
*/
function http_PROPPATCH()
{
if ($this->_check_lock_status($this->path)) {
$options = Array();
$options["path"] = $this->path;
$propinfo = new _parse_proppatch("php://input");
if (!$propinfo->success) {
$this->http_status("400 Error");
return;
}
$options['props'] = $propinfo->props;
$responsedescr = $this->PROPPATCH($options);
$this->http_status("207 Multi-Status");
header('Content-Type: text/xml; charset="utf-8"');
echo "\n";
echo "\n";
echo " \n";
echo " ".$this->_urlencode($this->_mergePaths($this->_SERVER["SCRIPT_NAME"], $this->path))."\n";
foreach ($options["props"] as $prop) {
echo " \n";
echo " <$prop[name] xmlns=\"$prop[ns]\"/>\n";
echo " HTTP/1.1 $prop[status]\n";
echo " \n";
}
if ($responsedescr) {
echo " ".
$this->_prop_encode(htmlspecialchars($responsedescr)).
"\n";
}
echo " \n";
echo "\n";
} else {
$this->http_status("423 Locked");
}
}
// }}}
// {{{ http_MKCOL()
/**
* MKCOL method handler
*
* @param void
* @return void
*/
function http_MKCOL()
{
$options = Array();
$options["path"] = $this->path;
$stat = $this->MKCOL($options);
$this->http_status($stat);
}
// }}}
// {{{ http_GET()
/**
* GET method handler
*
* @param void
* @returns void
*/
function http_GET()
{
// TODO check for invalid stream
$options = Array();
$options["path"] = $this->path;
$this->_get_ranges($options);
if (true === ($status = $this->GET($options))) {
if (!headers_sent()) {
$status = "200 OK";
if (!isset($options['mimetype'])) {
$options['mimetype'] = "application/octet-stream";
}
header("Content-type: $options[mimetype]");
if (isset($options['mtime'])) {
header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
}
if (isset($options['stream'])) {
// GET handler returned a stream
if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
// partial request and stream is seekable
if (count($options['ranges']) === 1) {
$range = $options['ranges'][0];
if (isset($range['start'])) {
fseek($options['stream'], $range['start'], SEEK_SET);
if (feof($options['stream'])) {
$this->http_status("416 Requested range not satisfiable");
return;
}
if (isset($range['end'])) {
$size = $range['end']-$range['start']+1;
$this->http_status("206 partial");
header("Content-length: $size");
header("Content-range: $range[start]-$range[end]/"
. (isset($options['size']) ? $options['size'] : "*"));
while ($size && !feof($options['stream'])) {
$buffer = fread($options['stream'], 4096);
$size -= $this->bytes($buffer);
echo $buffer;
}
} else {
$this->http_status("206 partial");
if (isset($options['size'])) {
header("Content-length: ".($options['size'] - $range['start']));
header("Content-range: ".$range['start']."-".$range['end']."/"
. (isset($options['size']) ? $options['size'] : "*"));
}
fpassthru($options['stream']);
}
} else {
header("Content-length: ".$range['last']);
fseek($options['stream'], -$range['last'], SEEK_END);
fpassthru($options['stream']);
}
} else {
$this->_multipart_byterange_header(); // init multipart
foreach ($options['ranges'] as $range) {
// TODO what if size unknown? 500?
if (isset($range['start'])) {
$from = $range['start'];
$to = !empty($range['end']) ? $range['end'] : $options['size']-1;
} else {
$from = $options['size'] - $range['last']-1;
$to = $options['size'] -1;
}
$total = isset($options['size']) ? $options['size'] : "*";
$size = $to - $from + 1;
$this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
fseek($options['stream'], $from, SEEK_SET);
while ($size && !feof($options['stream'])) {
$buffer = fread($options['stream'], 4096);
$size -= $this->bytes($buffer);
echo $buffer;
}
}
$this->_multipart_byterange_header(); // end multipart
}
} else {
// normal request or stream isn't seekable, return full content
if (isset($options['size'])) {
header("Content-length: ".$options['size']);
}
fpassthru($options['stream']);
return; // no more headers
}
} elseif (isset($options['data'])) {
if (is_array($options['data'])) {
// reply to partial request
} else {
header("Content-length: ".$this->bytes($options['data']));
echo $options['data'];
}
}
}
}
if (!headers_sent()) {
if (false === $status) {
$this->http_status("404 not found");
} else {
// TODO: check setting of headers in various code paths above
$this->http_status("$status");
}
}
}
/**
* parse HTTP Range: header
*
* @param array options array to store result in
* @return void
*/
function _get_ranges(&$options)
{
// process Range: header if present
if (isset($this->_SERVER['HTTP_RANGE'])) {
// we only support standard "bytes" range specifications for now
if (preg_match('/bytes\s*=\s*(.+)/', $this->_SERVER['HTTP_RANGE'], $matches)) {
$options["ranges"] = array();
// ranges are comma separated
foreach (explode(",", $matches[1]) as $range) {
// ranges are either from-to pairs or just end positions
list($start, $end) = explode("-", $range);
$options["ranges"][] = ($start==="")
? array("last"=>$end)
: array("start"=>$start, "end"=>$end);
}
}
}
}
/**
* generate separator headers for multipart response
*
* first and last call happen without parameters to generate
* the initial header and closing sequence, all calls inbetween
* require content mimetype, start and end byte position and
* optionaly the total byte length of the requested resource
*
* @param string mimetype
* @param int start byte position
* @param int end byte position
* @param int total resource byte size
*/
function _multipart_byterange_header($mimetype = false, $from = false, $to=false, $total=false)
{
if ($mimetype === false) {
if (!isset($this->multipart_separator)) {
// initial
// a little naive, this sequence *might* be part of the content
// but it's really not likely and rather expensive to check
$this->multipart_separator = "SEPARATOR_".md5(microtime());
// generate HTTP header
header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator);
} else {
// final
// generate closing multipart sequence
echo "\n--{$this->multipart_separator}--";
}
} else {
// generate separator and header for next part
echo "\n--{$this->multipart_separator}\n";
echo "Content-type: $mimetype\n";
echo "Content-range: $from-$to/". ($total === false ? "*" : $total);
echo "\n\n";
}
}
// }}}
// {{{ http_HEAD()
/**
* HEAD method handler
*
* @param void
* @return void
*/
function http_HEAD()
{
$status = false;
$options = Array();
$options["path"] = $this->path;
if (method_exists($this, "HEAD")) {
$status = $this->head($options);
} else if (method_exists($this, "GET")) {
ob_start();
$status = $this->GET($options);
if (!isset($options['size'])) {
$options['size'] = ob_get_length();
}
ob_end_clean();
}
if (!isset($options['mimetype'])) {
$options['mimetype'] = "application/octet-stream";
}
header("Content-type: $options[mimetype]");
if (isset($options['mtime'])) {
header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
}
if (isset($options['size'])) {
header("Content-length: ".$options['size']);
}
if ($status === true) $status = "200 OK";
if ($status === false) $status = "404 Not found";
$this->http_status($status);
}
// }}}
// {{{ http_PUT()
/**
* PUT method handler
*
* @param void
* @return void
*/
function http_PUT()
{
if ($this->_check_lock_status($this->path)) {
$options = Array();
$options["path"] = $this->path;
$options["content_length"] = $this->_SERVER["CONTENT_LENGTH"];
// get the Content-type
if (isset($this->_SERVER["CONTENT_TYPE"])) {
// for now we do not support any sort of multipart requests
if (!strncmp($this->_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
$this->http_status("501 not implemented");
echo "The service does not support mulipart PUT requests";
return;
}
$options["content_type"] = $this->_SERVER["CONTENT_TYPE"];
} else {
// default content type if none given
$options["content_type"] = "application/octet-stream";
}
/* RFC 2616 2.6 says: "The recipient of the entity MUST NOT
ignore any Content-* (e.g. Content-Range) headers that it
does not understand or implement and MUST return a 501
(Not Implemented) response in such cases."
*/
foreach ($this->_SERVER as $key => $val) {
if (strncmp($key, "HTTP_CONTENT", 11)) continue;
switch ($key) {
case 'HTTP_CONTENT_ENCODING': // RFC 2616 14.11
// TODO support this if ext/zlib filters are available
$this->http_status("501 not implemented");
echo "The service does not support '$val' content encoding";
return;
case 'HTTP_CONTENT_LANGUAGE': // RFC 2616 14.12
// we assume it is not critical if this one is ignored
// in the actual PUT implementation ...
$options["content_language"] = $val;
break;
case 'HTTP_CONTENT_LENGTH':
// defined on IIS and has the same value as CONTENT_LENGTH
break;
case 'HTTP_CONTENT_LOCATION': // RFC 2616 14.14
/* The meaning of the Content-Location header in PUT
or POST requests is undefined; servers are free
to ignore it in those cases. */
break;
case 'HTTP_CONTENT_RANGE': // RFC 2616 14.16
// single byte range requests are supported
// the header format is also specified in RFC 2616 14.16
// TODO we have to ensure that implementations support this or send 501 instead
if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
$this->http_status("400 bad request");
echo "The service does only support single byte ranges";
return;
}
$range = array("start" => $matches[1], "end" => $matches[2]);
if (is_numeric($matches[3])) {
$range["total_length"] = $matches[3];
}
if (!isset($options['ranges'])) {
$options['ranges'] = array();
}
$options["ranges"][] = $range;
// TODO make sure the implementation supports partial PUT
// this has to be done in advance to avoid data being overwritten
// on implementations that do not support this ...
break;
case 'HTTP_CONTENT_TYPE':
// defined on IIS and has the same value as CONTENT_TYPE
break;
case 'HTTP_CONTENT_MD5': // RFC 2616 14.15
// TODO: maybe we can just pretend here?
$this->http_status("501 not implemented");
echo "The service does not support content MD5 checksum verification";
return;
default:
// any other unknown Content-* headers
$this->http_status("501 not implemented");
echo "The service does not support '$key'";
return;
}
}
$options["stream"] = fopen("php://input", "r");
$stat = $this->PUT($options);
if ($stat === false) {
$stat = "403 Forbidden";
} else if (is_resource($stat) && get_resource_type($stat) == "stream") {
$stream = $stat;
$stat = $options["new"] ? "201 Created" : "204 No Content";
if (!empty($options["ranges"])) {
// TODO multipart support is missing (see also above)
if (0 == fseek($stream, $options['ranges'][0]["start"], SEEK_SET)) {
$length = $options['ranges'][0]["end"] - $options['ranges'][0]["start"]+1;
while (!feof($options['stream'])) {
if ($length <= 0) {
break;
}
if ($length <= 8192) {
$data = fread($options['stream'], $length);
} else {
$data = fread($options['stream'], 8192);
}
if ($data === false) {
$stat = "400 Bad request";
} elseif (strlen($data)) {
if (false === fwrite($stream, $data)) {
$stat = "403 Forbidden";
break;
}
$length -= strlen($data);
}
}
} else {
$stat = "403 Forbidden";
}
} else {
while (!feof($options["stream"])) {
if (false === fwrite($stream, fread($options["stream"], 8192))) {
$stat = "403 Forbidden";
break;
}
}
}
fclose($stream);
}
$this->http_status($stat);
} else {
$this->http_status("423 Locked");
}
}
// }}}
// {{{ http_DELETE()
/**
* DELETE method handler
*
* @param void
* @return void
*/
function http_DELETE()
{
// check RFC 2518 Section 9.2, last paragraph
if (isset($this->_SERVER["HTTP_DEPTH"])) {
if ($this->_SERVER["HTTP_DEPTH"] != "infinity") {
$this->http_status("400 Bad Request");
return;
}
}
// check lock status
if ($this->_check_lock_status($this->path)) {
// ok, proceed
$options = Array();
$options["path"] = $this->path;
$stat = $this->DELETE($options);
$this->http_status($stat);
} else {
// sorry, its locked
$this->http_status("423 Locked");
}
}
// }}}
// {{{ http_COPY()
/**
* COPY method handler
*
* @param void
* @return void
*/
function http_COPY()
{
// no need to check source lock status here
// destination lock status is always checked by the helper method
$this->_copymove("copy");
}
// }}}
// {{{ http_MOVE()
/**
* MOVE method handler
*
* @param void
* @return void
*/
function http_MOVE()
{
if ($this->_check_lock_status($this->path)) {
// destination lock status is always checked by the helper method
$this->_copymove("move");
} else {
$this->http_status("423 Locked");
}
}
// }}}
// {{{ http_LOCK()
/**
* LOCK method handler
*
* @param void
* @return void
*/
function http_LOCK()
{
$options = Array();
$options["path"] = $this->path;
if (isset($this->_SERVER['HTTP_DEPTH'])) {
$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
} else {
$options["depth"] = "infinity";
}
if (isset($this->_SERVER["HTTP_TIMEOUT"])) {
$options["timeout"] = explode(",", $this->_SERVER["HTTP_TIMEOUT"]);
}
if (empty($this->_SERVER['CONTENT_LENGTH']) && !empty($this->_SERVER['HTTP_IF'])) {
// check if locking is possible
if (!$this->_check_lock_status($this->path)) {
$this->http_status("423 Locked");
return;
}
// refresh lock
$options["locktoken"] = substr($this->_SERVER['HTTP_IF'], 2, -2);
$options["update"] = $options["locktoken"];
// setting defaults for required fields, LOCK() SHOULD overwrite these
$options['owner'] = "unknown";
$options['scope'] = "exclusive";
$options['type'] = "write";
$stat = $this->LOCK($options);
} else {
// extract lock request information from request XML payload
$lockinfo = new _parse_lockinfo("php://input");
if (!$lockinfo->success) {
$this->http_status("400 bad request");
}
// check if locking is possible
if (!$this->_check_lock_status($this->path, $lockinfo->lockscope === "shared")) {
$this->http_status("423 Locked");
return;
}
// new lock
$options["scope"] = $lockinfo->lockscope;
$options["type"] = $lockinfo->locktype;
$options["owner"] = $lockinfo->owner;
$options["locktoken"] = $this->_new_locktoken();
$stat = $this->LOCK($options);
}
if (is_bool($stat)) {
$http_stat = $stat ? "200 OK" : "423 Locked";
} else {
$http_stat = (string)$stat;
}
$this->http_status($http_stat);
if ($http_stat{0} == 2) { // 2xx states are ok
if ($options["timeout"]) {
// if multiple timeout values were given we take the first only
if (is_array($options["timeout"])) {
reset($options["timeout"]);
$options["timeout"] = current($options["timeout"]);
}
// if the timeout is numeric only we need to reformat it
if (is_numeric($options["timeout"])) {
// more than a million is considered an absolute timestamp
// less is more likely a relative value
if ($options["timeout"]>1000000) {
$timeout = "Second-".($options['timeout']-time());
} else {
$timeout = "Second-$options[timeout]";
}
} else {
// non-numeric values are passed on verbatim,
// no error checking is performed here in this case
// TODO: send "Infinite" on invalid timeout strings?
$timeout = $options["timeout"];
}
} else {
$timeout = "Infinite";
}
header('Content-Type: text/xml; charset="utf-8"');
header("Lock-Token: <$options[locktoken]>");
echo "\n";
echo "\n";
echo " \n";
echo " \n";
echo " \n";
echo " \n";
echo " $options[depth]\n";
echo " $options[owner]\n";
echo " $timeout\n";
echo " $options[locktoken]\n";
echo " \n";
echo " \n";
echo "\n\n";
}
}
// }}}
// {{{ http_UNLOCK()
/**
* UNLOCK method handler
*
* @param void
* @return void
*/
function http_UNLOCK()
{
$options = Array();
$options["path"] = $this->path;
if (isset($this->_SERVER['HTTP_DEPTH'])) {
$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
} else {
$options["depth"] = "infinity";
}
// strip surrounding <>
$options["token"] = substr(trim($this->_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
// call user method
$stat = $this->UNLOCK($options);
$this->http_status($stat);
}
// }}}
// }}}
// {{{ _copymove()
function _copymove($what)
{
$options = Array();
$options["path"] = $this->path;
if (isset($this->_SERVER["HTTP_DEPTH"])) {
$options["depth"] = $this->_SERVER["HTTP_DEPTH"];
} else {
$options["depth"] = "infinity";
}
$http_header_host = preg_replace("/:80$/", "", $this->_SERVER["HTTP_HOST"]);
$url = parse_url($this->_SERVER["HTTP_DESTINATION"]);
$path = urldecode($url["path"]);
if (isset($url["host"])) {
// TODO check url scheme, too
$http_host = $url["host"];
if (isset($url["port"]) && $url["port"] != 80)
$http_host.= ":".$url["port"];
} else {
// only path given, set host to self
$http_host == $http_header_host;
}
if ($http_host == $http_header_host &&
!strncmp($this->_SERVER["SCRIPT_NAME"], $path,
strlen($this->_SERVER["SCRIPT_NAME"]))) {
$options["dest"] = substr($path, strlen($this->_SERVER["SCRIPT_NAME"]));
if (!$this->_check_lock_status($options["dest"])) {
$this->http_status("423 Locked");
return;
}
} else {
$options["dest_url"] = $this->_SERVER["HTTP_DESTINATION"];
}
// see RFC 2518 Sections 9.6, 8.8.4 and 8.9.3
if (isset($this->_SERVER["HTTP_OVERWRITE"])) {
$options["overwrite"] = $this->_SERVER["HTTP_OVERWRITE"] == "T";
} else {
$options["overwrite"] = true;
}
$stat = $this->$what($options);
$this->http_status($stat);
}
// }}}
// {{{ _allow()
/**
* check for implemented HTTP methods
*
* @param void
* @return array something
*/
function _allow()
{
// OPTIONS is always there
$allow = array("OPTIONS" =>"OPTIONS");
// all other METHODS need both a http_method() wrapper
// and a method() implementation
// the base class supplies wrappers only
foreach (get_class_methods($this) as $method) {
if (!strncmp("http_", $method, 5)) {
$method = strtoupper(substr($method, 5));
if (method_exists($this, $method)) {
$allow[$method] = $method;
}
}
}
// we can emulate a missing HEAD implemetation using GET
if (isset($allow["GET"]))
$allow["HEAD"] = "HEAD";
// no LOCK without checklok()
if (!method_exists($this, "checklock")) {
unset($allow["LOCK"]);
unset($allow["UNLOCK"]);
}
return $allow;
}
// }}}
/**
* helper for property element creation
*
* @param string XML namespace (optional)
* @param string property name
* @param string property value
* @return array property array
*/
function mkprop()
{
$args = func_get_args();
if (count($args) == 3) {
return array("ns" => $args[0],
"name" => $args[1],
"val" => $args[2]);
} else {
return array("ns" => "DAV:",
"name" => $args[0],
"val" => $args[1]);
}
}
// {{{ _check_auth
/**
* check authentication if check is implemented
*
* @param void
* @return bool true if authentication succeded or not necessary
*/
function _check_auth()
{
$auth_type = isset($this->_SERVER["AUTH_TYPE"])
? $this->_SERVER["AUTH_TYPE"]
: null;
$auth_user = isset($this->_SERVER["PHP_AUTH_USER"])
? $this->_SERVER["PHP_AUTH_USER"]
: null;
$auth_pw = isset($this->_SERVER["PHP_AUTH_PW"])
? $this->_SERVER["PHP_AUTH_PW"]
: null;
if (method_exists($this, "checkAuth")) {
// PEAR style method name
return $this->checkAuth($auth_type, $auth_user, $auth_pw);
} else if (method_exists($this, "check_auth")) {
// old (pre 1.0) method name
return $this->check_auth($auth_type, $auth_user, $auth_pw);
} else {
// no method found -> no authentication required
return true;
}
}
// }}}
// {{{ UUID stuff
/**
* generate Unique Universal IDentifier for lock token
*
* @param void
* @return string a new UUID
*/
function _new_uuid()
{
// use uuid extension from PECL if available
if (function_exists("uuid_create")) {
return uuid_create();
}
// fallback
$uuid = md5(microtime().getmypid()); // this should be random enough for now
// set variant and version fields for 'true' random uuid
$uuid{12} = "4";
$n = 8 + (ord($uuid{16}) & 3);
$hex = "0123456789abcdef";
$uuid{16} = $hex{$n};
// return formated uuid
return substr($uuid, 0, 8)."-"
. substr($uuid, 8, 4)."-"
. substr($uuid, 12, 4)."-"
. substr($uuid, 16, 4)."-"
. substr($uuid, 20);
}
/**
* create a new opaque lock token as defined in RFC2518
*
* @param void
* @return string new RFC2518 opaque lock token
*/
function _new_locktoken()
{
return "opaquelocktoken:".$this->_new_uuid();
}
// }}}
// {{{ WebDAV If: header parsing
/**
*
*
* @param string header string to parse
* @param int current parsing position
* @return array next token (type and value)
*/
function _if_header_lexer($string, &$pos)
{
// skip whitespace
while (ctype_space($string{$pos})) {
++$pos;
}
// already at end of string?
if (strlen($string) <= $pos) {
return false;
}
// get next character
$c = $string{$pos++};
// now it depends on what we found
switch ($c) {
case "<":
// URIs are enclosed in <...>
$pos2 = strpos($string, ">", $pos);
$uri = substr($string, $pos, $pos2 - $pos);
$pos = $pos2 + 1;
return array("URI", $uri);
case "[":
//Etags are enclosed in [...]
if ($string{$pos} == "W") {
$type = "ETAG_WEAK";
$pos += 2;
} else {
$type = "ETAG_STRONG";
}
$pos2 = strpos($string, "]", $pos);
$etag = substr($string, $pos + 1, $pos2 - $pos - 2);
$pos = $pos2 + 1;
return array($type, $etag);
case "N":
// "N" indicates negation
$pos += 2;
return array("NOT", "Not");
default:
// anything else is passed verbatim char by char
return array("CHAR", $c);
}
}
/**
* parse If: header
*
* @param string header string
* @return array URIs and their conditions
*/
function _if_header_parser($str)
{
$pos = 0;
$len = strlen($str);
$uris = array();
// parser loop
while ($pos < $len) {
// get next token
$token = $this->_if_header_lexer($str, $pos);
// check for URI
if ($token[0] == "URI") {
$uri = $token[1]; // remember URI
$token = $this->_if_header_lexer($str, $pos); // get next token
} else {
$uri = "";
}
// sanity check
if ($token[0] != "CHAR" || $token[1] != "(") {
return false;
}
$list = array();
$level = 1;
$not = "";
while ($level) {
$token = $this->_if_header_lexer($str, $pos);
if ($token[0] == "NOT") {
$not = "!";
continue;
}
switch ($token[0]) {
case "CHAR":
switch ($token[1]) {
case "(":
$level++;
break;
case ")":
$level--;
break;
default:
return false;
}
break;
case "URI":
$list[] = $not."<$token[1]>";
break;
case "ETAG_WEAK":
$list[] = $not."[W/'$token[1]']>";
break;
case "ETAG_STRONG":
$list[] = $not."['$token[1]']>";
break;
default:
return false;
}
$not = "";
}
if (isset($uris[$uri]) && is_array($uris[$uri])) {
$uris[$uri] = array_merge($uris[$uri], $list);
} else {
$uris[$uri] = $list;
}
}
return $uris;
}
/**
* check if conditions from "If:" headers are meat
*
* the "If:" header is an extension to HTTP/1.1
* defined in RFC 2518 section 9.4
*
* @param void
* @return void
*/
function _check_if_header_conditions()
{
if (isset($this->_SERVER["HTTP_IF"])) {
$this->_if_header_uris =
$this->_if_header_parser($this->_SERVER["HTTP_IF"]);
foreach ($this->_if_header_uris as $uri => $conditions) {
if ($uri == "") {
$uri = $this->uri;
}
// all must match
$state = true;
foreach ($conditions as $condition) {
// lock tokens may be free form (RFC2518 6.3)
// but if opaquelocktokens are used (RFC2518 6.4)
// we have to check the format (litmus tests this)
if (!strncmp($condition, "$/', $condition)) {
$this->http_status("423 Locked");
return false;
}
}
if (!$this->_check_uri_condition($uri, $condition)) {
$this->http_status("412 Precondition failed");
$state = false;
break;
}
}
// any match is ok
if ($state == true) {
return true;
}
}
return false;
}
return true;
}
/**
* Check a single URI condition parsed from an if-header
*
* Check a single URI condition parsed from an if-header
*
* @abstract
* @param string $uri URI to check
* @param string $condition Condition to check for this URI
* @returns bool Condition check result
*/
function _check_uri_condition($uri, $condition)
{
// not really implemented here,
// implementations must override
// a lock token can never be from the DAV: scheme
// litmus uses DAV:no-lock in some tests
if (!strncmp(" ignored for now
if (method_exists($this, "checkLock")) {
// is locked?
$lock = $this->checkLock($path);
// ... and lock is not owned?
if (is_array($lock) && count($lock)) {
// FIXME doesn't check uri restrictions yet
if (!isset($this->_SERVER["HTTP_IF"]) || !strstr($this->_SERVER["HTTP_IF"], $lock["token"])) {
if (!$exclusive_only || ($lock["scope"] !== "shared"))
return false;
}
}
}
return true;
}
// }}}
/**
* Generate lockdiscovery reply from checklock() result
*
* @param string resource path to check
* @return string lockdiscovery response
*/
function lockdiscovery($path)
{
// no lock support without checklock() method
if (!method_exists($this, "checklock")) {
return "";
}
// collect response here
$activelocks = "";
// get checklock() reply
$lock = $this->checklock($path);
// generate block for returned data
if (is_array($lock) && count($lock)) {
// check for 'timeout' or 'expires'
if (!empty($lock["expires"])) {
$timeout = "Second-".($lock["expires"] - time());
} else if (!empty($lock["timeout"])) {
$timeout = "Second-$lock[timeout]";
} else {
$timeout = "Infinite";
}
// genreate response block
$activelocks.= "
$lock[depth]$lock[owner]$timeout$lock[token]
";
}
// return generated response
return $activelocks;
}
/**
* set HTTP return status and mirror it in a private header
*
* @param string status code and message
* @return void
*/
function http_status($status)
{
// simplified success case
if ($status === true) {
$status = "200 OK";
}
// remember status
$this->_http_status = $status;
// generate HTTP status response
header("HTTP/1.1 $status");
header("X-WebDAV-Status: $status", true);
}
/**
* private minimalistic version of PHP urlencode()
*
* only blanks, percent and XML special chars must be encoded here
* full urlencode() encoding confuses some clients ...
*
* @param string URL to encode
* @return string encoded URL
*/
function _urlencode($url)
{
return strtr($url, array(" "=>"%20",
"%"=>"%25",
"&"=>"%26",
"<"=>"%3C",
">"=>"%3E",
));
}
/**
* private version of PHP urldecode
*
* not really needed but added for completenes
*
* @param string URL to decode
* @return string decoded URL
*/
function _urldecode($path)
{
return rawurldecode($path);
}
/**
* UTF-8 encode property values if not already done so
*
* @param string text to encode
* @return string utf-8 encoded text
*/
function _prop_encode($text)
{
switch (strtolower($this->_prop_encoding)) {
case "utf-8":
return $text;
case "iso-8859-1":
case "iso-8859-15":
case "latin-1":
default:
return utf8_encode($text);
}
}
/**
* Slashify - make sure path ends in a slash
*
* @param string directory path
* @returns string directory path wiht trailing slash
*/
function _slashify($path)
{
if ($path[strlen($path)-1] != '/') {
$path = $path."/";
}
return $path;
}
/**
* Unslashify - make sure path doesn't in a slash
*
* @param string directory path
* @returns string directory path wihtout trailing slash
*/
function _unslashify($path)
{
if ($path[strlen($path)-1] == '/') {
$path = substr($path, 0, strlen($path) -1);
}
return $path;
}
/**
* Merge two paths, make sure there is exactly one slash between them
*
* @param string parent path
* @param string child path
* @return string merged path
*/
function _mergePaths($parent, $child)
{
if ($child{0} == '/') {
return $this->_unslashify($parent).$child;
} else {
return $this->_slashify($parent).$child;
}
}
/**
* mbstring.func_overload save strlen version: counting the bytes not the chars
*
* @param string $str
* @return int
*/
function bytes($str)
{
static $func_overload;
if (is_null($func_overload))
{
$func_overload = @extension_loaded('mbstring') ? ini_get('mbstring.func_overload') : 0;
}
return $func_overload & 2 ? mb_strlen($str,'ascii') : strlen($str);
}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
*/
?>
HTTP_WebDAV_Server-1.0.0RC8/db/ 0000755 0001750 0001750 00000000000 12671653173 015176 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/db/Fileserver.sql 0000644 0001750 0001750 00000002127 12041241525 020010 0 ustar taffit taffit -- MySQL dump 9.06
--
-- Host: localhost Database: webdav
---------------------------------------------------------
-- Server version 4.0.3-beta
CREATE DATABASE webdav;
USE webdav;
--
-- Table structure for table 'locks'
--
CREATE TABLE locks (
token varchar(255) NOT NULL default '',
path varchar(200) NOT NULL default '',
expires int(11) NOT NULL default '0',
owner varchar(200) default NULL,
recursive int(11) default '0',
writelock int(11) default '0',
exclusivelock int(11) NOT NULL default 0,
created bigint(20) default 0,
modified bigint(20) default 0,
PRIMARY KEY (token),
UNIQUE KEY token (token),
KEY path (path),
KEY path_2 (path),
KEY path_3 (path,token),
KEY expires (expires)
) TYPE=MyISAM;
--
-- Dumping data for table 'locks'
--
--
-- Table structure for table 'properties'
--
CREATE TABLE properties (
path varchar(255) NOT NULL default '',
name varchar(120) NOT NULL default '',
ns varchar(120) NOT NULL default 'DAV:',
value text,
PRIMARY KEY (path,name,ns),
KEY path (path)
) TYPE=MyISAM;
--
-- Dumping data for table 'properties'
--
HTTP_WebDAV_Server-1.0.0RC8/AUTHORS 0000644 0001750 0001750 00000000142 12041241525 015637 0 ustar taffit taffit Authors: Hartmut Holzgraefe
Christian Stocker
HTTP_WebDAV_Server-1.0.0RC8/Tools/ 0000755 0001750 0001750 00000000000 12671653173 015711 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/Tools/_parse_proppatch.php 0000644 0001750 0001750 00000014713 12041241525 021742 0 ustar taffit taffit
* @version @package-version@
*/
class _parse_proppatch
{
/**
*
*
* @var
* @access
*/
var $success;
/**
*
*
* @var
* @access
*/
var $props;
/**
*
*
* @var
* @access
*/
var $depth;
/**
*
*
* @var
* @access
*/
var $mode;
/**
*
*
* @var
* @access
*/
var $current;
/**
* constructor
*
* @param string path of input stream
* @access public
*/
function _parse_proppatch($path)
{
$this->success = true;
$this->depth = 0;
$this->props = array();
$had_input = false;
$f_in = fopen($path, "r");
if (!$f_in) {
$this->success = false;
return;
}
$xml_parser = xml_parser_create_ns("UTF-8", " ");
xml_set_element_handler($xml_parser,
array(&$this, "_startElement"),
array(&$this, "_endElement"));
xml_set_character_data_handler($xml_parser,
array(&$this, "_data"));
xml_parser_set_option($xml_parser,
XML_OPTION_CASE_FOLDING, false);
while($this->success && !feof($f_in)) {
$line = fgets($f_in);
if (is_string($line)) {
$had_input = true;
$this->success &= xml_parse($xml_parser, $line, false);
}
}
if($had_input) {
$this->success &= xml_parse($xml_parser, "", true);
}
xml_parser_free($xml_parser);
fclose($f_in);
}
/**
* tag start handler
*
* @param resource parser
* @param string tag name
* @param array tag attributes
* @return void
* @access private
*/
function _startElement($parser, $name, $attrs)
{
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
if ($ns == "")
$this->success = false;
} else {
$ns = "";
$tag = $name;
}
if ($this->depth == 1) {
$this->mode = $tag;
}
if ($this->depth == 3) {
$prop = array("name" => $tag);
$this->current = array("name" => $tag, "ns" => $ns, "status"=> 200);
if ($this->mode == "set") {
$this->current["val"] = ""; // default set val
}
}
if ($this->depth >= 4) {
$this->current["val"] .= "<$tag";
if (isset($attr)) {
foreach ($attr as $key => $val) {
$this->current["val"] .= ' '.$key.'="'.str_replace('"','"', $val).'"';
}
}
$this->current["val"] .= ">";
}
$this->depth++;
}
/**
* tag end handler
*
* @param resource parser
* @param string tag name
* @return void
* @access private
*/
function _endElement($parser, $name)
{
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
if ($ns == "")
$this->success = false;
} else {
$ns = "";
$tag = $name;
}
$this->depth--;
if ($this->depth >= 4) {
$this->current["val"] .= "$tag>";
}
if ($this->depth == 3) {
if (isset($this->current)) {
$this->props[] = $this->current;
unset($this->current);
}
}
}
/**
* input data handler
*
* @param resource parser
* @param string data
* @return void
* @access private
*/
function _data($parser, $data)
{
if (isset($this->current)) {
$this->current["val"] .= $data;
}
}
}
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode:nil
* End:
*/
HTTP_WebDAV_Server-1.0.0RC8/Tools/_parse_propfind.php 0000644 0001750 0001750 00000013456 12041241525 021566 0 ustar taffit taffit
* @version @package-version@
*/
class _parse_propfind
{
/**
* success state flag
*
* @var bool
* @access public
*/
var $success = false;
/**
* found properties are collected here
*
* @var array
* @access public
*/
var $props = false;
/**
* internal tag nesting depth counter
*
* @var int
* @access private
*/
var $depth = 0;
/**
* constructor
*
* @access public
*/
function _parse_propfind($path)
{
// success state flag
$this->success = true;
// property storage array
$this->props = array();
// internal tag depth counter
$this->depth = 0;
// remember if any input was parsed
$had_input = false;
// open input stream
$f_in = fopen($path, "r");
if (!$f_in) {
$this->success = false;
return;
}
// create XML parser
$xml_parser = xml_parser_create_ns("UTF-8", " ");
// set tag and data handlers
xml_set_element_handler($xml_parser,
array(&$this, "_startElement"),
array(&$this, "_endElement"));
// we want a case sensitive parser
xml_parser_set_option($xml_parser,
XML_OPTION_CASE_FOLDING, false);
// parse input
while ($this->success && !feof($f_in)) {
$line = fgets($f_in);
if (is_string($line)) {
$had_input = true;
$this->success &= xml_parse($xml_parser, $line, false);
}
}
// finish parsing
if ($had_input) {
$this->success &= xml_parse($xml_parser, "", true);
}
// free parser
xml_parser_free($xml_parser);
// close input stream
fclose($f_in);
// if no input was parsed it was a request
if(!count($this->props)) $this->props = "all"; // default
}
/**
* start tag handler
*
* @access private
* @param resource parser
* @param string tag name
* @param array tag attributes
*/
function _startElement($parser, $name, $attrs)
{
// name space handling
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
if ($ns == "")
$this->success = false;
} else {
$ns = "";
$tag = $name;
}
// special tags at level 1: and
if ($this->depth == 1) {
if ($tag == "allprop")
$this->props = "all";
if ($tag == "propname")
$this->props = "names";
}
// requested properties are found at level 2
if ($this->depth == 2) {
$prop = array("name" => $tag);
if ($ns)
$prop["xmlns"] = $ns;
$this->props[] = $prop;
}
// increment depth count
$this->depth++;
}
/**
* end tag handler
*
* @access private
* @param resource parser
* @param string tag name
*/
function _endElement($parser, $name)
{
// here we only need to decrement the depth count
$this->depth--;
}
}
?>
HTTP_WebDAV_Server-1.0.0RC8/Tools/_parse_lockinfo.php 0000644 0001750 0001750 00000016606 12041241525 021551 0 ustar taffit taffit
* @version @package-version@
*/
class _parse_lockinfo
{
/**
* success state flag
*
* @var bool
* @access public
*/
var $success = false;
/**
* lock type, currently only "write"
*
* @var string
* @access public
*/
var $locktype = "";
/**
* lock scope, "shared" or "exclusive"
*
* @var string
* @access public
*/
var $lockscope = "";
/**
* lock owner information
*
* @var string
* @access public
*/
var $owner = "";
/**
* flag that is set during lock owner read
*
* @var bool
* @access private
*/
var $collect_owner = false;
/**
* constructor
*
* @param string path of stream to read
* @access public
*/
function _parse_lockinfo($path)
{
// we assume success unless problems occur
$this->success = true;
// remember if any input was parsed
$had_input = false;
// open stream
$f_in = fopen($path, "r");
if (!$f_in) {
$this->success = false;
return;
}
// create namespace aware parser
$xml_parser = xml_parser_create_ns("UTF-8", " ");
// set tag and data handlers
xml_set_element_handler($xml_parser,
array(&$this, "_startElement"),
array(&$this, "_endElement"));
xml_set_character_data_handler($xml_parser,
array(&$this, "_data"));
// we want a case sensitive parser
xml_parser_set_option($xml_parser,
XML_OPTION_CASE_FOLDING, false);
// parse input
while ($this->success && !feof($f_in)) {
$line = fgets($f_in);
if (is_string($line)) {
$had_input = true;
$this->success &= xml_parse($xml_parser, $line, false);
}
}
// finish parsing
if ($had_input) {
$this->success &= xml_parse($xml_parser, "", true);
}
// check if required tags where found
$this->success &= !empty($this->locktype);
$this->success &= !empty($this->lockscope);
// free parser resource
xml_parser_free($xml_parser);
// close input stream
fclose($f_in);
}
/**
* tag start handler
*
* @param resource parser
* @param string tag name
* @param array tag attributes
* @return void
* @access private
*/
function _startElement($parser, $name, $attrs)
{
// namespace handling
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
} else {
$ns = "";
$tag = $name;
}
if ($this->collect_owner) {
// everything within the tag needs to be collected
$ns_short = "";
$ns_attr = "";
if ($ns) {
if ($ns == "DAV:") {
$ns_short = "D:";
} else {
$ns_attr = " xmlns='$ns'";
}
}
$this->owner .= "<$ns_short$tag$ns_attr>";
} else if ($ns == "DAV:") {
// parse only the essential tags
switch ($tag) {
case "write":
$this->locktype = $tag;
break;
case "exclusive":
case "shared":
$this->lockscope = $tag;
break;
case "owner":
$this->collect_owner = true;
break;
}
}
}
/**
* data handler
*
* @param resource parser
* @param string data
* @return void
* @access private
*/
function _data($parser, $data)
{
// only the tag has data content
if ($this->collect_owner) {
$this->owner .= $data;
}
}
/**
* tag end handler
*
* @param resource parser
* @param string tag name
* @return void
* @access private
*/
function _endElement($parser, $name)
{
// namespace handling
if (strstr($name, " ")) {
list($ns, $tag) = explode(" ", $name);
} else {
$ns = "";
$tag = $name;
}
// finished?
if (($ns == "DAV:") && ($tag == "owner")) {
$this->collect_owner = false;
}
// within we have to collect everything
if ($this->collect_owner) {
$ns_short = "";
$ns_attr = "";
if ($ns) {
if ($ns == "DAV:") {
$ns_short = "D:";
} else {
$ns_attr = " xmlns='$ns'";
}
}
$this->owner .= "$ns_short$tag$ns_attr>";
}
}
}
?>
HTTP_WebDAV_Server-1.0.0RC8/LICENSE 0000644 0001750 0001750 00000002506 12041241525 015602 0 ustar taffit taffit 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, th
is 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 name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WA
RRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABIL
ITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR C
ONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOW
EVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILI
TY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE U
SE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. HTTP_WebDAV_Server-1.0.0RC8/dav.txt 0000644 0001750 0001750 00000017777 12041241525 016130 0 ustar taffit taffit The HTTP_WebDAV_Server class provides a framwork for the
implementation of customized WebDAV servers that can provide
filesystem like access to almost any kind of hierachically stored
data.
The (abstract) server base class tries to encapsulate as much of
the protocol details as possible. It takes care of the needed WebDAV
header and XML payload parsing and generation (and knows about some
of the problems with common clients and tries hard to work around
them).
WebDAV itself is an extension to the HTTP protocol. The HTTP
specific parts of it are already taken care of by the web server. Any
data needed by the server class is provided by the PHP SAPI interface
of the server used.
To create a working server from the base class you have to extend and
add methods for the actual access, modification and access control of
your own data.
You may use the included HTTP_WebDAV_Server_Filesystem class as an
example of how to create a working server. This sample implementation
is used for testing the implementation of this package against the
litmus WebDAV compliance test suite.
(litmus is available on http://www.webdav.org/neon/litmus)
The methods you can add in your extended class are mostly named after
the WebDAV specific request methods (using upper case names). Methods
you may implement are:
* GET() get a resource from the server
* HEAD() get resource headers only from the server
* PUT() create or modify a resource on the server
* COPY() copy a resource on the server
* MOVE() move a resource on the server
* DELETE() delete a resource on the server
* MKCOL() create a new collection
* PROPFIND() get property data for a resource
* PROPPATCH() modify property data for a resource
* LOCK() lock a resource
* UNLOCK() unlock a locked resource
* checklock() check whether a resource is locked
* check_auth() check authentication
You can think of WebDAV resources as files, collections as directories
and properties as filesystem meta data (like size, creation date, ...).
The base class is able identify which of the methods you have
implemented and will create appropriate answers to OPTIONS requests
that ask for the WebDAV standards compliance level and the allowed
HTTP methods for you.
For a minimal working test server you need to implement GET(), PUT()
and PROPFIND() only.
For a minimal (level 1) standards compliant server you also need to
implement MKCOL(), DELETE(), and PROPPATCH(). The COPY(), MOVE() and
HEAD() methods are emulated using GET(), PUT() and DELETE() if not
implemented, but for performance reasons you should better implement
them yourself.
For a complete (level 2) RFC2518 compliand server you also have to
provide locking support by implementing LOCK(), UNLOCK() and
checklock().
Authentication is not really part of the WebDAV specification and
should be handled on the HTTP level. You can do so by means of, for
example, .htaccess files or similar services provided by your web
server. But you can also make use of the authentication features
offered by PHP by implementing the check_auth() method.
Using the check_auth() method you can create a dynamic interface
to any authentication system protecting the data you want to serve.
the following reference information may be outdated and/or
incomplete ...
bool PROPINFO($options, &$files)
options[path] - Resource-Path
options[depth] - Depth of search requested: "0", "1", or "infinity"
options[props] - "all", "names", or an arry of requested properties
each property array element is either a string
(which implies the default "DAV:" namespace) or
an array with the two elements "name" and "xmlns"
for the properties name and XML namespace
&$files - storage array for property results with the following elements:
"files" -> array of found properties forresources. elements are:
"path" -> path of the resource
"props" -> properties array
each property array element is either a string
(which implies the default "DAV:" namespace) or
an array with the two elements "name" and "xmlns"
for the properties name and XML namespace
you should at least support the following
list of properties from the "DAV:" namespave:
- resourcetype: "collection" oder ""
- creationdate: unix-timestamp
- getcontentlength: integer
- getlastmodified: unix-timestamp
You may want to add support for these "DAV:"
properties, too:
- getcontenttype: mime-type
- displayname: string
for a compliant server you also have to be
able to return any property from other
namespaces that has been stored using
PROPPATCH
return-value: true / false
string MKCOL($option)
options[path] - path of the new collection to be created
return-value: string
HTTP status and status message, possible values are
* 201 Success
* 403 Forbidden
* 405 Method not allowed
* 409 Conflict
* 415 Unsupported media type
* 507 Insufficient Storage
(see also RFC2518 8.3.2)
string GET(&$options)
$options['path'] - path to the requested resource
$options['ranges'] - optional array of range specifications for
partial access. range specs are arrays that
consist of either a 'start' and 'end' element
(where 'end' can be empty to indicate a request
up to the actual end of the resource) or a
'last' element to access the last n bytes of
a resource without knowing its actual size in
advance
Return-value: true bei Erfolg, false wenn not found
(TODO: andere stati berücksichtigen)
Content-Type, Content-Length header müssen von der Methode selbst
erzeugt werden (TODO: outdated)
string PUT($options)
options[path] - path to the requested resource
options[content_length] - size of request data in bytes
options[stream] - a PHP stream providing the input data
return-value: string
HTTP status, possible values are:
* 201 Created -> the resource did not exist before
and has been successfully created
* 204 No Content -> a previously existing resource has
successfully been modified
* 409 Conflict
...
string COPY($options)
options[path] - path to the resource to be copied
options[depth] - "0" or "infinity" (applies only to directories)
options[overwrite] - true / false
options[dest] - path to the destination resource if local
options[dest_url] - non-local destination path
return-value: string
HTTP status, see RFC2518 8.8.5
string MOVE($options)
options[path] - path to the resource to be moved
options[overwrite] - true / false
options[dest] - path to the destination resource if local
options[dest_url] - non-local destination path
return-value: string
HTTP status, see RFC2518 8.9.4
string DELETE($options)
options[path] - path to the resource to be removed
return-value: string
HTTP status, see RFC2518 8.6.2
bool check_auth($type, $user, $passwd)
$type: HTTP-Auth type, i.A. "Basic"
$user: HTTP Username
$passwd: HTTP Passwort
return-value: true bei success, sonst false
(ToDo: array mit Auth-Type und Realm String zulassen bei fehler) HTTP_WebDAV_Server-1.0.0RC8/EXPERIMENTAL 0000644 0001750 0001750 00000000457 12041241525 016420 0 ustar taffit taffit THE HTTP_WebDAV_Server CLASSES ARE EXPERIMENTAL!
the functionalities they provide may (and most likely will)
change names or the way they work
or even cease to exists in future releases
and they may not perform as expected
or might even not work at all
use it at your own risk
YOU HAVE BEEN WARNED!
HTTP_WebDAV_Server-1.0.0RC8/tests/ 0000755 0001750 0001750 00000000000 12671653173 015753 5 ustar taffit taffit HTTP_WebDAV_Server-1.0.0RC8/tests/test.sh 0000755 0001750 0001750 00000000537 12041241525 017257 0 ustar taffit taffit #!/bin/bash
(cd ..; sudo pear install -f package.xml)
rm -f *.log
mysql -u root webdav -e "TRUNCATE TABLE locks"
mysql -u root webdav -e "TRUNCATE TABLE properties"
sudo rm -rf /usr/local/apache/htdocs/mod_dav/*
sudo rm -rf /usr/local/apache/htdocs/litmus/*
litmus -k http://localhost/file.php # add -k to continue on errors
php -q split_log.php
HTTP_WebDAV_Server-1.0.0RC8/tests/split_log.php 0000644 0001750 0001750 00000000645 12041241525 020446 0 ustar taffit taffit
HTTP_WebDAV_Server-1.0.0RC8/TODO 0000644 0001750 0001750 00000002005 12041241525 015257 0 ustar taffit taffit Short term (1.0 show stoppers):
- documentation
- check url en-/decodings (all pending bug reports are about this)
Medium term (nice to have in 1.1):
- the following items affect the fileserver example only,
these are not problems of the base class itself
- "Depth: Infinite" not supported yet (in PROPFIND)
- properties not always copied/moved (in COPY/MOVE)
- no recursove locks (in LOCK, UNLOCK etc.)
- API cleanups
- ETags support ...
- MIME-type detection should become a
package of its own?
- mimetype may be guessed for stream
(mime_magic extension now supports this in HEAD)
- provide helper classes that extend the
Server class by adding prepared LOCKing
functionality, eg.:
- HTTP_WebDAV_Server_Lock_MySQL
- HTTP_WebDAV_Server_Lock_dbm
- HTTP_WebDAV_Server_Lock_PearDB
- ...
(have to dig out my design pattern book ...?)
Long term (definetly not in 1.0 or even 1.x):
- the XML parsing parts are *very* Q&D
change to simpleXML and DOM (requires PHP5)
- Versioning
- DASL
- ...