webserver.php
changeset 21 74edc873234f
parent 17 66c3eb4cdc4c
child 23 08225c2eb0b6
equal deleted inserted replaced
20:bb8237ca678d 21:74edc873234f
    19  */
    19  */
    20 
    20 
    21 define('HTTPD_VERSION', '0.1b1');
    21 define('HTTPD_VERSION', '0.1b1');
    22 
    22 
    23 /**
    23 /**
       
    24  * Webserver system icons
       
    25  */
       
    26 
       
    27 define('HTTPD_ICON_SCRIPT', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGSSURBVCjPVVFNSwJhEF78Ad79Cf6PvXQRsotUlzKICosuRYmR2RJR0KE6lBFFZVEbpFBSqKu2rum6llFS9HHI4iUhT153n6ZtIWMOM+/MM88z7wwH7s9Ub16SJcnbmrNcxVm2q7Z8/QPvEOtntpj92NkCqITLepEpjix7xQtiLOoQ2b6+E7YAN/5nfOEJ2WbKqOIOJ4bYVMEQx4LfBBQDsvFMhUcCVU1/CxVXmDBGA5ZETrhDCQVcYAPbyEJBhvrnBVPiSpNr6cYDNCQwo4zzU/ySckkgDYuNuVpI42T9k4gLKGMPs/xPzzovQiY2hQYe0jlJfyNNhTqiWDYBq/wBMcSRpnyPzu1oS7WtxjVBSthU1vgVksiQ3Dn6Gp5ah2YOKQo5GiuHPA6xT1EKpxQNCNYejgIR457KKio0S56YckjSa9jo//3mrj+BV0QQagqGTOo+Y7gZIf1puP3WHoLhEb2PjTlCTCWGXtbp8DCX3hZuOdaIc9A+aQvWk4ihq95p67a7nP+u+Ws+r0dql9z/zv0NCYhdCPKZ7oYAAAAASUVORK5CYII=');
       
    28 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');
       
    29 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=');
       
    30 
       
    31 /**
    24  * Simple web server written in PHP.
    32  * Simple web server written in PHP.
    25  * @package Amarok
    33  * @package Amarok
    26  * @subpackage WebControl
    34  * @subpackage WebControl
    27  * @author Dan Fuhry
    35  * @author Dan Fuhry
    28  * @license Public domain
    36  * @license Public domain
    58    */
    66    */
    59   
    67   
    60   var $default_document = false;
    68   var $default_document = false;
    61   
    69   
    62   /**
    70   /**
       
    71    * List of filenames or handlers used when a directory listing is requested
       
    72    * @var array
       
    73    */
       
    74   
       
    75   var $directory_index = array('index.html', 'index.htm', 'index', 'default.html', 'default.htm');
       
    76   
       
    77   /**
    63    * HTTP response code set by the handler function
    78    * HTTP response code set by the handler function
    64    * @var int
    79    * @var int
    65    */
    80    */
    66   
    81   
    67   var $response_code = 0;
    82   var $response_code = 0;
   108    */
   123    */
   109   
   124   
   110   var $in_keepalive = false;
   125   var $in_keepalive = false;
   111   
   126   
   112   /**
   127   /**
       
   128    * UUID for this server instance
       
   129    * @var string
       
   130    */
       
   131   
       
   132   var $uuid = '00000000-0000-0000-0000-000000000000';
       
   133   
       
   134   /**
       
   135    * Switch to track whether a scriptlet is running. If it is, send_http_error() does more than normal.
       
   136    * @var bool
       
   137    */
       
   138   
       
   139   var $in_scriptlet = false;
       
   140   
       
   141   /**
       
   142    * Switch to track whether headers have been sent or not.
       
   143    * @var bool
       
   144    */
       
   145   
       
   146   var $headers_sent = false;
       
   147   
       
   148   /**
       
   149    * Switch to track if the socket is bound and thus needs to be freed or not
       
   150    * @var bool
       
   151    */
       
   152   
       
   153   var $socket_initted = false;
       
   154   
       
   155   /**
   113    * Constructor.
   156    * Constructor.
   114    * @param string IPv4 address to bind to
   157    * @param string IPv4 address to bind to
   115    * @param int Port number
   158    * @param int Port number
   116    */
   159    * @param int If port is under 1024, specify a user ID/name to switch to here
   117   
   160    * @param int If port is under 1024, specify a group ID/name to switch to here
   118   function __construct($address = '127.0.0.1', $port = 8080)
   161    */
       
   162   
       
   163   function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null)
   119   {
   164   {
   120     @set_time_limit(0);
   165     @set_time_limit(0);
   121     @ini_set('memory_limit', '256M');
   166     @ini_set('memory_limit', '256M');
   122     
   167     
   123     // do we have socket functions?
   168     // do we have socket functions?
   124     if ( !function_exists('socket_create') )
   169     if ( !function_exists('socket_create') )
   125     {
   170     {
   126       burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
   171       burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
   127     }
   172     }
   128     
   173     
   129     $this->sock = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
   174     // make sure we're not running as root
       
   175     // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound
       
   176     $allow_root = ( $port < 1024 ) ? true : false;
       
   177     if ( function_exists('posix_geteuid') )
       
   178     {
       
   179       $euid = posix_geteuid();
       
   180       $egid = posix_getegid();
       
   181       $username = posix_getpwuid($euid);
       
   182       $username = $username['name'];
       
   183       $group = posix_getgrgid($egid);
       
   184       $group = $group['name'];
       
   185       if ( $euid == 0 && !$allow_root )
       
   186       {
       
   187         // running as root but not on a privileged port - die for security
       
   188         burnout("Running as superuser (user \"$username\" and group \"$group\"). This is not allowed for security reasons.");
       
   189       }
       
   190       else if ( $euid == 0 && $allow_root )
       
   191       {
       
   192         // running as root and port below 1024, so notify of the switch and verify that a target UID and GID were passed
       
   193         if ( $targetuser === null || $targetgroup === null )
       
   194         {
       
   195           // no target user/group specified
       
   196           burnout("Must specify a target user and group when running server as root");
       
   197         }
       
   198         // get info about target user/group
       
   199         if ( is_string($targetuser) )
       
   200         {
       
   201           $targetuser = posix_getpwnam($targetuser);
       
   202           $targetuser = $targetuser['uid'];
       
   203         }
       
   204         if ( is_string($targetgroup) )
       
   205         {
       
   206           $targetgroup = posix_getgrnam($targetgroup);
       
   207           $targetgroup = $targetgroup['uid'];
       
   208         }
       
   209         // make sure all info is valid
       
   210         if ( !is_int($targetuser) || !is_int($targetgroup) )
       
   211         {
       
   212           burnout('Invalid user or group specified');
       
   213         }
       
   214         $userinfo = posix_getpwuid($targetuser);
       
   215         $groupinfo = posix_getgrgid($targetgroup);
       
   216         if ( function_exists('status') )
       
   217           status("Will switch to user \"{$userinfo['name']}\" and group \"{$groupinfo['name']}\" shortly after binding to socket");
       
   218       }
       
   219       else if ( $allow_root && $euid > 0 )
       
   220       {
       
   221         burnout("Must be superuser to bind to ports below 1024");
       
   222       }
       
   223     }
       
   224     $socket_do_root = ( $allow_root ) ? function_exists('posix_geteuid') : false;
       
   225     
       
   226     $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
   130     if ( !$this->sock )
   227     if ( !$this->sock )
   131       throw new Exception('Could not create socket');
   228       throw new Exception('Could not create socket');
   132     $result = socket_bind($this->sock, $address, $port);
   229     $result = @socket_bind($this->sock, $address, $port);
   133     if ( !$result )
   230     if ( !$result )
   134       throw new Exception("Could not bind to $address:$port");
   231       throw new Exception("Could not bind to $address:$port");
   135     $result = socket_listen($this->sock, SOMAXCONN);
   232     $this->socket_initted = true;
       
   233     $result = @socket_listen($this->sock, SOMAXCONN);
   136     if ( !$result )
   234     if ( !$result )
   137       throw new Exception("Could not listen for connections $address:$port");
   235       throw new Exception("Could not listen for connections $address:$port");
       
   236     
       
   237     // if running as root and we made it here, switch credentials
       
   238     if ( $socket_do_root )
       
   239     {
       
   240       posix_setuid($targetuser);
       
   241       posix_setgid($targetgroup);
       
   242       posix_setegid($targetgroup);
       
   243       posix_seteuid($targetuser);
       
   244       if ( function_exists('status') )
       
   245         status('Successfully switched user ID');
       
   246     }
       
   247     
   138     $this->bind_address = $address;
   248     $this->bind_address = $address;
   139     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
   249     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
       
   250     
       
   251     // create a UUID
       
   252     $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
       
   253     $this->uuid = substr($uuid_base, 0,  8) . '-' .
       
   254                   substr($uuid_base, 8,  4) . '-' .
       
   255                   substr($uuid_base, 12, 4) . '-' .
       
   256                   substr($uuid_base, 16, 4) . '-' .
       
   257                   substr($uuid_base, 20, 20);
   140   }
   258   }
   141   
   259   
   142   /**
   260   /**
   143    * Destructor.
   261    * Destructor.
   144    */
   262    */
   145   
   263   
   146   function __destruct()
   264   function __destruct()
   147   {
   265   {
   148     if ( !defined('HTTPD_WS_CHILD') )
   266     if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted )
   149     {
   267     {
   150       status('WebServer: destroying socket');
   268       if ( function_exists('status') )
       
   269         status('WebServer: destroying socket');
       
   270       // http://us3.php.net/manual/en/function.socket-bind.php
       
   271       if ( !@socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1) )
       
   272       {
       
   273         echo socket_strerror(socket_last_error($sock)) . "\n";
       
   274       }
   151       @socket_shutdown($this->sock, 2);
   275       @socket_shutdown($this->sock, 2);
   152       @socket_close($this->sock);
   276       @socket_close($this->sock);
   153     }
   277     }
   154   }
   278   }
   155   
   279   
   162     while ( true )
   286     while ( true )
   163     {
   287     {
   164       // if this is a child process, we're finished - close up shop
   288       // if this is a child process, we're finished - close up shop
   165       if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
   289       if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
   166       {
   290       {
   167         status('Exiting child process');
   291         if ( function_exists('status') )
       
   292           status('Exiting child process');
   168         @socket_shutdown($remote);
   293         @socket_shutdown($remote);
   169         @socket_close($remote);
   294         @socket_close($remote);
   170         exit(0);
   295         exit(0);
   171       }
   296       }
   172       
   297       
   209         }
   334         }
   210         else
   335         else
   211         {
   336         {
   212           // this is the child
   337           // this is the child
   213           define('HTTPD_WS_CHILD', 1);
   338           define('HTTPD_WS_CHILD', 1);
       
   339           @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
   214           socket_close($this->sock);
   340           socket_close($this->sock);
   215         }
   341         }
   216       }
   342       }
   217       
   343       
   218       $this->in_keepalive = false;
   344       $this->in_keepalive = false;
       
   345       $this->headers_sent = false;
       
   346       $this->in_scriptlet = false;
   219       
   347       
   220       // read request
   348       // read request
   221       $last_line = '';
   349       $last_line = '';
   222       $client_headers = '';
   350       $client_headers = '';
   223       while ( $line = @socket_read($remote, 1024, PHP_NORMAL_READ) )
   351       while ( $line = @socket_read($remote, 1024, PHP_NORMAL_READ) )
   259       if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') )
   387       if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') )
   260       {
   388       {
   261         $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' );
   389         $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' );
   262       }
   390       }
   263       
   391       
       
   392       // parse authorization, if any
       
   393       if ( isset($_SERVER['PHP_AUTH_USER']) )
       
   394       {
       
   395         unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
       
   396       }
   264       if ( isset($_SERVER['HTTP_AUTHORIZATION']) )
   397       if ( isset($_SERVER['HTTP_AUTHORIZATION']) )
   265       {
   398       {
   266         $data = $_SERVER['HTTP_AUTHORIZATION'];
   399         $data = $_SERVER['HTTP_AUTHORIZATION'];
   267         $data = substr(strstr($data, ' '), 1);
   400         $data = substr(strstr($data, ' '), 1);
   268         $data = base64_decode($data);
   401         $data = base64_decode($data);
   269         $_SERVER['PHP_AUTH_USER'] = substr($data, 0, strpos($data, ':'));
   402         $_SERVER['PHP_AUTH_USER'] = substr($data, 0, strpos($data, ':'));
   270         $_SERVER['PHP_AUTH_PW'] = substr(strstr($data, ':'), 1);
   403         $_SERVER['PHP_AUTH_PW'] = substr(strstr($data, ':'), 1);
   271       }
   404       }
   272       
   405       
       
   406       // anything on POST?
   273       $postdata = '';
   407       $postdata = '';
   274       $_POST = array();
   408       $_POST = array();
   275       if ( $method == 'POST' )
   409       if ( $method == 'POST' )
   276       {
   410       {
   277         // read POST data
   411         // read POST data
   301       {
   435       {
   302         $params = substr(strstr($uri, '?'), 1);
   436         $params = substr(strstr($uri, '?'), 1);
   303         $uri    = substr($uri, 0, strpos($uri, '?'));
   437         $uri    = substr($uri, 0, strpos($uri, '?'));
   304       }
   438       }
   305       
   439       
       
   440       // set some server vars
   306       $_SERVER['REQUEST_URI'] = '/' . rawurldecode($uri);
   441       $_SERVER['REQUEST_URI'] = '/' . rawurldecode($uri);
   307       $_SERVER['REQUEST_METHOD'] = $method;
   442       $_SERVER['REQUEST_METHOD'] = $method;
       
   443       
       
   444       // get remote IP and port
   308       socket_getpeername($remote, $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
   445       socket_getpeername($remote, $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
   309       
   446       
   310       $_GET = array();
   447       $_GET = array();
   311       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) )
   448       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) )
   312       {
   449       {
   317             $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   454             $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   318           }
   455           }
   319         }
   456         }
   320       }
   457       }
   321       
   458       
       
   459       // init handler
       
   460       $handler = false;
       
   461       
   322       if ( $uri == '' )
   462       if ( $uri == '' )
   323       {
   463       {
       
   464         // user requested the root (/). If there's a default document, use that; else, see if we can do a directory listing
   324         $uri = strval($this->default_document);
   465         $uri = strval($this->default_document);
       
   466         if ( !$this->default_document && $this->allow_dir_list )
       
   467         {
       
   468           // we can list directories and this was requested by the user, so list it out
       
   469           $handler = array('type' => 'rootdir');
       
   470         }
   325       }
   471       }
   326       
   472       
   327       $uri_parts = explode('/', $uri);
   473       $uri_parts = explode('/', $uri);
   328       
   474       
       
   475       // hook for the special UUID handler
       
   476       if ( $uri_parts[0] === $this->uuid && !$handler )
       
   477       {
       
   478         $handler = array('type' => 'sysuuid');
       
   479       }
       
   480       
   329       // loop through URI parts, see if a handler is set
   481       // loop through URI parts, see if a handler is set
   330       $handler = false;
       
   331       for ( $i = count($uri_parts) - 1; $i >= 0; $i-- )
       
   332       {
       
   333         $handler_test = implode('/', $uri_parts);
       
   334         if ( isset($this->handlers[$handler_test]) )
       
   335         {
       
   336           $handler = $this->handlers[$handler_test];
       
   337           $handler['id'] = $handler_test;
       
   338           break;
       
   339         }
       
   340         unset($uri_parts[$i]);
       
   341       }
       
   342       
       
   343       if ( !$handler )
   482       if ( !$handler )
   344       {
   483       {
   345         $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server.");
   484         for ( $i = count($uri_parts) - 1; $i >= 0; $i-- )
   346         continue;
   485         {
       
   486           $handler_test = implode('/', $uri_parts);
       
   487           if ( isset($this->handlers[$handler_test]) )
       
   488           {
       
   489             $handler = $this->handlers[$handler_test];
       
   490             $handler['id'] = $handler_test;
       
   491             break;
       
   492           }
       
   493           unset($uri_parts[$i]);
       
   494         }
       
   495       }
       
   496       
       
   497       if ( !$handler )
       
   498       {
       
   499         // try to make a fakie
       
   500         if ( $this->check_for_handler_children($uri) )
       
   501         {
       
   502           $handler = array(
       
   503             'type' => 'folder',
       
   504             'dir' => "/{$this->uuid}/__fakie",
       
   505             'id' => $uri
       
   506           );
       
   507         }
       
   508         if ( !$handler )
       
   509         {
       
   510           $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server.");
       
   511           continue;
       
   512         }
   347       }
   513       }
   348       
   514       
   349       $this->send_standard_response($remote, $handler, $uri, $params);
   515       $this->send_standard_response($remote, $handler, $uri, $params);
   350       
   516       
   351       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   517       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   352       {
   518       {
   353         // if ( defined('HTTPD_WS_CHILD') )
   519         // if ( defined('HTTPD_WS_CHILD') )
   354         //   status('Closing connection');
   520         //   status('Closing connection');
   355         @socket_shutdown($remote);
   521         @socket_shutdown($remote);
   356         @socket_close($remote);
   522         @socket_close($remote);
   357         status('Exiting child process');
   523         if ( function_exists('status') )
       
   524           status('Exiting child process');
   358         exit(0);
   525         exit(0);
   359       }
   526       }
   360       else if ( defined('HTTPD_WS_CHILD') )
   527       else if ( defined('HTTPD_WS_CHILD') )
   361       {
   528       {
   362         // if ( defined('HTTPD_WS_CHILD') )
   529         // if ( defined('HTTPD_WS_CHILD') )
   363         //   status('Continuing connection');
   530         //   status('Continuing connection');
   364         // @socket_write($remote, "\r\n\r\n");
   531         // @socket_write($remote, "\r\n\r\n");
       
   532       }
       
   533       else
       
   534       {
       
   535         @socket_shutdown($remote);
       
   536         @socket_close($remote);
   365       }
   537       }
   366     }
   538     }
   367   }
   539   }
   368   
   540   
   369   /**
   541   /**
   375    */                     
   547    */                     
   376   
   548   
   377   function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '')
   549   function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '')
   378   {
   550   {
   379     global $http_responses;
   551     global $http_responses;
       
   552     if ( $this->headers_sent )
       
   553       return false;
       
   554 
       
   555     // this is reset after the request is completed (hopefully)    
       
   556     $this->headers_sent = true;
       
   557     
   380     $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
   558     $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
   381     
   559     
   382     $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)';
   560     $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)';
   383     status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}");
   561     
       
   562     if ( function_exists('status') )
       
   563       status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}");
   384     
   564     
   385     $headers = str_replace("\r\n", "\n", $headers);
   565     $headers = str_replace("\r\n", "\n", $headers);
   386     $headers = str_replace("\n", "\r\n", $headers);
   566     $headers = str_replace("\n", "\r\n", $headers);
   387     $headers = preg_replace("#[\r\n]+$#", '', $headers);
   567     $headers = preg_replace("#[\r\n]+$#", '', $headers);
   388     $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close';
   568     $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close';
   406   
   586   
   407   function send_standard_response($socket, $handler)
   587   function send_standard_response($socket, $handler)
   408   {
   588   {
   409     switch ( $handler['type'] )
   589     switch ( $handler['type'] )
   410     {
   590     {
   411       case 'dir':
   591       case 'folder':
   412         // security
   592         // security
   413         $uri = str_replace("\000", '', $_SERVER['REQUEST_URI']);
   593         $uri = str_replace("\000", '', $_SERVER['REQUEST_URI']);
   414         if ( preg_match('#(\.\./|\/\.\.)#', $uri) || strstr($uri, "\r") || strstr($uri, "\n") )
   594         if ( preg_match('#(\.\./|\/\.\.)#', $uri) || strstr($uri, "\r") || strstr($uri, "\n") )
   415         {
   595         {
   416           $this->send_http_error($socket, 403, 'Access to this resource is forbidden.');
   596           $this->send_http_error($socket, 403, 'Access to this resource is forbidden.');
   418         
   598         
   419         // import mimetypes
   599         // import mimetypes
   420         global $mime_types;
   600         global $mime_types;
   421         
   601         
   422         // trim handler id from uri
   602         // trim handler id from uri
       
   603         $uri_full = rtrim($uri, '/');
   423         $uri = substr($uri, strlen($handler['id']) + 1);
   604         $uri = substr($uri, strlen($handler['id']) + 1);
   424         
   605         
   425         // get file path
   606         // get file path
   426         $file_path = rtrim($handler['dir'], '/') . $uri;
   607         $file_path = rtrim($handler['dir'], '/') . $uri;
   427         if ( file_exists($file_path) )
   608         if ( file_exists($file_path) || $this->check_for_handler_children($uri_full) )
   428         {
   609         {
   429           // found it :-D
   610           // found it :-D
   430           
   611           
   431           // is this a directory?
   612           // is this a directory?
   432           if ( is_dir($file_path) )
   613           if ( is_dir($file_path) || $this->check_for_handler_children($uri_full) )
   433           {
   614           {
       
   615             // allowed to list?
   434             if ( !$this->allow_dir_list )
   616             if ( !$this->allow_dir_list )
   435             {
   617             {
   436               $this->send_http_error($socket, 403, "Directory listing is not allowed.");
   618               $this->send_http_error($socket, 403, "Directory listing is not allowed.");
   437               return true;
   619               return true;
   438             }
   620             }
   439             // yes, list contents
   621             // yes, list contents
   440             $root = '/' . $handler['id'] . rtrim($uri, '/');
   622             try
   441             $parent = substr($root, 0, strrpos($root, '/')) . '/';
   623             {
   442               
   624               $dir_list = $this->list_directory($uri_full, true);
       
   625             }
       
   626             catch ( Exception $e )
       
   627             {
       
   628               $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>");
       
   629               return true;
       
   630             }
       
   631             
       
   632             $root = rtrim($uri_full, '/') . '/';
       
   633             $parent = rtrim(dirname(rtrim($uri_full, '/')), '/') . '/';
       
   634             
   443             $contents = <<<EOF
   635             $contents = <<<EOF
   444 <html>
   636 <html>
   445   <head>
   637   <head>
   446     <title>Index of: $root</title>
   638     <title>Index of: $root</title>
       
   639     <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" />
   447   </head>
   640   </head>
   448   <body>
   641   <body>
   449     <h1>Index of $root</h1>
   642     <h1>Index of $root</h1>
   450     <ul>
   643     <ul>
   451       <li><a href="$parent">Parent directory</a></li>
   644       <li><tt><a href="$parent">Parent directory</a></tt></li>
   452     
   645     
   453 EOF;
   646 EOF;
   454             $dirs = array();
   647 
   455             $files = array();
   648             foreach ( $dir_list as $filename => $info )
   456             $d = @opendir($file_path);
       
   457             while ( $dh = readdir($d) )
       
   458             {
   649             {
   459               if ( $dh == '.' || $dh == '..' )
   650               $ts = ( $info['type'] == 'folder' ) ? '/' : '';
   460                 continue;
   651               $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    ";
   461               if ( is_dir("$file_path/$dh") )
       
   462                 $dirs[] = $dh;
       
   463               else
       
   464                 $files[] = $dh;
       
   465             }
       
   466             asort($dirs);
       
   467             asort($files);
       
   468             foreach ( $dirs as $dh )
       
   469             {
       
   470               $contents .= '  <li><a href="' . $root . '/' . $dh . '">' . $dh . '/</a></li>' . "\n    ";
       
   471             }
       
   472             foreach ( $files as $dh )
       
   473             {
       
   474               $contents .= '  <li><a href="' . $root . '/' . $dh . '">' . $dh . '</a></li>' . "\n    ";
       
   475             }
   652             }
   476             $contents .= "\n    </ul>\n    <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n";
   653             $contents .= "\n    </ul>\n    <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n";
   477             
   654             
   478             $sz = strlen($contents);
   655             $sz = strlen($contents);
   479             $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n");
   656             $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n");
   565         {
   742         {
   566           $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
   743           $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
   567         }
   744         }
   568         
   745         
   569         break;
   746         break;
   570       case 'function':
   747       case 'script':
   571         // init vars
   748         // init vars
   572         $this->content_type = 'text/html';
   749         $this->content_type = 'text/html';
   573         $this->response_code = 200;
   750         $this->response_code = 200;
   574         $this->response_headers = array();
   751         $this->response_headers = array();
   575         
   752         
   576         // error handling
   753         // error handling
   577         @set_error_handler(array($this, 'function_error_handler'), E_ALL);
   754         @set_error_handler(array($this, 'function_error_handler'), E_ALL);
   578         try
   755         try
   579         {
   756         {
   580           ob_start();
   757           ob_start();
   581           $result = @call_user_func($handler['function'], $this);
   758           $this->in_scriptlet = true;
       
   759           $result = @call_user_func($handler['function'], $this, $socket);
       
   760           $this->in_scriptlet = false;
   582           $output = ob_get_contents();
   761           $output = ob_get_contents();
   583           ob_end_clean();
   762           ob_end_clean();
   584         }
   763         }
   585         catch ( Exception $e )
   764         catch ( Exception $e )
   586         {
   765         {
   587           restore_error_handler();
   766           restore_error_handler();
   588           $this->send_http_error($socket, 500, "A handler crashed with an exception; see the command line for details.");
   767           $this->send_http_error($socket, 500, "A handler crashed with an exception; see the command line for details.");
   589           status("caught exception in handler {$handler['id']}:\n$e");
   768           if ( function_exists('status') )
       
   769             status("caught exception in handler {$handler['id']}:\n$e");
   590           return true;
   770           return true;
   591         }
   771         }
   592         restore_error_handler();
   772         restore_error_handler();
   593         
   773         
   594         // the handler function should return this magic string if it writes its own headers and socket data
   774         // the handler function should return this magic string if it writes its own headers and socket data
   608         // $output = dechex(strlen($output)) . "\r\n$output";
   788         // $output = dechex(strlen($output)) . "\r\n$output";
   609         
   789         
   610         // write body
   790         // write body
   611         @socket_write($socket, $output);
   791         @socket_write($socket, $output);
   612         
   792         
       
   793         $this->headers_sent = false;
       
   794         
       
   795         break;
       
   796       case 'sysuuid':
       
   797         // requested one of the system's icon images
       
   798         $uri_parts = explode('/', $_SERVER['REQUEST_URI']);
       
   799         if ( count($uri_parts) != 3 )
       
   800         {
       
   801           $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
       
   802         }
       
   803         
       
   804         // load image data
       
   805         $filename =& $uri_parts[2];
       
   806         switch ( $filename )
       
   807         {
       
   808           case 'script.png':
       
   809             ( !isset($image_data) ) ? $image_data = HTTPD_ICON_SCRIPT : null;
       
   810           case 'folder.png':
       
   811             ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FOLDER : null;
       
   812           case 'file.png':
       
   813             ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FILE : null;
       
   814             
       
   815             $image_data = base64_decode($image_data);
       
   816             $found = true;
       
   817             $type = 'image/png';
       
   818             break;
       
   819           case 'dirlist.css':
       
   820             $type = 'text/css';
       
   821             $found = true;
       
   822             $image_data = <<<EOF
       
   823 /**
       
   824  * PhpHttpd directory list visual style
       
   825  */
       
   826 
       
   827 html {
       
   828   background-color: #c9c9c9;
       
   829   margin: 0;
       
   830   padding: 0;
       
   831 }
       
   832 
       
   833 body {
       
   834   background-color: #ffffff;
       
   835   margin: 20px;
       
   836   padding: 10px;
       
   837   border: 1px solid #aaaaaa;
       
   838 }
       
   839 
       
   840 a {
       
   841   text-decoration: none;
       
   842 }
       
   843 
       
   844 a img {
       
   845   border-width: 0;
       
   846 }
       
   847 
       
   848 ul {
       
   849   list-style-type: none;
       
   850 }
       
   851 
       
   852 EOF;
       
   853             break;
       
   854           default:
       
   855             $found = false;
       
   856         }
       
   857         
       
   858         // ship it out
       
   859         if ( $found )
       
   860         {
       
   861           $lm_date = date('r', filemtime(__FILE__));
       
   862           $size = strlen($image_data);
       
   863           $this->send_client_headers($socket, 200, $type, "Last-Modified: $lm_date\r\nContent-Length: $size");
       
   864           @socket_write($socket, $image_data);
       
   865         }
       
   866         else
       
   867         {
       
   868           $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
       
   869         }
       
   870         
       
   871         return true;
       
   872         
       
   873         break;
       
   874       case 'rootdir':
       
   875         //
       
   876         // list the contents of the document root
       
   877         //
       
   878         
       
   879         $handlers = $this->list_directory('/', true);
       
   880         
       
   881         $contents = <<<EOF
       
   882 <html>
       
   883   <head>
       
   884     <title>Index of: /</title>
       
   885     <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" />
       
   886   </head>
       
   887   <body>
       
   888     <h1>Index of /</h1>
       
   889     <ul>
       
   890     
       
   891 EOF;
       
   892         
       
   893         $html = '';
       
   894         // generate content
       
   895         foreach ( $handlers as $uri => $handler )
       
   896         {
       
   897           switch($handler['type'])
       
   898           {
       
   899             case 'folder':
       
   900               $image = 'folder.png';
       
   901               $abbr = 'DIR';
       
   902               $add = '/';
       
   903               break;
       
   904             case 'file':
       
   905             default:
       
   906               $image = 'file.png';
       
   907               $abbr = '   ';
       
   908               $add = '';
       
   909               break;
       
   910             case 'script':
       
   911               $image = 'script.png';
       
   912               $abbr = 'CGI';
       
   913               $add = '';
       
   914               break;
       
   915           }
       
   916           $html .= "  <li><tt><a href=\"/$uri\"><img alt=\"[{$abbr}]\" src=\"/{$this->uuid}/{$image}\" /> {$uri}{$add}</a></tt></li>\n      ";
       
   917         }
       
   918         
       
   919         $contents .= $html;
       
   920         $contents .= <<<EOF
       
   921 </ul>
       
   922     <address>Served by {$this->server_string}</address>
       
   923   </body>
       
   924 </html>
       
   925 EOF;
       
   926 
       
   927         // get length
       
   928         $len = strlen($contents);
       
   929         
       
   930         // send headers
       
   931         $this->send_client_headers($socket, 200, 'text/html', "Content-Length: $len");
       
   932         
       
   933         // write to the socket
       
   934         @socket_write($socket, $contents);
       
   935         
       
   936         return true;
   613         break;
   937         break;
   614     }
   938     }
   615   }
   939   }
   616   
   940   
   617   /**
   941   /**
   644   
   968   
   645   function send_http_error($socket, $http_code, $errstring)
   969   function send_http_error($socket, $http_code, $errstring)
   646   {
   970   {
   647     global $http_responses;
   971     global $http_responses;
   648     $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
   972     $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown';
   649     $this->send_client_headers($socket, $http_code);
   973     
       
   974     // if we're in a scriptlet, include custom headers
       
   975     if ( $this->in_scriptlet )
       
   976       $headers = implode("\r\n", $this->response_headers);
       
   977     else
       
   978       $headers = '';
       
   979       
       
   980     $this->send_client_headers($socket, $http_code, 'text/html', $headers);
   650     $html = <<<EOF
   981     $html = <<<EOF
   651 <html>
   982 <html>
   652   <head>
   983   <head>
   653     <title>$http_code $reason_code</title>
   984     <title>$http_code $reason_code</title>
   654   </head>
   985   </head>
   659     <address>Served by $this->server_string</address>
   990     <address>Served by $this->server_string</address>
   660   </body>
   991   </body>
   661 </html>
   992 </html>
   662 EOF;
   993 EOF;
   663     @socket_write($socket, $html);
   994     @socket_write($socket, $html);
   664     @socket_close($socket);
   995   } 
   665   }
       
   666   
   996   
   667   /**
   997   /**
   668    * Adds a new handler
   998    * Adds a new handler
   669    * @param string URI, minus the initial /
   999    * @param string URI, minus the initial /
   670    * @param string Type of handler - function or dir
  1000    * @param string Type of handler - function or dir
   671    * @param string Value - function name or absolute/relative path to directory
  1001    * @param string Value - function name or absolute/relative path to directory
   672    */
  1002    */
   673   
  1003   
   674   function add_handler($uri, $type, $value)
  1004   function add_handler($uri, $type, $value)
   675   {
  1005   {
       
  1006     if ( $type == 'dir' )
       
  1007       $type = 'folder';
       
  1008     if ( $type == 'function' )
       
  1009       $type = 'script';
   676     switch($type)
  1010     switch($type)
   677     {
  1011     {
   678       case 'dir':
  1012       case 'folder':
   679         $this->handlers[$uri] = array(
  1013         $this->handlers[$uri] = array(
   680             'type' => 'dir',
  1014             'type' => 'folder',
   681             'dir' => $value
  1015             'dir' => $value
   682           );
  1016           );
   683         break;
  1017         break;
   684       case 'file':
  1018       case 'file':
   685         $this->handlers[$uri] = array(
  1019         $this->handlers[$uri] = array(
   686             'type' => 'file',
  1020             'type' => 'file',
   687             'file' => $value
  1021             'file' => $value
   688           );
  1022           );
   689         break;
  1023         break;
   690       case 'function':
  1024       case 'script':
   691         $this->handlers[$uri] = array(
  1025         $this->handlers[$uri] = array(
   692             'type' => 'function',
  1026             'type' => 'script',
   693             'function' => $value
  1027             'function' => $value
   694           );
  1028           );
   695         break;
  1029         break;
   696     }
  1030     }
   697   }
  1031   }
   705   {
  1039   {
   706     echo '<div style="border: 1px solid #AA0000; background-color: #FFF0F0; padding: 10px;">';
  1040     echo '<div style="border: 1px solid #AA0000; background-color: #FFF0F0; padding: 10px;">';
   707     echo "<b>PHP warning/error:</b> type $errno ($errstr) caught in <b>$errfile</b> on <b>$errline</b><br />";
  1041     echo "<b>PHP warning/error:</b> type $errno ($errstr) caught in <b>$errfile</b> on <b>$errline</b><br />";
   708     // echo "Error context:<pre>" . htmlspecialchars(print_r($errcontext, true)) . "</pre>";
  1042     // echo "Error context:<pre>" . htmlspecialchars(print_r($errcontext, true)) . "</pre>";
   709     echo '</div>';
  1043     echo '</div>';
       
  1044   }
       
  1045   
       
  1046   /**
       
  1047    * Lists out the contents of a directory, including virtual handlers.
       
  1048    * @example
       
  1049    * Example return data: (will be ksorted)
       
  1050    <code>
       
  1051    array(
       
  1052        'bar' => 'folder',
       
  1053        'baz' => 'script',
       
  1054        'foo' => 'file'
       
  1055      );
       
  1056    </code>
       
  1057    * @param string Directory name, relative to the server's document root
       
  1058    * @param bool If true, sorts folders first (default: false)
       
  1059    * @return array Exception thrown on failure
       
  1060    */
       
  1061   
       
  1062   function list_directory($dir, $folders_first = false)
       
  1063   {
       
  1064     // clean slashes from the directory name
       
  1065     $dir = trim($dir, '/');
       
  1066     
       
  1067     if ( $dir == '' )
       
  1068     {
       
  1069       //
       
  1070       // list the root, which can consist only of handlers
       
  1071       //
       
  1072       
       
  1073       // copy the handlers array, which we need to ksort
       
  1074       $handlers = $this->handlers;
       
  1075       
       
  1076       // get rid of multi-depth handlers
       
  1077       foreach ( $handlers as $uri => $handler )
       
  1078       {
       
  1079         if ( strpos($uri, '/') )
       
  1080         {
       
  1081           unset($handlers[$uri]);
       
  1082           $newuri = explode('/', $uri);
       
  1083           if ( !isset($handlers[$newuri[0]]) )
       
  1084           {
       
  1085             $handlers[$newuri[0]] = array(
       
  1086                 'type' => 'folder'
       
  1087               );
       
  1088           }
       
  1089         }
       
  1090       }
       
  1091       
       
  1092       ksort($handlers);
       
  1093       
       
  1094       if ( $folders_first )
       
  1095       {
       
  1096         // sort folders first
       
  1097         $handlers_sorted = array();
       
  1098         foreach ( $handlers as $uri => $handler )
       
  1099         {
       
  1100           if ( $handler['type'] == 'folder' )
       
  1101             $handlers_sorted[$uri] = $handler;
       
  1102         }
       
  1103         foreach ( $handlers as $uri => $handler )
       
  1104         {
       
  1105           if ( $handler['type'] != 'folder' )
       
  1106             $handlers_sorted[$uri] = $handler;
       
  1107         }
       
  1108         $handlers = $handlers_sorted;
       
  1109         unset($handlers_sorted);
       
  1110       }
       
  1111       
       
  1112       // done
       
  1113       return $handlers;
       
  1114     }
       
  1115     else
       
  1116     {
       
  1117       // list something within the root
       
  1118       $dir_stack = explode('/', $dir);
       
  1119       
       
  1120       // lookup handler
       
  1121       $handler_search = $dir;
       
  1122       $found_handler = false;
       
  1123       $fake_handler = false;
       
  1124       $i = 1;
       
  1125       while ( $i > 0 )
       
  1126       {
       
  1127         if ( isset($this->handlers[$handler_search]) )
       
  1128         {
       
  1129           $found_handler = true;
       
  1130           break;
       
  1131         }
       
  1132         $i = strrpos($handler_search, '/');
       
  1133         $handler_search = substr($handler_search, 0, strrpos($handler_search, '/'));
       
  1134       }
       
  1135       if ( $this->check_for_handler_children($dir) )
       
  1136       {
       
  1137         $fake_handler = true;
       
  1138       }
       
  1139       else if ( !$found_handler )
       
  1140       {
       
  1141         // nope. not there.
       
  1142         throw new Exception("ERR_NO_SUCH_FILE_OR_DIRECTORY");
       
  1143       }
       
  1144       
       
  1145       // make sure this is a directory
       
  1146       if ( !$fake_handler )
       
  1147       {
       
  1148         $handler =& $handler_search;
       
  1149         if ( $this->handlers[$handler]['type'] != 'folder' )
       
  1150         {
       
  1151           throw new Exception("ERR_NOT_A_DIRECTORY");
       
  1152         }
       
  1153         
       
  1154         // determine real path
       
  1155         $real_path = realpath($this->handlers[$handler]['dir'] . substr($dir, strlen($handler)));
       
  1156         
       
  1157         // directory is resolved; list contents
       
  1158         $dir_contents = array();
       
  1159         
       
  1160         if ( $dr = opendir($real_path) )
       
  1161         {
       
  1162           while ( $dh = readdir($dr) )
       
  1163           {
       
  1164             if ( $dh == '.' || $dh == '..' )
       
  1165             {
       
  1166               continue;
       
  1167             }
       
  1168             $dir_contents[$dh] = array(
       
  1169                 'type' => ( is_dir("$real_path/$dh") ) ? 'folder' : 'file',
       
  1170                 'size' => filesize("$real_path/$dh"),
       
  1171                 'time' => filemtime("$real_path/$dh")
       
  1172               );
       
  1173           }
       
  1174         }
       
  1175         else
       
  1176         {
       
  1177           // only if directory open failed
       
  1178           throw new Exception("ERR_PERMISSION_DENIED");
       
  1179         }
       
  1180         
       
  1181         closedir($dr);
       
  1182       
       
  1183         // some cleanup
       
  1184         unset($handler, $handler_search);
       
  1185       }
       
  1186       
       
  1187       // list any additional handlers in there
       
  1188       foreach ( $this->handlers as $handler => $info )
       
  1189       {
       
  1190         // parse handler name
       
  1191         $handler_name = explode('/', trim($handler, '/'));
       
  1192         // is this handler in this directory?
       
  1193         if ( count($handler_name) != count($dir_stack) + 1 )
       
  1194         {
       
  1195           continue;
       
  1196         }
       
  1197         foreach ( $dir_stack as $i => $_ )
       
  1198         {
       
  1199           if ( $dir_stack[$i] != $handler_name[$i] )
       
  1200           {
       
  1201             continue 2;
       
  1202           }
       
  1203         }
       
  1204         // it's in here!
       
  1205         $dir_contents[ basename($handler) ] = array(
       
  1206             'type' => $info['type']
       
  1207           );
       
  1208       }
       
  1209       
       
  1210       // list "fake" handlers
       
  1211       foreach ( $this->handlers as $handler => $info )
       
  1212       {
       
  1213         // parse handler name
       
  1214         $handler_name = explode('/', trim($handler, '/'));
       
  1215         // is this handler somewhere underneath this directory?
       
  1216         if ( count($handler_name) < count($dir_stack) + 2 )
       
  1217         {
       
  1218           continue;
       
  1219         }
       
  1220         // path check
       
  1221         foreach ( $dir_stack as $i => $_ )
       
  1222         {
       
  1223           if ( $dir_stack[$i] != $handler_name[$i] )
       
  1224           {
       
  1225             continue 2;
       
  1226           }
       
  1227         }
       
  1228         // create a "fake" directory
       
  1229         $fakie_name = $handler_name[ count($dir_stack) ];
       
  1230         $dir_contents[$fakie_name] = array(
       
  1231             'type' => 'folder'
       
  1232           );
       
  1233       }
       
  1234       
       
  1235       if ( $folders_first )
       
  1236       {
       
  1237         // perform folder sorting
       
  1238         $unsorted = $dir_contents;
       
  1239         ksort($unsorted);
       
  1240         $dir_contents = array();
       
  1241         foreach ( $unsorted as $name => $info )
       
  1242         {
       
  1243           if ( $info['type'] == 'folder' )
       
  1244             $dir_contents[$name] = $info;
       
  1245         }
       
  1246         foreach ( $unsorted as $name => $info )
       
  1247         {
       
  1248           if ( $info['type'] != 'folder' )
       
  1249             $dir_contents[$name] = $info;
       
  1250         }
       
  1251       }
       
  1252       else
       
  1253       {
       
  1254         // not sorting with folders first, so just alphabetize
       
  1255         ksort($dir_contents);
       
  1256       }
       
  1257       
       
  1258       // done
       
  1259       
       
  1260       return $dir_contents;
       
  1261     }
       
  1262   }
       
  1263   
       
  1264   /**
       
  1265    * Searches deeper to see if there are sub-handlers within a path to see if a fake handler can be created
       
  1266    * @param string URI
       
  1267    * @return bool
       
  1268    */
       
  1269   
       
  1270   function check_for_handler_children($file_path)
       
  1271   {
       
  1272     $file_path = trim($file_path, '/');
       
  1273     $dir_stack = explode('/', $file_path);
       
  1274     
       
  1275     // make sure this isn't a "real" handler
       
  1276     if ( isset($this->handlers[$file_path]) )
       
  1277     {
       
  1278       return false;
       
  1279     }
       
  1280     
       
  1281     // list any additional handlers in there
       
  1282     foreach ( $this->handlers as $handler => $info )
       
  1283     {
       
  1284       // parse handler name
       
  1285       $handler_name = explode('/', trim($handler, '/'));
       
  1286       // is this handler in this directory?
       
  1287       if ( count($handler_name) != count($dir_stack) + 1 )
       
  1288       {
       
  1289         continue;
       
  1290       }
       
  1291       foreach ( $dir_stack as $i => $_ )
       
  1292       {
       
  1293         if ( $dir_stack[$i] != $handler_name[$i] )
       
  1294         {
       
  1295           continue 2;
       
  1296         }
       
  1297       }
       
  1298       // it's in here!
       
  1299       return true;
       
  1300     }
       
  1301     
       
  1302     return false;
   710   }
  1303   }
   711   
  1304   
   712 }
  1305 }
   713 
  1306 
   714 /**
  1307 /**