'audio/x-aiff',
'aiff' => 'audio/x-aiff',
'asc' => 'text/plain',
'atom' => 'text/plain',
'au' => 'audio/basic',
'avi' => 'video/x-msvideo',
'bmp' => 'image/bmp',
'c' => 'text/plain',
'cc' => 'text/plain',
'cgi' => 'text/plain',
'cpp' => 'text/plain',
'css' => 'text/css',
'cxx' => 'text/plain',
'doc' => 'application/msword',
'dv' => 'video/x-dv',
'eps' => 'application/postscript',
'gif' => 'image/gif',
'gz' => 'application/x-gzip',
'h' => 'text/plain',
'hpp' => 'text/plain',
'hqx' => 'application/mac-binhex40',
'htm' => 'text/html',
'html' => 'text/html',
'hxx' => 'text/plain',
'jar' => 'application/java-archive',
'jav' => 'text/plain',
'java' => 'text/plain',
'jpeg' => 'image/jpeg',
'jpg' => 'image/jpeg',
'js' => 'text/plain',
'lzh' => 'application/x-lzh',
'm' => 'text/plain',
'm4a' => 'audio/mp4a-latm',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'mm' => 'text/plain',
'mov' => 'video/quicktime',
'mp2' => 'audio/mpeg',
'mp3' => 'audio/mpeg',
'mp4' => 'video/mp4',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'ogg' => 'application/ogg',
'pdf' => 'application/pdf',
'php' => 'text/plain',
'pict' => 'image/pict',
'pl' => 'text/plain',
'png' => 'image/png',
'ppt' => 'application/vnd.ms-powerpoint',
'ps' => 'application/postscript',
'py' => 'text/plain',
'rb' => 'text/plain',
'rdf' => 'text/plain',
'rm' => 'audio/x-pn-realaudio',
'rtf' => 'text/rtf',
'sh' => 'text/plain',
'shtml' => 'text/html',
'snd' => 'audio/basic',
'svg' => 'image/svg+xml',
'swf' => 'application/x-shockwave-flash',
'tar' => 'application/x-tar',
'tex' => 'application/x-tex',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'txt' => 'text/plain',
'vrml' => 'model/vrml',
'wav' => 'audio/x-wav',
'wbmp' => 'image/vnd.wap.wbmp',
'wrl' => 'model/vrml',
'xbm' => 'image/x-xbitmap',
'xhtml' => 'text/html',
'xls' => 'application/vnd.ms-excel',
'xml' => 'text/xml',
'xpm' => 'image/x-xpixmap',
'xsl' => 'text/xsl',
'zip' => 'application/zip'
);
$extension = substr (strrchr ($path, '.'), 1);
$mime = $mime_types[$extension];
return $mime ? $mime : 'application/octet-stream';
}
# PROPFIND is recursive in nature, so it gets its own function. Since we
# assume "allprop" is set, it's not especially complicated: just stat() a file
# and format the results as XML. (Granted, the XML formatting is kinda silly,
# but you can blame the WebDAV folks for that.)
function propfind ($root, $path, $depth) {
$href = str_replace (array ('%2F', '+'),
array ('/', '%20'),
urlencode ($path));
$file = $root . $path;
$exists = file_exists ($file);
$dir = NULL;
$stat = NULL;
if ($href === '')
$href = '/';
if ($exists) {
$dir = is_dir ($file);
$stat = stat ($file);
}
echo ('');
echo ("$href");
echo ('');
# File not found.
if (!$exists)
echo ('HTTP/1.1 404 File Not Found');
# If we can't stat the file, it's probably a permissions issue. (I use a
# 403 and not a 401 because the client can never recover from the error--
# it's based on the server's permissions, not the client's.)
else if (!$stat)
echo ('HTTP/1.1 403 Forbidden');
else {
echo ('HTTP/1.1 200 OK');
echo ('');
$name = htmlspecialchars (basename ($file));
$created = gmdate ('c', $stat['ctime']);
$modified = gmdate ('c', $stat['mtime']);
# Display various general properties.
echo ("$name");
echo ("$created");
echo ("$modified");
echo ('');
# If it's a directory, say so.
if ($dir)
echo ('');
# Otherwise, print out statistics that only make sense on files.
else {
$size = $stat['size'];
$mime = mime_type ($path);
$etag = "{$stat['dev']}-{$stat['ino']}-{$stat['mtime']}";
echo ('');
echo ("$size");
echo ("$mime");
echo ("$etag");
}
echo ('');
}
echo ('');
echo ('');
# If this is a directory and we're set to recurse, then also print out
# PROPFIND responses for all of this directory's children.
if ($dir && $depth > 0)
foreach (scandir ($file) as $child)
if ($child != '.' && $child != '..')
propfind ($root, "$path/$child", $depth - 1);
}
# Response handling begins here. (Determine what method is being called, and
# respond appropriately.)
$request_method = $_ENV['REQUEST_METHOD'];
switch ($request_method) {
# PROPFIND supports a truly staggering amount of options and flags to limit
# or define the various pieces of data you're interested in retrieving. We
# pretend that the client has always specified "allprop" (that is, complete
# information about everything), and make it the client's responsibility to
# pull out less information if so desired.
case 'PROPFIND':
# Figure out what file they're looking at.
$document_root = $_ENV['DOCUMENT_ROOT'];
$document_uri = urldecode (rtrim ($_ENV['DOCUMENT_URI'], '/'));
# Figure out what depth to recurse to. Valid values are 0, 1, and infinity.
$depth = validate ($_ENV['DEPTH'],
'infinity',
array ('0' => 0, '1' => 1, 'infinity' => 'infinity'));
# Choke if they specify an invalid depth.
if (is_null ($depth))
header ('HTTP/1.1 400 Bad Request');
# "allprop" with an infinite depth is a scary proposition. Supporting it is
# both optional and stupid, so I don't.
else if ($depth === 'infinity') {
header ('HTTP/1.1 403 Forbidden');
header ('Content-Type: text/xml');
echo ('');
echo ('');
echo ('');
}
# Otherwise, give them the requested information.
else {
header ('HTTP/1.1 207 Multi-Status');
header ('Content-Type: text/xml');
echo ('');
propfind ($document_root, $document_uri, $depth);
echo ('');
}
break;
# We handle COPY and MOVE together, since they're almost identical. COPY
# should support the Depth header, which modifies the semantics of the copy.
# We ignore it and assume an infinite depth. Additionally, we do not support
# copies between servers--all copies must be local. Finally, we don't
# properly check for disk space errors, but a generic 500 should be good
# enough.
case 'COPY':
case 'MOVE':
# Figure out what files they're looking at.
$host = $_ENV['HOST'];
$document_root = $_ENV['DOCUMENT_ROOT'];
$document_uri = urldecode (rtrim ($_ENV['DOCUMENT_URI'], '/'));
$destination = NULL;
$destination_host = NULL;
$url = parse_url ($_ENV['DESTINATION']);
if ($url) {
$destination = urldecode (rtrim ($url['path'], '/'));
$destination_host = $url['host'];
}
$source_exists = file_exists ($document_root . $document_uri);
$destination_exists = file_exists ($document_root . $destination);
# Do we overwrite the destination file?
$overwrite = validate ($ENV_['OVERWRITE'],
'T',
array ('T' => true, 't' => true,
'F' => false, 'f' => false));
# Choke if they specify an invalid destination or depth.
if (is_null ($destination))
header ('HTTP/1.1 400 Bad Request');
# Disallow copying/moving to a remote host.
else if ($host != $destination_host)
header ('HTTP/1.1 502 Bad Gateway');
# Fail if the source doesn't exist.
else if (!$source_exists)
header ('HTTP/1.1 404 File Not Found');
# Disallow copying/moving a file to itself.
else if ($document_uri == $destination)
header ('HTTP/1.1 403 Forbidden');
# Fail if the destination file exists and they said they didn't want to
# overwrite it.
else if ($overwrite == false && $destination_exists)
header ('HTTP/1.1 412 Precondition Failed');
# If we're doing a copy, copy the files.
# FIXME: We resort to shell since PHP doesn't support a recursive copy and
# I didn't want to bother implementing it (though it would be a good idea
# to do so at some point).
else if ($request_method == 'COPY' &&
!recursive_copy ($document_root . $document_uri,
$document_root . $destination))
header ('500 Internal Server Error');
# If we're doing a move, move the files.
else if ($request_method == 'MOVE' &&
!rename ($document_root . $document_uri,
$document_root . $destination))
header ('500 Internal Server Error');
else
header ($destination_exists ?
'HTTP/1.1 204 No Content' :
'HTTP/1.1 201 Created');
break;
# In case they ask, pretend that we actually know what we're talking about.
# (The only methods conspicuously absent are PROPPATCH, LOCK, and UNLOCK. We
# tell the clients that we don't support them.)
case 'OPTIONS':
header ('HTTP/1.1 200 OK');
header ('Allow: OPTIONS, GET, HEAD, POST, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND');
header ('DAV: 1, 2');
break;
# The following methods are valid but unimplemented. In theory, NGINX is
# supposed to handle each of them anyway.
case 'GET':
case 'HEAD':
case 'POST':
case 'PUT':
case 'DELETE':
case 'MKCOL':
header ('HTTP/1.1 501 Not Implemented');
break;
# Any other methods are unknown.
default:
header ('HTTP/1.1 400 Bad Request');
break;
}
?>