webserver.php
changeset 37 65e70ada71c9
parent 34 3b817b961984
child 38 87fe0dec1536
equal deleted inserted replaced
36:70ef461bbffa 37:65e70ada71c9
    34 define('HTTPD_ICON_SCRIPT', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGSSURBVCjPVVFNSwJhEF78Ad79Cf6PvXQRsotUlzKICosuRYmR2RJR0KE6lBFFZVEbpFBSqKu2rum6llFS9HHI4iUhT153n6ZtIWMOM+/MM88z7wwH7s9Ub16SJcnbmrNcxVm2q7Z8/QPvEOtntpj92NkCqITLepEpjix7xQtiLOoQ2b6+E7YAN/5nfOEJ2WbKqOIOJ4bYVMEQx4LfBBQDsvFMhUcCVU1/CxVXmDBGA5ZETrhDCQVcYAPbyEJBhvrnBVPiSpNr6cYDNCQwo4zzU/ySckkgDYuNuVpI42T9k4gLKGMPs/xPzzovQiY2hQYe0jlJfyNNhTqiWDYBq/wBMcSRpnyPzu1oS7WtxjVBSthU1vgVksiQ3Dn6Gp5ah2YOKQo5GiuHPA6xT1EKpxQNCNYejgIR457KKio0S56YckjSa9jo//3mrj+BV0QQagqGTOo+Y7gZIf1puP3WHoLhEb2PjTlCTCWGXtbp8DCX3hZuOdaIc9A+aQvWk4ihq95p67a7nP+u+Ws+r0dql9z/zv0NCYhdCPKZ7oYAAAAASUVORK5CYII=');
    34 define('HTTPD_ICON_SCRIPT', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGSSURBVCjPVVFNSwJhEF78Ad79Cf6PvXQRsotUlzKICosuRYmR2RJR0KE6lBFFZVEbpFBSqKu2rum6llFS9HHI4iUhT153n6ZtIWMOM+/MM88z7wwH7s9Ub16SJcnbmrNcxVm2q7Z8/QPvEOtntpj92NkCqITLepEpjix7xQtiLOoQ2b6+E7YAN/5nfOEJ2WbKqOIOJ4bYVMEQx4LfBBQDsvFMhUcCVU1/CxVXmDBGA5ZETrhDCQVcYAPbyEJBhvrnBVPiSpNr6cYDNCQwo4zzU/ySckkgDYuNuVpI42T9k4gLKGMPs/xPzzovQiY2hQYe0jlJfyNNhTqiWDYBq/wBMcSRpnyPzu1oS7WtxjVBSthU1vgVksiQ3Dn6Gp5ah2YOKQo5GiuHPA6xT1EKpxQNCNYejgIR457KKio0S56YckjSa9jo//3mrj+BV0QQagqGTOo+Y7gZIf1puP3WHoLhEb2PjTlCTCWGXtbp8DCX3hZuOdaIc9A+aQvWk4ihq95p67a7nP+u+Ws+r0dql9z/zv0NCYhdCPKZ7oYAAAAASUVORK5CYII=');
    35 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');
    35 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');
    36 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=');
    36 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=');
    37 
    37 
    38 /**
    38 /**
       
    39  * Abstraction layer to use
       
    40  */
       
    41 
       
    42 define('HTTPD_SOCKET_LAYER', 'Stream');
       
    43 
       
    44 /**
    39  * Simple but full-featured embedded web server written in PHP.
    45  * Simple but full-featured embedded web server written in PHP.
    40  * @package Amarok
    46  * @package Amarok
    41  * @subpackage WebControl
    47  * @subpackage WebControl
    42  * @author Dan Fuhry
    48  * @author Dan Fuhry
    43  * @license GNU General Public License <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
    49  * @license GNU General Public License <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
    52    */
    58    */
    53   
    59   
    54   var $bind_address = '127.0.0.1';
    60   var $bind_address = '127.0.0.1';
    55   
    61   
    56   /**
    62   /**
    57    * Socket resource
    63    * Socket abstraction object
    58    * @var resource
    64    * @var object
    59    */
    65    */
    60   
    66   
    61   var $sock = null;
    67   var $server = null;
    62   
    68   
    63   /**
    69   /**
    64    * Server string
    70    * Server string
    65    * @var string
    71    * @var string
    66    */
    72    */
   170    * The parent process's PID
   176    * The parent process's PID
   171    * @var int
   177    * @var int
   172    */
   178    */
   173   
   179   
   174   var $parent_pid = 0;
   180   var $parent_pid = 0;
       
   181   
       
   182   /**
       
   183    * Sockets for parent and child to communicate
       
   184    * @var resource
       
   185    * @var resource
       
   186    */
       
   187   
       
   188   var $parent_sock = null;
       
   189   var $child_sock = null;
   175   
   190   
   176   /**
   191   /**
   177    * Constructor.
   192    * Constructor.
   178    * @param string IPv4 address to bind to
   193    * @param string IPv4 address to bind to
   179    * @param int Port number
   194    * @param int Port number
   242         burnout("Must be superuser to bind to ports below 1024");
   257         burnout("Must be superuser to bind to ports below 1024");
   243       }
   258       }
   244     }
   259     }
   245     $socket_do_root = ( $allow_root ) ? function_exists('posix_geteuid') : false;
   260     $socket_do_root = ( $allow_root ) ? function_exists('posix_geteuid') : false;
   246     
   261     
   247     $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
   262     $class = 'Socket_' . HTTPD_SOCKET_LAYER;
   248     if ( !$this->sock )
   263     $this->server = new $class();
   249       throw new Exception('Could not create socket');
   264     $this->server->tcp_listen($address, $port);
   250     $result = @socket_bind($this->sock, $address, $port);
       
   251     if ( !$result )
       
   252       throw new Exception("Could not bind to $address:$port");
       
   253     $this->socket_initted = true;
       
   254     $result = @socket_listen($this->sock, SOMAXCONN);
       
   255     if ( !$result )
       
   256       throw new Exception("Could not listen for connections $address:$port");
       
   257     
   265     
   258     // if running as root and we made it here, switch credentials
   266     // if running as root and we made it here, switch credentials
   259     if ( $socket_do_root )
   267     if ( $socket_do_root )
   260     {
   268     {
   261       posix_setuid($targetuser);
   269       posix_setuid($targetuser);
   275     $this->uuid = substr($uuid_base, 0,  8) . '-' .
   283     $this->uuid = substr($uuid_base, 0,  8) . '-' .
   276                   substr($uuid_base, 8,  4) . '-' .
   284                   substr($uuid_base, 8,  4) . '-' .
   277                   substr($uuid_base, 12, 4) . '-' .
   285                   substr($uuid_base, 12, 4) . '-' .
   278                   substr($uuid_base, 16, 4) . '-' .
   286                   substr($uuid_base, 16, 4) . '-' .
   279                   substr($uuid_base, 20, 20);
   287                   substr($uuid_base, 20, 20);
       
   288                   
   280   }
   289   }
   281   
   290   
   282   /**
   291   /**
   283    * Destructor.
   292    * Destructor.
   284    */
   293    */
   287   {
   296   {
   288     if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted )
   297     if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted )
   289     {
   298     {
   290       if ( function_exists('status') )
   299       if ( function_exists('status') )
   291         status('WebServer: destroying socket');
   300         status('WebServer: destroying socket');
   292       // http://us3.php.net/manual/en/function.socket-bind.php
   301       $this->server->destroy();
   293       if ( !@socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1) )
   302       
   294       {
   303       // tell all children to shut down
   295         echo socket_strerror(socket_last_error($sock)) . "\n";
   304       if ( $this->allow_fork )
   296       }
   305       {
   297       @socket_shutdown($this->sock, 2);
   306         if ( function_exists('status') )
   298       @socket_close($this->sock);
   307           status('WebServer: asking all children to exit');
       
   308         $this->send_ipc_event("die _");
       
   309       }
       
   310       
       
   311       // that last operation should have been asynchronous, so shut everything down now
       
   312       @socket_shutdown($this->parent_sock);
       
   313       @socket_close($this->parent_sock);
       
   314     }
       
   315     else if ( defined('HTTPD_WS_CHILD') )
       
   316     {
       
   317       @socket_shutdown($this->child_sock);
       
   318       @socket_close($this->child_sock);
   299     }
   319     }
   300   }
   320   }
   301   
   321   
   302   /**
   322   /**
   303    * Main server loop
   323    * Main server loop
   304    */
   324    */
   305   
   325   
   306   function serve()
   326   function serve()
   307   {
   327   {
       
   328     // If we're allowed to use multithreading, set up to handle SIGUSR2 which waits on the child
       
   329     if ( function_exists('pcntl_signal') && $this->allow_fork )
       
   330     {
       
   331       // required for signal handling to work
       
   332       declare(ticks=1);
       
   333       
       
   334       // trap SIGTERM
       
   335       pcntl_signal(SIGUSR2, array(&$this, '_ipc_event'));
       
   336       
       
   337       if ( !($sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP)) )
       
   338       {
       
   339         throw new Exception("Could not set up private IPC socket. Reason: " . socket_strerror(socket_last_error()));
       
   340       }
       
   341       
       
   342       $this->parent_sock =& $sockets[0];
       
   343       $this->child_sock =& $sockets[1];
       
   344     }
       
   345     
   308     while ( true )
   346     while ( true )
   309     {
   347     {
   310       // if this is a child process, we're finished - close up shop
   348       // if this is a child process, we're finished - close up shop
   311       if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
   349       if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
   312       {
   350       {
   313         if ( function_exists('status') )
   351         if ( function_exists('status') )
   314           status('Exiting child process');
   352           status('Exiting child process');
   315         @socket_shutdown($remote);
   353         
   316         @socket_close($remote);
   354         $remote->destroy();
       
   355         
       
   356         // let the parent know that we're out of here
       
   357         $this->send_ipc_event("exit " . getmypid());
       
   358         
       
   359         // bye
   317         exit(0);
   360         exit(0);
   318       }
   361       }
   319       
   362       
   320       // wait for connection...
   363       // wait for connection...
   321       // trick from http://us.php.net/manual/en/function.socket-accept.php
   364       // trick from http://us.php.net/manual/en/function.socket-accept.php
   322       if ( !defined('HTTPD_WS_CHILD') )
   365       if ( !defined('HTTPD_WS_CHILD') )
   323       {
   366       {
   324         $remote = false;
   367         $remote = $this->server->accept();
   325         $timeout = 5;
   368       }
   326         switch(@socket_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout)) {
   369       
   327           case 2:
       
   328             break;
       
   329           case 1:
       
   330             $remote = @socket_accept($this->sock);
       
   331             break;
       
   332           case 0:
       
   333             break;
       
   334         }
       
   335       }
       
   336          
       
   337       if ( !$remote )
   370       if ( !$remote )
   338       {
   371       {
   339         $this->in_keepalive = false;
   372         $this->in_keepalive = false;
   340         continue;
   373         continue;
   341       }
   374       }
   349           // do nothing; continue responding to request in single-threaded mode
   382           // do nothing; continue responding to request in single-threaded mode
   350         }
   383         }
   351         else if ( $pid )
   384         else if ( $pid )
   352         {
   385         {
   353           // we are the parent, continue listening
   386           // we are the parent, continue listening
   354           socket_close($remote);
   387           $remote->soft_shutdown();
   355           $this->child_list[] = $pid;
   388           $this->child_list[] = $pid;
   356           continue;
   389           continue;
   357         }
   390         }
   358         else
   391         else
   359         {
   392         {
   360           // this is the child
   393           // this is the child
   361           define('HTTPD_WS_CHILD', 1);
   394           define('HTTPD_WS_CHILD', 1);
   362           @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
   395           
   363           socket_close($this->sock);
   396           // setup to handle signals
       
   397           if ( function_exists('pcntl_signal') )
       
   398           {
       
   399             // required for signal handling to work
       
   400             declare(ticks=1);
       
   401             
       
   402             // trap SIGTERM
       
   403             pcntl_signal(SIGUSR2, array(&$this, '_ipc_event'));
       
   404           }
   364         }
   405         }
   365       }
   406       }
   366       
   407       
   367       $this->in_keepalive = false;
   408       $this->in_keepalive = false;
   368       $this->headers_sent = false;
   409       $this->headers_sent = false;
   369       $this->in_scriptlet = false;
   410       $this->in_scriptlet = false;
   370       
   411       
   371       // read request
   412       //
       
   413       // READ THE REQUEST
       
   414       //
       
   415       
       
   416       // this is a complicated situation because we need to keep enough ticks going to properly handle
       
   417       // signals, meaning we can't use stream_set_timeout() and instead need to rely on our own timing
       
   418       // logic. setting the timeout to a short period, say 200,000 usec, we can minimize CPU usage and
       
   419       // have a good response time.
       
   420       
       
   421       $remote->set_timeout(0, 200000);
       
   422       $start_time = microtime(true);
       
   423       $client_headers = '';
       
   424       $last_line = '';
       
   425       while ( true )
       
   426       {
       
   427         if ( $start_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) || $remote->is_eof() )
       
   428         {
       
   429           // request expired -- end the process here
       
   430           if ( !defined('HTTPD_WS_CHILD') )
       
   431             $remote->destroy();
       
   432           
       
   433           continue 2;
       
   434         }
       
   435         $line = strval($remote->read_normal());
       
   436         $line = str_replace("\r", "", $line);
       
   437         
       
   438         // raw layer wants to send 2 empty lines through, stream layer wants to send one.
       
   439         $last_line_check = ( HTTPD_SOCKET_LAYER != 'Raw' || ( HTTPD_SOCKET_LAYER == 'Raw' && $last_line == "\n" ) );
       
   440         if ( $line == "\n" && $last_line_check )
       
   441           // we have two newlines in a row, break out since we have a full request
       
   442           break;
       
   443           
       
   444         $client_headers .= $line;
       
   445         $last_line = $line;
       
   446       }
       
   447       
       
   448       /*
   372       $last_line = '';
   449       $last_line = '';
   373       $client_headers = '';
   450       $client_headers = '';
   374       if ( defined('HTTPD_WS_CHILD') )
   451       if ( defined('HTTPD_WS_CHILD') )
   375       {
   452       {
   376         @socket_set_timeout($remote, HTTPD_KEEP_ALIVE_TIMEOUT);
   453         $remote->set_timeout(1);
   377       }
   454       }
   378       if ( $line = @socket_read($remote, 1024, PHP_NORMAL_READ) )
   455       if ( $line = $remote->read_normal() )
   379       {
   456       {
   380         do
   457         do
   381         {
   458         {
   382           $line = str_replace("\r", "", $line);
   459           $line = str_replace("\r", "", $line);
   383           if ( empty($line) )
   460           if ( empty($line) )
   384             continue;
   461             continue;
   385           if ( $line == "\n" && $last_line == "\n" )
   462           $last_line_check = ( HTTPD_SOCKET_LAYER != 'Raw' || ( HTTPD_SOCKET_LAYER == 'Raw' && $last_line == "\n" ) );
       
   463           if ( $line == "\n" && $last_line_check )
   386             break;
   464             break;
   387           $client_headers .= $line;
   465           $client_headers .= $line;
   388           $last_line = $line;
   466           $last_line = $line;
   389         }
   467           $line = $remote->read_normal()
   390         while ( $line = @socket_read($remote, 1024, PHP_NORMAL_READ) );
   468         }
       
   469         while ( true );
   391       }
   470       }
   392       else
   471       else
   393       {
   472       {
   394         if ( defined('HTTPD_WS_CHILD') )
   473         if ( defined('HTTPD_WS_CHILD') )
   395         {
   474         {
   396           $md = @socket_get_status($remote);
   475           if ( $remote->timed_out() )
   397           if ( @$md['timed_out'] )
       
   398           {
   476           {
   399             status('[debug] keep-alive connection timed out');
   477             status('[debug] keep-alive connection timed out');
   400             continue; // will jump back to the start of the loop and kill the child process
   478             continue; // will jump back to the start of the loop and kill the child process
   401           }
   479           }
   402         }
   480         }
   403       }
   481       }
       
   482       */
   404       
   483       
   405       // parse request
   484       // parse request
   406       $client_headers = trim($client_headers);
   485       $client_headers = trim($client_headers);
   407       
   486       
   408       if ( isset($last_finish_time) && empty($client_headers) && defined('HTTPD_WS_CHILD') && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) )
   487       if ( isset($last_finish_time) && empty($client_headers) && defined('HTTPD_WS_CHILD') && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) )
   465           // this is a multipart request
   544           // this is a multipart request
   466           $boundary =& $match[1];
   545           $boundary =& $match[1];
   467           $mode = 'data';
   546           $mode = 'data';
   468           $last_line = '';
   547           $last_line = '';
   469           $i = 0;
   548           $i = 0;
   470           while ( $data = socket_read($remote, 8388608, PHP_NORMAL_READ) )
   549           while ( $data = $remote->read_normal(8388608) )
   471           {
   550           {
   472             $data_trim = trim($data, "\r\n");
   551             $data_trim = trim($data, "\r\n");
   473             if ( $mode != 'data' )
   552             if ( $mode != 'data' )
   474             {
   553             {
   475               $data = str_replace("\r", '', $data);
   554               $data = str_replace("\r", '', $data);
   567         }
   646         }
   568         else
   647         else
   569         {
   648         {
   570           if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) )
   649           if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) )
   571           {
   650           {
   572             $postdata = socket_read($remote, intval($_SERVER['HTTP_CONTENT_LENGTH']), PHP_BINARY_READ);
   651             $postdata = $remote->read_binary(intval($_SERVER['HTTP_CONTENT_LENGTH']));
   573           }
   652           }
   574           else
   653           else
   575           {
   654           {
   576             $postdata = socket_read($remote, 8388608, PHP_NORMAL_READ);
   655             $postdata = $remote->read_normal(8388608);
   577           }
   656           }
   578           if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $postdata, $matches) )
   657           if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $postdata, $matches) )
   579           {
   658           {
   580             if ( isset($matches[1]) )
   659             if ( isset($matches[1]) )
   581             {
   660             {
   599       // set some server vars
   678       // set some server vars
   600       $_SERVER['REQUEST_URI'] = '/' . rawurldecode($uri);
   679       $_SERVER['REQUEST_URI'] = '/' . rawurldecode($uri);
   601       $_SERVER['REQUEST_METHOD'] = $method;
   680       $_SERVER['REQUEST_METHOD'] = $method;
   602       
   681       
   603       // get remote IP and port
   682       // get remote IP and port
   604       socket_getpeername($remote, $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
   683       $remote->get_peer_info($_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
   605       
   684       
   606       $_GET = array();
   685       $_GET = array();
   607       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) )
   686       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) )
   608       {
   687       {
   609         if ( isset($matches[1]) )
   688         if ( isset($matches[1]) )
   689         }
   768         }
   690       }
   769       }
   691       
   770       
   692       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   771       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   693       {
   772       {
   694         // if ( defined('HTTPD_WS_CHILD') )
   773         // connection: close
   695         //   status('Closing connection');
   774         // continue on to the shutdown handler
   696         @socket_shutdown($remote);
   775         continue;
   697         @socket_close($remote);
       
   698         if ( function_exists('status') )
       
   699           status('Exiting child process');
       
   700         exit(0);
       
   701       }
   776       }
   702       else if ( defined('HTTPD_WS_CHILD') )
   777       else if ( defined('HTTPD_WS_CHILD') )
   703       {
   778       {
   704         // if ( defined('HTTPD_WS_CHILD') )
   779         // if ( defined('HTTPD_WS_CHILD') )
   705         //   status('Continuing connection');
   780         //   status('Continuing connection');
   706         // @socket_write($remote, "\r\n\r\n");
   781         // $remote->write("\r\n\r\n");
   707         $last_finish_time = microtime(true);
   782         $last_finish_time = microtime(true);
   708       }
   783       }
   709       else
   784       else
   710       {
   785       {
   711         @socket_shutdown($remote);
   786         $remote->destroy();
   712         @socket_close($remote);
       
   713       }
   787       }
   714     }
   788     }
   715   }
   789   }
   716   
   790   
   717   /**
   791   /**
   741     $headers = str_replace("\r\n", "\n", $headers);
   815     $headers = str_replace("\r\n", "\n", $headers);
   742     $headers = str_replace("\n", "\r\n", $headers);
   816     $headers = str_replace("\n", "\r\n", $headers);
   743     $headers = preg_replace("#[\r\n]+$#", '', $headers);
   817     $headers = preg_replace("#[\r\n]+$#", '', $headers);
   744     $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close';
   818     $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close';
   745     
   819     
   746     @socket_write($socket, "HTTP/1.1 $http_code $reason_code\r\n");
   820     $socket->write("HTTP/1.1 $http_code $reason_code\r\n");
   747     @socket_write($socket, "Server: $this->server_string");
   821     $socket->write("Server: $this->server_string");
   748     @socket_write($socket, "Connection: $connection\r\n");
   822     $socket->write("Connection: $connection\r\n");
   749     @socket_write($socket, "Content-Type: $contenttype\r\n");
   823     $socket->write("Content-Type: $contenttype\r\n");
   750     if ( !empty($headers) )
   824     if ( !empty($headers) )
   751     {
   825     {
   752       @socket_write($socket, "$headers\r\n");
   826       $socket->write("$headers\r\n");
   753     }
   827     }
   754     @socket_write($socket, "\r\n");
   828     $socket->write("\r\n");
   755   }
   829   }
   756   
   830   
   757   /**
   831   /**
   758    * Sends a normal response
   832    * Sends a normal response
   759    * @param resource Socket connection to client
   833    * @param resource Socket connection to client
   829             $contents .= "\n    </ul>\n    <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n";
   903             $contents .= "\n    </ul>\n    <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n";
   830             
   904             
   831             $sz = strlen($contents);
   905             $sz = strlen($contents);
   832             $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n");
   906             $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n");
   833             
   907             
   834             @socket_write($socket, $contents);
   908             $socket->write($contents);
   835             
   909             
   836             return true;
   910             return true;
   837           }
   911           }
   838           
   912           
   839           // try to open the file
   913           // try to open the file
   856           $this->send_client_headers($socket, 200, $mimetype, "Content-length: $sz\r\nLast-Modified: $time\r\n");
   930           $this->send_client_headers($socket, 200, $mimetype, "Content-length: $sz\r\nLast-Modified: $time\r\n");
   857           
   931           
   858           // send body
   932           // send body
   859           while ( $blk = @fread($fh, 768000) )
   933           while ( $blk = @fread($fh, 768000) )
   860           {
   934           {
   861             @socket_write($socket, $blk);
   935             $socket->write($blk);
   862           }
   936           }
   863           fclose($fh);
   937           fclose($fh);
   864           return true;
   938           return true;
   865         }
   939         }
   866         else
   940         else
   907           $this->send_client_headers($socket, 200, $mimetype, "Content-length: $sz\r\nLast-Modified: $time\r\n");
   981           $this->send_client_headers($socket, 200, $mimetype, "Content-length: $sz\r\nLast-Modified: $time\r\n");
   908           
   982           
   909           // send body
   983           // send body
   910           while ( $blk = @fread($fh, 768000) )
   984           while ( $blk = @fread($fh, 768000) )
   911           {
   985           {
   912             @socket_write($socket, $blk);
   986             $socket->write($blk);
   913           }
   987           }
   914           fclose($fh);
   988           fclose($fh);
   915           return true;
   989           return true;
   916         }
   990         }
   917         else
   991         else
   981         
  1055         
   982         // chunk output
  1056         // chunk output
   983         // $output = dechex(strlen($output)) . "\r\n$output";
  1057         // $output = dechex(strlen($output)) . "\r\n$output";
   984         
  1058         
   985         // write body
  1059         // write body
   986         @socket_write($socket, $output);
  1060         $socket->write($output);
   987         
  1061         
   988         $this->headers_sent = false;
  1062         $this->headers_sent = false;
   989         
  1063         
   990         break;
  1064         break;
   991       case 'sysuuid':
  1065       case 'sysuuid':
  1054         if ( $found )
  1128         if ( $found )
  1055         {
  1129         {
  1056           $lm_date = date('r', filemtime(__FILE__));
  1130           $lm_date = date('r', filemtime(__FILE__));
  1057           $size = strlen($image_data);
  1131           $size = strlen($image_data);
  1058           $this->send_client_headers($socket, 200, $type, "Last-Modified: $lm_date\r\nContent-Length: $size");
  1132           $this->send_client_headers($socket, 200, $type, "Last-Modified: $lm_date\r\nContent-Length: $size");
  1059           @socket_write($socket, $image_data);
  1133           $socket->write($image_data);
  1060         }
  1134         }
  1061         else
  1135         else
  1062         {
  1136         {
  1063           $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
  1137           $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server.");
  1064         }
  1138         }
  1124         
  1198         
  1125         // send headers
  1199         // send headers
  1126         $this->send_client_headers($socket, 200, 'text/html', "Content-Length: $len");
  1200         $this->send_client_headers($socket, 200, 'text/html', "Content-Length: $len");
  1127         
  1201         
  1128         // write to the socket
  1202         // write to the socket
  1129         @socket_write($socket, $contents);
  1203         $socket->write($contents);
  1130         
  1204         
  1131         return true;
  1205         return true;
  1132         break;
  1206         break;
  1133     }
  1207     }
  1134   }
  1208   }
  1189       $headers = implode("\r\n", $this->response_headers);
  1263       $headers = implode("\r\n", $this->response_headers);
  1190     else
  1264     else
  1191       $headers = 'Content-length: ' . strlen($html);
  1265       $headers = 'Content-length: ' . strlen($html);
  1192       
  1266       
  1193     $this->send_client_headers($socket, $http_code, 'text/html', $headers);
  1267     $this->send_client_headers($socket, $http_code, 'text/html', $headers);
  1194     @socket_write($socket, $html);
  1268     $socket->write($html);
  1195   } 
  1269   } 
  1196   
  1270   
  1197   /**
  1271   /**
  1198    * Adds a new handler
  1272    * Adds a new handler
  1199    * @param string URI, minus the initial /
  1273    * @param string URI, minus the initial /
  1526       }
  1600       }
  1527     }
  1601     }
  1528     return $array;
  1602     return $array;
  1529   }
  1603   }
  1530   
  1604   
       
  1605   /**
       
  1606    * Handle an IPC event. Called only upon SIGUSR2.
       
  1607    */
       
  1608   
       
  1609   function _ipc_event()
       
  1610   {
       
  1611     $pid = getmypid() . ':' . $this->parent_pid;
       
  1612     
       
  1613     // decide which socket to use
       
  1614     if ( defined('HTTPD_WS_CHILD') )
       
  1615       $sock =& $this->parent_sock;
       
  1616     else
       
  1617       $sock =& $this->child_sock;
       
  1618     
       
  1619     // try to read the event
       
  1620     // this sometimes gets hung up because socket_set_timeout() doesn't seem to work on its own set of
       
  1621     // functions (it only works on PHP's normal streams)
       
  1622     if ( $line = @fgets($sock, 1024) )
       
  1623     {
       
  1624       $line = trim($line);
       
  1625       list($action, $param) = explode(' ', $line);
       
  1626       switch($action)
       
  1627       {
       
  1628         case 'exit':
       
  1629           // this is to prevent zombie children
       
  1630           pcntl_waitpid(intval($param), $status);
       
  1631           // we know this child is dead now, remove them from the list
       
  1632           foreach ( $this->child_list as $i => $pid )
       
  1633           {
       
  1634             if ( $pid === intval($param) )
       
  1635             {
       
  1636               unset($this->child_list[$i]);
       
  1637               $this->child_list = array_values($this->child_list);
       
  1638               break;
       
  1639             }
       
  1640           }
       
  1641           break;
       
  1642         case 'die':
       
  1643           // only do this if this is a child (both security and design)
       
  1644           if ( defined('HTTPD_WS_CHILD') )
       
  1645           {
       
  1646             if ( function_exists('status') )
       
  1647             {
       
  1648               status('Received shutdown request, complying');
       
  1649             }
       
  1650             $this->send_ipc_event("exit " . getmypid());
       
  1651             exit(0);
       
  1652           }
       
  1653           break;
       
  1654         default:
       
  1655           break;
       
  1656       }
       
  1657     }
       
  1658   }
       
  1659   
       
  1660   /**
       
  1661    * Send an IPC event.
       
  1662    * @param string Data to write to the socket, newline will be added automatically
       
  1663    */
       
  1664   
       
  1665   function send_ipc_event($data)
       
  1666   {
       
  1667     if ( defined('HTTPD_WS_CHILD') )
       
  1668       $sock =& $this->parent_sock;
       
  1669     else
       
  1670       $sock =& $this->child_sock;
       
  1671       
       
  1672     $data = rtrim($data, "\r\n") . "\n";
       
  1673     @fwrite($sock, $data);
       
  1674     
       
  1675     // if we're a child, signal the parent
       
  1676     if ( defined('HTTPD_WS_CHILD') )
       
  1677     {
       
  1678       posix_kill($this->parent_pid, SIGUSR2);
       
  1679     }
       
  1680     // if we're the parent, signal all children
       
  1681     else
       
  1682     {
       
  1683       foreach ( $this->child_list as $pid )
       
  1684       {
       
  1685         posix_kill($pid, SIGUSR2);
       
  1686       }
       
  1687     }
       
  1688   }
       
  1689   
       
  1690 }
       
  1691 
       
  1692 /**
       
  1693  * Socket abstraction layer - low-level socket functions (socket_*)
       
  1694  */
       
  1695 
       
  1696 class Socket_Raw
       
  1697 {
       
  1698   var $sock;
       
  1699   var $socket_initted = false;
       
  1700   
       
  1701   function tcp_listen($address, $port)
       
  1702   {
       
  1703     $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
       
  1704     if ( !$this->sock )
       
  1705       throw new Exception('Could not create socket');
       
  1706     $result = @socket_bind($this->sock, $address, $port);
       
  1707     if ( !$result )
       
  1708       throw new Exception("Could not bind to $address:$port");
       
  1709     $this->socket_initted = true;
       
  1710     $result = @socket_listen($this->sock, SOMAXCONN);
       
  1711     if ( !$result )
       
  1712       throw new Exception("Could not listen for connections $address:$port");
       
  1713     
       
  1714     $this->socket_initted = true;
       
  1715   }
       
  1716   
       
  1717   function destroy()
       
  1718   {
       
  1719     if ( $this->socket_initted )
       
  1720     {
       
  1721       // http://us3.php.net/manual/en/function.socket-bind.php
       
  1722       if ( !@socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1) )
       
  1723       {
       
  1724         echo socket_strerror(socket_last_error($this->sock)) . "\n";
       
  1725       }
       
  1726       @socket_shutdown($this->sock, 2);
       
  1727       @socket_close($this->sock);
       
  1728     }
       
  1729   }
       
  1730   
       
  1731   function accept()
       
  1732   {
       
  1733     $remote = false;
       
  1734     $timeout = 5;
       
  1735     switch(@socket_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout)) {
       
  1736       case 2:
       
  1737         return false;
       
  1738       case 1:
       
  1739         $remote = @socket_accept($this->sock);
       
  1740         $return = new Socket_Raw();
       
  1741         $return->sock = $remote;
       
  1742         $return->socket_initted = true;
       
  1743         return $return;
       
  1744         break;
       
  1745       case 0:
       
  1746         return false;
       
  1747     }
       
  1748   }
       
  1749   
       
  1750   /**
       
  1751    * Closes the socket but doesn't destroy it.
       
  1752    */
       
  1753   
       
  1754   function soft_shutdown()
       
  1755   {
       
  1756     @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
       
  1757     socket_close($this->sock);
       
  1758   }
       
  1759   
       
  1760   function set_timeout($timeout, $usec = false)
       
  1761   {
       
  1762     // doesn't work in this.
       
  1763   }
       
  1764   
       
  1765   function read_normal($length = 1024)
       
  1766   {
       
  1767     return @socket_read($this->sock, $length, PHP_NORMAL_READ);
       
  1768   }
       
  1769   
       
  1770   function read_binary($length = 1024)
       
  1771   {
       
  1772     return @socket_read($this->sock, $length, PHP_BINARY_READ);
       
  1773   }
       
  1774   
       
  1775   function timed_out()
       
  1776   {
       
  1777     $md = @socket_get_status($this->sock);
       
  1778     return ( isset($md['timed_out']) ) ? $md['timed_out'] : false;
       
  1779   }
       
  1780   
       
  1781   function get_peer_info(&$addr, &$port)
       
  1782   {
       
  1783     socket_getpeername($this->sock, $addr, $port);
       
  1784   }
       
  1785   
       
  1786   function write($data)
       
  1787   {
       
  1788     return @socket_write($this->sock, $data);
       
  1789   }
       
  1790   
       
  1791   function is_eof()
       
  1792   {
       
  1793     // feof() not supported
       
  1794     return false;
       
  1795   }
       
  1796 }
       
  1797 
       
  1798 /**
       
  1799  * Socket abstraction layer - PHP stream support
       
  1800  */
       
  1801 
       
  1802 class Socket_Stream
       
  1803 {
       
  1804   var $sock;
       
  1805   var $socket_initted = false;
       
  1806   
       
  1807   function tcp_listen($address, $port)
       
  1808   {
       
  1809     $this->sock = @stream_socket_server("tcp://$address:$port", $errno, $errstr);
       
  1810     if ( !$this->sock )
       
  1811       throw new Exception("Could not create the socket: error $errno: $errstr");
       
  1812   }
       
  1813   
       
  1814   function destroy()
       
  1815   {
       
  1816     if ( $this->socket_initted )
       
  1817     {
       
  1818       // PHP >= 5.2.1
       
  1819       if ( function_exists('stream_socket_shutdown') )
       
  1820       {
       
  1821         @stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR);
       
  1822       }
       
  1823       fclose($this->sock);
       
  1824     }
       
  1825   }
       
  1826   
       
  1827   function accept()
       
  1828   {
       
  1829     // the goal of a custom accept() with *_select() is to tick every 5 seconds to allow signals.
       
  1830     stream_set_blocking($this->sock, 1);
       
  1831     $timeout = 5;
       
  1832     $selection = @stream_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout);
       
  1833     if ( !$selection )
       
  1834     {
       
  1835       return false;
       
  1836     }
       
  1837     $remote = stream_socket_accept($this->sock);
       
  1838     $return = new Socket_Stream();
       
  1839     $return->sock = $remote;
       
  1840     $return->socket_initted = true;
       
  1841     return $return;
       
  1842   }
       
  1843   
       
  1844   function soft_shutdown()
       
  1845   {
       
  1846     fclose($this->sock);
       
  1847   }
       
  1848   
       
  1849   function set_timeout($timeout, $usec = false)
       
  1850   {
       
  1851     return ( $usec ) ? @stream_set_timeout($this->sock, 0, $usec) : @stream_set_timeout($this->sock, $timeout);
       
  1852   }
       
  1853   
       
  1854   function read_normal($length = 1024)
       
  1855   {
       
  1856     return @fgets($this->sock, $length);
       
  1857   }
       
  1858   
       
  1859   function read_binary($length = 1024)
       
  1860   {
       
  1861     return @fread($this->sock, $length);
       
  1862   }
       
  1863   
       
  1864   function timed_out()
       
  1865   {
       
  1866     $md = @stream_get_meta_data($this->sock);
       
  1867     return ( isset($md['timed_out']) ) ? $md['timed_out'] : false;
       
  1868   }
       
  1869   
       
  1870   function get_peer_info(&$addr, &$port)
       
  1871   {
       
  1872     $peer = stream_socket_get_name($this->sock, true);
       
  1873     list($addr, $port) = explode(':', $peer);
       
  1874   }
       
  1875   
       
  1876   function write($data)
       
  1877   {
       
  1878     return @fwrite($this->sock, $data);
       
  1879   }
       
  1880   
       
  1881   function is_eof()
       
  1882   {
       
  1883     return feof($this->sock);
       
  1884   }
  1531 }
  1885 }
  1532 
  1886 
  1533 /**
  1887 /**
  1534  * Exception class that allows breaking directly out of a scriptlet.
  1888  * Exception class that allows breaking directly out of a scriptlet.
  1535  */
  1889  */