--- a/webserver.php Fri Apr 25 14:56:52 2008 -0400
+++ b/webserver.php Mon Jun 30 12:36:13 2008 -0400
@@ -21,6 +21,14 @@
define('HTTPD_VERSION', '0.1b1');
/**
+ * Webserver system icons
+ */
+
+define('HTTPD_ICON_SCRIPT', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGSSURBVCjPVVFNSwJhEF78Ad79Cf6PvXQRsotUlzKICosuRYmR2RJR0KE6lBFFZVEbpFBSqKu2rum6llFS9HHI4iUhT153n6ZtIWMOM+/MM88z7wwH7s9Ub16SJcnbmrNcxVm2q7Z8/QPvEOtntpj92NkCqITLepEpjix7xQtiLOoQ2b6+E7YAN/5nfOEJ2WbKqOIOJ4bYVMEQx4LfBBQDsvFMhUcCVU1/CxVXmDBGA5ZETrhDCQVcYAPbyEJBhvrnBVPiSpNr6cYDNCQwo4zzU/ySckkgDYuNuVpI42T9k4gLKGMPs/xPzzovQiY2hQYe0jlJfyNNhTqiWDYBq/wBMcSRpnyPzu1oS7WtxjVBSthU1vgVksiQ3Dn6Gp5ah2YOKQo5GiuHPA6xT1EKpxQNCNYejgIR457KKio0S56YckjSa9jo//3mrj+BV0QQagqGTOo+Y7gZIf1puP3WHoLhEb2PjTlCTCWGXtbp8DCX3hZuOdaIc9A+aQvWk4ihq95p67a7nP+u+Ws+r0dql9z/zv0NCYhdCPKZ7oYAAAAASUVORK5CYII=');
+define('HTTPD_ICON_FOLDER', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC');
+define('HTTPD_ICON_FILE', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAINSURBVBgZBcG/r55zGAfg6/4+z3va01NHlYgzEfE7MdCIGISFgS4Gk8ViYyM2Mdlsko4GSf8Do0FLRCIkghhYJA3aVBtEz3nP89wf11VJvPDepdd390+8Nso5nESBQoq0pfvXm9fzWf19453LF85vASqJlz748vInb517dIw6EyYBIIG49u+xi9/c9MdvR//99MPPZ7+4cP4IZhhTPbwzT2d+vGoaVRRp1rRliVvHq+cfvM3TD82+7mun0o/ceO7NT+/4/KOXjwZU1ekk0840bAZzMQ2mooqh0A72d5x/6sB9D5zYnff3PoYBoWBgFKPKqDKqjCpjKr//dcu9p489dra88cydps30KswACfNEKanSaxhlntjJ8Mv12Paie+vZ+0+oeSwwQ0Iw1xAR1CiFNJkGO4wu3ZMY1AAzBI0qSgmCNJsJUEOtJSMaCTBDLyQ0CknAGOgyTyFFiLI2awMzdEcSQgSAAKVUmAeNkxvWJWCGtVlDmgYQ0GFtgg4pNtOwbBcwQy/Rife/2yrRRVI0qYCEBly8Z+P4qMEMy7JaVw72N568e+iwhrXoECQkfH91kY7jwwXMsBx1L93ZruqrK6uuiAIdSnTIKKPLPFcvay8ww/Hh+ufeznTXu49v95IMoQG3784gYXdTqvRmqn/Wpa/ADFX58MW3L71SVU9ETgEIQQQIOOzub+fhIvwPRDgeVjWDahIAAAAASUVORK5CYII=');
+
+/**
* Simple web server written in PHP.
* @package Amarok
* @subpackage WebControl
@@ -60,6 +68,13 @@
var $default_document = false;
/**
+ * List of filenames or handlers used when a directory listing is requested
+ * @var array
+ */
+
+ var $directory_index = array('index.html', 'index.htm', 'index', 'default.html', 'default.htm');
+
+ /**
* HTTP response code set by the handler function
* @var int
*/
@@ -110,12 +125,42 @@
var $in_keepalive = false;
/**
+ * UUID for this server instance
+ * @var string
+ */
+
+ var $uuid = '00000000-0000-0000-0000-000000000000';
+
+ /**
+ * Switch to track whether a scriptlet is running. If it is, send_http_error() does more than normal.
+ * @var bool
+ */
+
+ var $in_scriptlet = false;
+
+ /**
+ * Switch to track whether headers have been sent or not.
+ * @var bool
+ */
+
+ var $headers_sent = false;
+
+ /**
+ * Switch to track if the socket is bound and thus needs to be freed or not
+ * @var bool
+ */
+
+ var $socket_initted = false;
+
+ /**
* Constructor.
* @param string IPv4 address to bind to
* @param int Port number
+ * @param int If port is under 1024, specify a user ID/name to switch to here
+ * @param int If port is under 1024, specify a group ID/name to switch to here
*/
- function __construct($address = '127.0.0.1', $port = 8080)
+ function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null)
{
@set_time_limit(0);
@ini_set('memory_limit', '256M');
@@ -126,17 +171,90 @@
burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
}
- $this->sock = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
+ // make sure we're not running as root
+ // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound
+ $allow_root = ( $port < 1024 ) ? true : false;
+ if ( function_exists('posix_geteuid') )
+ {
+ $euid = posix_geteuid();
+ $egid = posix_getegid();
+ $username = posix_getpwuid($euid);
+ $username = $username['name'];
+ $group = posix_getgrgid($egid);
+ $group = $group['name'];
+ if ( $euid == 0 && !$allow_root )
+ {
+ // running as root but not on a privileged port - die for security
+ burnout("Running as superuser (user \"$username\" and group \"$group\"). This is not allowed for security reasons.");
+ }
+ else if ( $euid == 0 && $allow_root )
+ {
+ // running as root and port below 1024, so notify of the switch and verify that a target UID and GID were passed
+ if ( $targetuser === null || $targetgroup === null )
+ {
+ // no target user/group specified
+ burnout("Must specify a target user and group when running server as root");
+ }
+ // get info about target user/group
+ if ( is_string($targetuser) )
+ {
+ $targetuser = posix_getpwnam($targetuser);
+ $targetuser = $targetuser['uid'];
+ }
+ if ( is_string($targetgroup) )
+ {
+ $targetgroup = posix_getgrnam($targetgroup);
+ $targetgroup = $targetgroup['uid'];
+ }
+ // make sure all info is valid
+ if ( !is_int($targetuser) || !is_int($targetgroup) )
+ {
+ burnout('Invalid user or group specified');
+ }
+ $userinfo = posix_getpwuid($targetuser);
+ $groupinfo = posix_getgrgid($targetgroup);
+ if ( function_exists('status') )
+ status("Will switch to user \"{$userinfo['name']}\" and group \"{$groupinfo['name']}\" shortly after binding to socket");
+ }
+ else if ( $allow_root && $euid > 0 )
+ {
+ burnout("Must be superuser to bind to ports below 1024");
+ }
+ }
+ $socket_do_root = ( $allow_root ) ? function_exists('posix_geteuid') : false;
+
+ $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
if ( !$this->sock )
throw new Exception('Could not create socket');
- $result = socket_bind($this->sock, $address, $port);
+ $result = @socket_bind($this->sock, $address, $port);
if ( !$result )
throw new Exception("Could not bind to $address:$port");
- $result = socket_listen($this->sock, SOMAXCONN);
+ $this->socket_initted = true;
+ $result = @socket_listen($this->sock, SOMAXCONN);
if ( !$result )
throw new Exception("Could not listen for connections $address:$port");
+
+ // if running as root and we made it here, switch credentials
+ if ( $socket_do_root )
+ {
+ posix_setuid($targetuser);
+ posix_setgid($targetgroup);
+ posix_setegid($targetgroup);
+ posix_seteuid($targetuser);
+ if ( function_exists('status') )
+ status('Successfully switched user ID');
+ }
+
$this->bind_address = $address;
$this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
+
+ // create a UUID
+ $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
+ $this->uuid = substr($uuid_base, 0, 8) . '-' .
+ substr($uuid_base, 8, 4) . '-' .
+ substr($uuid_base, 12, 4) . '-' .
+ substr($uuid_base, 16, 4) . '-' .
+ substr($uuid_base, 20, 20);
}
/**
@@ -145,9 +263,15 @@
function __destruct()
{
- if ( !defined('HTTPD_WS_CHILD') )
+ if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted )
{
- status('WebServer: destroying socket');
+ if ( function_exists('status') )
+ status('WebServer: destroying socket');
+ // http://us3.php.net/manual/en/function.socket-bind.php
+ if ( !@socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1) )
+ {
+ echo socket_strerror(socket_last_error($sock)) . "\n";
+ }
@socket_shutdown($this->sock, 2);
@socket_close($this->sock);
}
@@ -164,7 +288,8 @@
// if this is a child process, we're finished - close up shop
if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
{
- status('Exiting child process');
+ if ( function_exists('status') )
+ status('Exiting child process');
@socket_shutdown($remote);
@socket_close($remote);
exit(0);
@@ -211,11 +336,14 @@
{
// this is the child
define('HTTPD_WS_CHILD', 1);
+ @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
socket_close($this->sock);
}
}
$this->in_keepalive = false;
+ $this->headers_sent = false;
+ $this->in_scriptlet = false;
// read request
$last_line = '';
@@ -261,6 +389,11 @@
$this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' );
}
+ // parse authorization, if any
+ if ( isset($_SERVER['PHP_AUTH_USER']) )
+ {
+ unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
+ }
if ( isset($_SERVER['HTTP_AUTHORIZATION']) )
{
$data = $_SERVER['HTTP_AUTHORIZATION'];
@@ -270,6 +403,7 @@
$_SERVER['PHP_AUTH_PW'] = substr(strstr($data, ':'), 1);
}
+ // anything on POST?
$postdata = '';
$_POST = array();
if ( $method == 'POST' )
@@ -303,8 +437,11 @@
$uri = substr($uri, 0, strpos($uri, '?'));
}
+ // set some server vars
$_SERVER['REQUEST_URI'] = '/' . rawurldecode($uri);
$_SERVER['REQUEST_METHOD'] = $method;
+
+ // get remote IP and port
socket_getpeername($remote, $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
$_GET = array();
@@ -319,31 +456,60 @@
}
}
+ // init handler
+ $handler = false;
+
if ( $uri == '' )
{
+ // user requested the root (/). If there's a default document, use that; else, see if we can do a directory listing
$uri = strval($this->default_document);
+ if ( !$this->default_document && $this->allow_dir_list )
+ {
+ // we can list directories and this was requested by the user, so list it out
+ $handler = array('type' => 'rootdir');
+ }
}
$uri_parts = explode('/', $uri);
+ // hook for the special UUID handler
+ if ( $uri_parts[0] === $this->uuid && !$handler )
+ {
+ $handler = array('type' => 'sysuuid');
+ }
+
// loop through URI parts, see if a handler is set
- $handler = false;
- for ( $i = count($uri_parts) - 1; $i >= 0; $i-- )
+ if ( !$handler )
{
- $handler_test = implode('/', $uri_parts);
- if ( isset($this->handlers[$handler_test]) )
+ for ( $i = count($uri_parts) - 1; $i >= 0; $i-- )
{
- $handler = $this->handlers[$handler_test];
- $handler['id'] = $handler_test;
- break;
+ $handler_test = implode('/', $uri_parts);
+ if ( isset($this->handlers[$handler_test]) )
+ {
+ $handler = $this->handlers[$handler_test];
+ $handler['id'] = $handler_test;
+ break;
+ }
+ unset($uri_parts[$i]);
}
- unset($uri_parts[$i]);
}
if ( !$handler )
{
- $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server.");
- continue;
+ // try to make a fakie
+ if ( $this->check_for_handler_children($uri) )
+ {
+ $handler = array(
+ 'type' => 'folder',
+ 'dir' => "/{$this->uuid}/__fakie",
+ 'id' => $uri
+ );
+ }
+ if ( !$handler )
+ {
+ $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server.");
+ continue;
+ }
}
$this->send_standard_response($remote, $handler, $uri, $params);
@@ -354,7 +520,8 @@
// status('Closing connection');
@socket_shutdown($remote);
@socket_close($remote);
- status('Exiting child process');
+ if ( function_exists('status') )
+ status('Exiting child process');
exit(0);
}
else if ( defined('HTTPD_WS_CHILD') )
@@ -363,6 +530,11 @@
// status('Continuing connection');
// @socket_write($remote, "\r\n\r\n");
}
+ else
+ {
+ @socket_shutdown($remote);
+ @socket_close($remote);
+ }
}
}
@@ -377,10 +549,18 @@
function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '')
{
global $http_responses;
+ if ( $this->headers_sent )
+ return false;
+
+ // this is reset after the request is completed (hopefully)
+ $this->headers_sent = true;
+
$reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
$_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)';
- status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}");
+
+ if ( function_exists('status') )
+ status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}");
$headers = str_replace("\r\n", "\n", $headers);
$headers = str_replace("\n", "\r\n", $headers);
@@ -408,7 +588,7 @@
{
switch ( $handler['type'] )
{
- case 'dir':
+ case 'folder':
// security
$uri = str_replace("\000", '', $_SERVER['REQUEST_URI']);
if ( preg_match('#(\.\./|\/\.\.)#', $uri) || strstr($uri, "\r") || strstr($uri, "\n") )
@@ -420,58 +600,55 @@
global $mime_types;
// trim handler id from uri
+ $uri_full = rtrim($uri, '/');
$uri = substr($uri, strlen($handler['id']) + 1);
// get file path
$file_path = rtrim($handler['dir'], '/') . $uri;
- if ( file_exists($file_path) )
+ if ( file_exists($file_path) || $this->check_for_handler_children($uri_full) )
{
// found it :-D
// is this a directory?
- if ( is_dir($file_path) )
+ if ( is_dir($file_path) || $this->check_for_handler_children($uri_full) )
{
+ // allowed to list?
if ( !$this->allow_dir_list )
{
$this->send_http_error($socket, 403, "Directory listing is not allowed.");
return true;
}
// yes, list contents
- $root = '/' . $handler['id'] . rtrim($uri, '/');
- $parent = substr($root, 0, strrpos($root, '/')) . '/';
-
+ try
+ {
+ $dir_list = $this->list_directory($uri_full, true);
+ }
+ catch ( Exception $e )
+ {
+ $this->send_http_error($socket, 500, "Directory listing failed due to an error in the listing core method. This may indicate that the webserver process does not have filesystem access to the specified directory.<br /><br />Debugging details:<pre>$e</pre>");
+ return true;
+ }
+
+ $root = rtrim($uri_full, '/') . '/';
+ $parent = rtrim(dirname(rtrim($uri_full, '/')), '/') . '/';
+
$contents = <<<EOF
<html>
<head>
<title>Index of: $root</title>
+ <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" />
</head>
<body>
<h1>Index of $root</h1>
<ul>
- <li><a href="$parent">Parent directory</a></li>
+ <li><tt><a href="$parent">Parent directory</a></tt></li>
EOF;
- $dirs = array();
- $files = array();
- $d = @opendir($file_path);
- while ( $dh = readdir($d) )
+
+ foreach ( $dir_list as $filename => $info )
{
- if ( $dh == '.' || $dh == '..' )
- continue;
- if ( is_dir("$file_path/$dh") )
- $dirs[] = $dh;
- else
- $files[] = $dh;
- }
- asort($dirs);
- asort($files);
- foreach ( $dirs as $dh )
- {
- $contents .= ' <li><a href="' . $root . '/' . $dh . '">' . $dh . '/</a></li>' . "\n ";
- }
- foreach ( $files as $dh )
- {
- $contents .= ' <li><a href="' . $root . '/' . $dh . '">' . $dh . '</a></li>' . "\n ";
+ $ts = ( $info['type'] == 'folder' ) ? '/' : '';
+ $contents .= ' <li><tt><a href="' . htmlspecialchars($root . basename($filename) . $ts) . '"><img alt="[ ]" src="/' . $this->uuid . '/' . $info['type'] . '.png" /> ' . htmlspecialchars($filename) . $ts . '</a></tt></li>' . "\n ";
}
$contents .= "\n </ul>\n <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n";
@@ -567,7 +744,7 @@
}
break;
- case 'function':
+ case 'script':
// init vars
$this->content_type = 'text/html';
$this->response_code = 200;
@@ -578,7 +755,9 @@
try
{
ob_start();
- $result = @call_user_func($handler['function'], $this);
+ $this->in_scriptlet = true;
+ $result = @call_user_func($handler['function'], $this, $socket);
+ $this->in_scriptlet = false;
$output = ob_get_contents();
ob_end_clean();
}
@@ -586,7 +765,8 @@
{
restore_error_handler();
$this->send_http_error($socket, 500, "A handler crashed with an exception; see the command line for details.");
- status("caught exception in handler {$handler['id']}:\n$e");
+ if ( function_exists('status') )
+ status("caught exception in handler {$handler['id']}:\n$e");
return true;
}
restore_error_handler();
@@ -610,6 +790,150 @@
// write body
@socket_write($socket, $output);
+ $this->headers_sent = false;
+
+ break;
+ case 'sysuuid':
+ // requested one of the system's icon images
+ $uri_parts = explode('/', $_SERVER['REQUEST_URI']);
+ if ( count($uri_parts) != 3 )
+ {
+ $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
+ }
+
+ // load image data
+ $filename =& $uri_parts[2];
+ switch ( $filename )
+ {
+ case 'script.png':
+ ( !isset($image_data) ) ? $image_data = HTTPD_ICON_SCRIPT : null;
+ case 'folder.png':
+ ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FOLDER : null;
+ case 'file.png':
+ ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FILE : null;
+
+ $image_data = base64_decode($image_data);
+ $found = true;
+ $type = 'image/png';
+ break;
+ case 'dirlist.css':
+ $type = 'text/css';
+ $found = true;
+ $image_data = <<<EOF
+/**
+ * PhpHttpd directory list visual style
+ */
+
+html {
+ background-color: #c9c9c9;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ background-color: #ffffff;
+ margin: 20px;
+ padding: 10px;
+ border: 1px solid #aaaaaa;
+}
+
+a {
+ text-decoration: none;
+}
+
+a img {
+ border-width: 0;
+}
+
+ul {
+ list-style-type: none;
+}
+
+EOF;
+ break;
+ default:
+ $found = false;
+ }
+
+ // ship it out
+ if ( $found )
+ {
+ $lm_date = date('r', filemtime(__FILE__));
+ $size = strlen($image_data);
+ $this->send_client_headers($socket, 200, $type, "Last-Modified: $lm_date\r\nContent-Length: $size");
+ @socket_write($socket, $image_data);
+ }
+ else
+ {
+ $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
+ }
+
+ return true;
+
+ break;
+ case 'rootdir':
+ //
+ // list the contents of the document root
+ //
+
+ $handlers = $this->list_directory('/', true);
+
+ $contents = <<<EOF
+<html>
+ <head>
+ <title>Index of: /</title>
+ <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" />
+ </head>
+ <body>
+ <h1>Index of /</h1>
+ <ul>
+
+EOF;
+
+ $html = '';
+ // generate content
+ foreach ( $handlers as $uri => $handler )
+ {
+ switch($handler['type'])
+ {
+ case 'folder':
+ $image = 'folder.png';
+ $abbr = 'DIR';
+ $add = '/';
+ break;
+ case 'file':
+ default:
+ $image = 'file.png';
+ $abbr = ' ';
+ $add = '';
+ break;
+ case 'script':
+ $image = 'script.png';
+ $abbr = 'CGI';
+ $add = '';
+ break;
+ }
+ $html .= " <li><tt><a href=\"/$uri\"><img alt=\"[{$abbr}]\" src=\"/{$this->uuid}/{$image}\" /> {$uri}{$add}</a></tt></li>\n ";
+ }
+
+ $contents .= $html;
+ $contents .= <<<EOF
+</ul>
+ <address>Served by {$this->server_string}</address>
+ </body>
+</html>
+EOF;
+
+ // get length
+ $len = strlen($contents);
+
+ // send headers
+ $this->send_client_headers($socket, 200, 'text/html', "Content-Length: $len");
+
+ // write to the socket
+ @socket_write($socket, $contents);
+
+ return true;
break;
}
}
@@ -646,7 +970,14 @@
{
global $http_responses;
$reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
- $this->send_client_headers($socket, $http_code);
+
+ // if we're in a scriptlet, include custom headers
+ if ( $this->in_scriptlet )
+ $headers = implode("\r\n", $this->response_headers);
+ else
+ $headers = '';
+
+ $this->send_client_headers($socket, $http_code, 'text/html', $headers);
$html = <<<EOF
<html>
<head>
@@ -661,8 +992,7 @@
</html>
EOF;
@socket_write($socket, $html);
- @socket_close($socket);
- }
+ }
/**
* Adds a new handler
@@ -673,11 +1003,15 @@
function add_handler($uri, $type, $value)
{
+ if ( $type == 'dir' )
+ $type = 'folder';
+ if ( $type == 'function' )
+ $type = 'script';
switch($type)
{
- case 'dir':
+ case 'folder':
$this->handlers[$uri] = array(
- 'type' => 'dir',
+ 'type' => 'folder',
'dir' => $value
);
break;
@@ -687,9 +1021,9 @@
'file' => $value
);
break;
- case 'function':
+ case 'script':
$this->handlers[$uri] = array(
- 'type' => 'function',
+ 'type' => 'script',
'function' => $value
);
break;
@@ -709,6 +1043,265 @@
echo '</div>';
}
+ /**
+ * Lists out the contents of a directory, including virtual handlers.
+ * @example
+ * Example return data: (will be ksorted)
+ <code>
+ array(
+ 'bar' => 'folder',
+ 'baz' => 'script',
+ 'foo' => 'file'
+ );
+ </code>
+ * @param string Directory name, relative to the server's document root
+ * @param bool If true, sorts folders first (default: false)
+ * @return array Exception thrown on failure
+ */
+
+ function list_directory($dir, $folders_first = false)
+ {
+ // clean slashes from the directory name
+ $dir = trim($dir, '/');
+
+ if ( $dir == '' )
+ {
+ //
+ // list the root, which can consist only of handlers
+ //
+
+ // copy the handlers array, which we need to ksort
+ $handlers = $this->handlers;
+
+ // get rid of multi-depth handlers
+ foreach ( $handlers as $uri => $handler )
+ {
+ if ( strpos($uri, '/') )
+ {
+ unset($handlers[$uri]);
+ $newuri = explode('/', $uri);
+ if ( !isset($handlers[$newuri[0]]) )
+ {
+ $handlers[$newuri[0]] = array(
+ 'type' => 'folder'
+ );
+ }
+ }
+ }
+
+ ksort($handlers);
+
+ if ( $folders_first )
+ {
+ // sort folders first
+ $handlers_sorted = array();
+ foreach ( $handlers as $uri => $handler )
+ {
+ if ( $handler['type'] == 'folder' )
+ $handlers_sorted[$uri] = $handler;
+ }
+ foreach ( $handlers as $uri => $handler )
+ {
+ if ( $handler['type'] != 'folder' )
+ $handlers_sorted[$uri] = $handler;
+ }
+ $handlers = $handlers_sorted;
+ unset($handlers_sorted);
+ }
+
+ // done
+ return $handlers;
+ }
+ else
+ {
+ // list something within the root
+ $dir_stack = explode('/', $dir);
+
+ // lookup handler
+ $handler_search = $dir;
+ $found_handler = false;
+ $fake_handler = false;
+ $i = 1;
+ while ( $i > 0 )
+ {
+ if ( isset($this->handlers[$handler_search]) )
+ {
+ $found_handler = true;
+ break;
+ }
+ $i = strrpos($handler_search, '/');
+ $handler_search = substr($handler_search, 0, strrpos($handler_search, '/'));
+ }
+ if ( $this->check_for_handler_children($dir) )
+ {
+ $fake_handler = true;
+ }
+ else if ( !$found_handler )
+ {
+ // nope. not there.
+ throw new Exception("ERR_NO_SUCH_FILE_OR_DIRECTORY");
+ }
+
+ // make sure this is a directory
+ if ( !$fake_handler )
+ {
+ $handler =& $handler_search;
+ if ( $this->handlers[$handler]['type'] != 'folder' )
+ {
+ throw new Exception("ERR_NOT_A_DIRECTORY");
+ }
+
+ // determine real path
+ $real_path = realpath($this->handlers[$handler]['dir'] . substr($dir, strlen($handler)));
+
+ // directory is resolved; list contents
+ $dir_contents = array();
+
+ if ( $dr = opendir($real_path) )
+ {
+ while ( $dh = readdir($dr) )
+ {
+ if ( $dh == '.' || $dh == '..' )
+ {
+ continue;
+ }
+ $dir_contents[$dh] = array(
+ 'type' => ( is_dir("$real_path/$dh") ) ? 'folder' : 'file',
+ 'size' => filesize("$real_path/$dh"),
+ 'time' => filemtime("$real_path/$dh")
+ );
+ }
+ }
+ else
+ {
+ // only if directory open failed
+ throw new Exception("ERR_PERMISSION_DENIED");
+ }
+
+ closedir($dr);
+
+ // some cleanup
+ unset($handler, $handler_search);
+ }
+
+ // list any additional handlers in there
+ foreach ( $this->handlers as $handler => $info )
+ {
+ // parse handler name
+ $handler_name = explode('/', trim($handler, '/'));
+ // is this handler in this directory?
+ if ( count($handler_name) != count($dir_stack) + 1 )
+ {
+ continue;
+ }
+ foreach ( $dir_stack as $i => $_ )
+ {
+ if ( $dir_stack[$i] != $handler_name[$i] )
+ {
+ continue 2;
+ }
+ }
+ // it's in here!
+ $dir_contents[ basename($handler) ] = array(
+ 'type' => $info['type']
+ );
+ }
+
+ // list "fake" handlers
+ foreach ( $this->handlers as $handler => $info )
+ {
+ // parse handler name
+ $handler_name = explode('/', trim($handler, '/'));
+ // is this handler somewhere underneath this directory?
+ if ( count($handler_name) < count($dir_stack) + 2 )
+ {
+ continue;
+ }
+ // path check
+ foreach ( $dir_stack as $i => $_ )
+ {
+ if ( $dir_stack[$i] != $handler_name[$i] )
+ {
+ continue 2;
+ }
+ }
+ // create a "fake" directory
+ $fakie_name = $handler_name[ count($dir_stack) ];
+ $dir_contents[$fakie_name] = array(
+ 'type' => 'folder'
+ );
+ }
+
+ if ( $folders_first )
+ {
+ // perform folder sorting
+ $unsorted = $dir_contents;
+ ksort($unsorted);
+ $dir_contents = array();
+ foreach ( $unsorted as $name => $info )
+ {
+ if ( $info['type'] == 'folder' )
+ $dir_contents[$name] = $info;
+ }
+ foreach ( $unsorted as $name => $info )
+ {
+ if ( $info['type'] != 'folder' )
+ $dir_contents[$name] = $info;
+ }
+ }
+ else
+ {
+ // not sorting with folders first, so just alphabetize
+ ksort($dir_contents);
+ }
+
+ // done
+
+ return $dir_contents;
+ }
+ }
+
+ /**
+ * Searches deeper to see if there are sub-handlers within a path to see if a fake handler can be created
+ * @param string URI
+ * @return bool
+ */
+
+ function check_for_handler_children($file_path)
+ {
+ $file_path = trim($file_path, '/');
+ $dir_stack = explode('/', $file_path);
+
+ // make sure this isn't a "real" handler
+ if ( isset($this->handlers[$file_path]) )
+ {
+ return false;
+ }
+
+ // list any additional handlers in there
+ foreach ( $this->handlers as $handler => $info )
+ {
+ // parse handler name
+ $handler_name = explode('/', trim($handler, '/'));
+ // is this handler in this directory?
+ if ( count($handler_name) != count($dir_stack) + 1 )
+ {
+ continue;
+ }
+ foreach ( $dir_stack as $i => $_ )
+ {
+ if ( $dir_stack[$i] != $handler_name[$i] )
+ {
+ continue 2;
+ }
+ }
+ // it's in here!
+ return true;
+ }
+
+ return false;
+ }
+
}
/**