'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; } ?>