webserver.php
changeset 48 d643bfb862d8
parent 43 2634d550a97b
child 53 a6b339665650
equal deleted inserted replaced
47:b7f1952cef8d 48:d643bfb862d8
    11  *
    11  *
    12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
    12  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
    13  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
    13  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
    14  */
    14  */
    15 
    15 
       
    16 require('multithreading.php');
       
    17 
    16 /**
    18 /**
    17  * Version of the server
    19  * Version of the server
    18  * @const string
    20  * @const string
    19  */
    21  */
    20 
    22 
    58    */
    60    */
    59   
    61   
    60   var $bind_address = '127.0.0.1';
    62   var $bind_address = '127.0.0.1';
    61   
    63   
    62   /**
    64   /**
       
    65    * Port we're listening on
       
    66    * @var int
       
    67    */
       
    68    
       
    69   var $port = 8080;
       
    70   
       
    71   /**
    63    * Socket abstraction object
    72    * Socket abstraction object
    64    * @var object
    73    * @var object
    65    */
    74    */
    66   
    75   
    67   var $server = null;
    76   var $server = null;
   126    * Switch to control forking support.
   135    * Switch to control forking support.
   127    * @var bool
   136    * @var bool
   128    */
   137    */
   129   
   138   
   130   var $allow_fork = true;
   139   var $allow_fork = true;
       
   140   
       
   141   /**
       
   142    * Multi-threading manager.
       
   143    * @var object
       
   144    */
       
   145   
       
   146   var $threader = false;
   131   
   147   
   132   /**
   148   /**
   133    * Keep-alive support uses this to track what the client requested.
   149    * Keep-alive support uses this to track what the client requested.
   134    * Only used if $allow_fork is set to true.
   150    * Only used if $allow_fork is set to true.
   135    * @var bool
   151    * @var bool
   178    */
   194    */
   179   
   195   
   180   var $parent_pid = 0;
   196   var $parent_pid = 0;
   181   
   197   
   182   /**
   198   /**
       
   199    * List of IPC request handlers
       
   200    * @var array
       
   201    * @access private
       
   202    */
       
   203   
       
   204   var $ipc_handlers = array();
       
   205   
       
   206   /**
   183    * Sockets for parent and child to communicate
   207    * Sockets for parent and child to communicate
   184    * @var resource
   208    * @var resource
   185    * @var resource
   209    * @var resource
   186    */
   210    */
   187   
   211   
   188   var $parent_sock = null;
   212   var $parent_sock = null;
   189   var $child_sock = null;
   213   var $child_sock = null;
       
   214   
       
   215   /**
       
   216    * Switched on when a graceful reboot event is sent.
       
   217    * @var bool
       
   218    */
       
   219   
       
   220   var $reboot_sent = false;
   190   
   221   
   191   /**
   222   /**
   192    * Constructor.
   223    * Constructor.
   193    * @param string IPv4 address to bind to
   224    * @param string IPv4 address to bind to
   194    * @param int Port number
   225    * @param int Port number
   198   
   229   
   199   function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null)
   230   function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null)
   200   {
   231   {
   201     @set_time_limit(0);
   232     @set_time_limit(0);
   202     @ini_set('memory_limit', '128M');
   233     @ini_set('memory_limit', '128M');
   203     
       
   204     // do we have socket functions?
       
   205     if ( !function_exists('socket_create') )
       
   206     {
       
   207       burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
       
   208     }
       
   209     
   234     
   210     // make sure we're not running as root
   235     // make sure we're not running as root
   211     // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound
   236     // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound
   212     $allow_root = ( $port < 1024 ) ? true : false;
   237     $allow_root = ( $port < 1024 ) ? true : false;
   213     if ( function_exists('posix_geteuid') )
   238     if ( function_exists('posix_geteuid') )
   273       if ( function_exists('status') )
   298       if ( function_exists('status') )
   274         status('Successfully switched user ID');
   299         status('Successfully switched user ID');
   275     }
   300     }
   276     
   301     
   277     $this->bind_address = $address;
   302     $this->bind_address = $address;
       
   303     $this->port = $port;
   278     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
   304     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
   279     $this->parent_pid = getmypid();
   305     $this->parent_pid = getmypid();
       
   306     $this->threader = new Threader();
   280     
   307     
   281     // create a UUID
   308     // create a UUID
   282     $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
   309     $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
   283     $this->uuid = substr($uuid_base, 0,  8) . '-' .
   310     $this->uuid = substr($uuid_base, 0,  8) . '-' .
   284                   substr($uuid_base, 8,  4) . '-' .
   311                   substr($uuid_base, 8,  4) . '-' .
   292    * Destructor.
   319    * Destructor.
   293    */
   320    */
   294   
   321   
   295   function __destruct()
   322   function __destruct()
   296   {
   323   {
   297     if ( !defined('HTTPD_WS_CHILD') && $this->socket_initted )
   324     if ( !$this->threader->is_child() && $this->socket_initted )
   298     {
   325     {
   299       if ( function_exists('status') )
   326       if ( function_exists('status') )
   300         status('WebServer: destroying socket');
   327         status('WebServer: destroying socket');
   301       $this->server->destroy();
   328       $this->server->destroy();
   302       
   329       
   303       // tell all children to shut down
   330       // tell all children to shut down
   304       if ( $this->allow_fork )
   331       if ( $this->allow_fork )
   305       {
   332       {
   306         if ( function_exists('status') )
   333         if ( function_exists('status') )
   307           status('WebServer: asking all children to exit');
   334           status('WebServer: asking all children to exit');
   308         $this->send_ipc_event("die _");
   335         $this->threader->kill_all_children();
   309       }
   336       }
   310       
   337     }
   311       // that last operation should have been asynchronous, so shut everything down now
   338   }
   312       @socket_shutdown($this->parent_sock);
   339   
   313       @socket_close($this->parent_sock);
   340   /**
   314     }
   341    * Reboot the server. Useful for applying new settings.
   315     else if ( defined('HTTPD_WS_CHILD') )
   342    * @param string Optional, new IP address to bind to
   316     {
   343    * @param int Optional, new port to bind to
   317       @socket_shutdown($this->child_sock);
   344    * @param bool Optional, whether to allow forking or not
   318       @socket_close($this->child_sock);
   345    */
   319     }
   346   
       
   347   function reboot($addr = null, $port = null, $allow_fork = null)
       
   348   {
       
   349     if ( function_exists('status') )
       
   350       status('Reboot request has been received');
       
   351     
       
   352     $addr = ( !is_string($addr) ) ? $this->bind_address : $addr;
       
   353     $port = ( !is_int($port) ) ? $this->port : $port;
       
   354     $fork = ( !is_bool($allow_fork) ) ? $this->allow_fork : $allow_fork;
       
   355     
       
   356     //
       
   357     // REBOOTING IS A COMPLICATED THING.
       
   358     // We need to ask all children to close any existing connections so
       
   359     // that all relevant socket resources can be freed. Then we need to
       
   360     // call the constructor again to respawn the server, and finally
       
   361     // re-enter the server loop.
       
   362     //
       
   363     // However, reboot() is often called from a PHP-based handler. This
       
   364     // means that some config page probably still needs to be sent. What
       
   365     // we can do here is send an IPC event that fires the actual reboot,
       
   366     // then return to allow the current page to finish up. We also need
       
   367     // to signal the current process to shut down any existing keep-
       
   368     // alive connections. This can be accomplished by setting in_keepalive
       
   369     // to false.
       
   370     //
       
   371     
       
   372     // Kill the entire child after this response is sent
       
   373     $this->in_keepalive = false;
       
   374     
       
   375     // If we're the parent process, we need to know that a reboot event
       
   376     // was fired, and thus the server's main socket needs to be destroyed
       
   377     // and recreated. This is just done with another boolean switch.
       
   378     $this->reboot_sent = true;
       
   379     
       
   380     // this is really to track if there are any children
       
   381     $oldfork = $this->allow_fork;
       
   382     
       
   383     // Set our new server flags
       
   384     $this->bind_address = $addr;
       
   385     $this->port = $port;
       
   386     $this->allow_fork = $fork;
       
   387     
       
   388     // If we're a child, we have to tell the parent what the hell is
       
   389     // going on, and then get out of here as quickly as possible
       
   390     // (and other children should do the same). If this is a child,
       
   391     // fire an IPC reboot event. Else, fire a "die all" event
       
   392     if ( $this->threader->is_child() )
       
   393     {
       
   394       if ( function_exists('status') )
       
   395         status('Signaling parent with parameter changes (fork = ' . intval($fork) . ') + reboot request');
       
   396       // this is the child
       
   397       $this->threader->ipc_send(array(
       
   398         'action' => 'ws_reboot',
       
   399         'addr' => $addr,
       
   400         'port' => $port,
       
   401         'fork' => $fork
       
   402       ));
       
   403     }
       
   404     else if ( !$this->threader->is_child() && $oldfork )
       
   405     {
       
   406       if ( function_exists('status') )
       
   407         status('Waiting on all children');
       
   408       
       
   409       // this is the parent, and there are children present
       
   410       $this->threader->kill_all_children();
       
   411       
       
   412       // all children are dead, we are ok to respawn
       
   413       $this->respawn();
       
   414     }
       
   415     else
       
   416     {
       
   417       // this is a childless parent; delay any action until the current
       
   418       // request has been sent (do nothing now)
       
   419     }
       
   420   }
       
   421   
       
   422   /**
       
   423    * Respawns the server. All children should be dead, and any client
       
   424    * connections must be closed.
       
   425    */
       
   426   
       
   427   function respawn()
       
   428   {
       
   429     $this->reboot_sent = false;
       
   430     
       
   431     if ( function_exists('status') )
       
   432       status('Respawn event sent');
       
   433     $this->server->destroy();
       
   434     unset($this->server);
       
   435     
       
   436     // try to spawn up to 10 times
       
   437     for ( $i = 0; $i < 10; $i++ )
       
   438     {
       
   439       try
       
   440       {
       
   441         $this->__construct($this->bind_address, $this->port);
       
   442       }
       
   443       catch ( Exception $e )
       
   444       {
       
   445         if ( $i == 9 )
       
   446         {
       
   447           if ( function_exists('burnout') )
       
   448           {
       
   449             burnout("Couldn't respawn because one of the child processes did not die, and thus the port was not freed.");
       
   450           }
       
   451           exit(1);
       
   452         }
       
   453         if ( function_exists('status') )
       
   454         {
       
   455           status("Respawn failed, retrying in 2 seconds");
       
   456         }
       
   457         usleep(2000000);
       
   458         continue;
       
   459       }
       
   460       break;
       
   461     }
       
   462     
       
   463     if ( function_exists('status') )
       
   464       status('Respawn is complete, entering server loop with bind_address = ' . $this->bind_address . ' allow_fork = ' . strval(intval($this->allow_fork)));
       
   465     
       
   466     // all handlers should already be set up, so just break out and we should automatically continue the server loop
   320   }
   467   }
   321   
   468   
   322   /**
   469   /**
   323    * Main server loop
   470    * Main server loop
   324    */
   471    */
   325   
   472   
   326   function serve()
   473   function serve()
   327   {
   474   {
   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     
       
   346     while ( true )
   475     while ( true )
   347     {
   476     {
       
   477       ##
       
   478       ## STAGE 0: CLEANUP FROM PREVIOUS RUN
       
   479       ##
       
   480       
   348       // if this is a child process, we're finished - close up shop
   481       // if this is a child process, we're finished - close up shop
   349       if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive )
   482       if ( $this->threader->is_child() && !$this->in_keepalive )
   350       {
   483       {
   351         if ( function_exists('status') )
   484         if ( function_exists('status') )
   352           status('Exiting child process');
   485           status('Exiting child process');
   353         
   486         
   354         $remote->destroy();
   487         $remote->destroy();
   355         
   488         
   356         // let the parent know that we're out of here
       
   357         $this->send_ipc_event("exit " . getmypid());
       
   358         
       
   359         // bye
       
   360         exit(0);
   489         exit(0);
   361       }
   490       }
   362       
   491       
       
   492       ##
       
   493       ## STAGE 1: LISTENER AND INIT
       
   494       ##
       
   495       
   363       // wait for connection...
   496       // wait for connection...
   364       // trick from http://us.php.net/manual/en/function.socket-accept.php
   497       if ( !$this->threader->is_child() )
   365       if ( !defined('HTTPD_WS_CHILD') )
       
   366       {
   498       {
   367         $remote = $this->server->accept();
   499         $remote = $this->server->accept();
   368       }
   500       }
   369       
   501       
   370       if ( !$remote )
   502       if ( !$remote )
   374       }
   506       }
   375       
   507       
   376       // fork off if possible
   508       // fork off if possible
   377       if ( function_exists('pcntl_fork') && $this->allow_fork && !$this->in_keepalive )
   509       if ( function_exists('pcntl_fork') && $this->allow_fork && !$this->in_keepalive )
   378       {
   510       {
   379         $pid = pcntl_fork();
   511         if ( $this->threader->fork() == FORK_CHILD )
   380         if ( $pid == -1 )
   512         {
   381         {
   513           // this is the child
   382           // do nothing; continue responding to request in single-threaded mode
   514           define('HTTPD_WS_CHILD', 1);
   383         }
   515         }
   384         else if ( $pid )
   516         else
   385         {
   517         {
   386           // we are the parent, continue listening
   518           // we are the parent, continue listening
   387           $remote->soft_shutdown();
   519           $remote->soft_shutdown();
   388           $this->child_list[] = $pid;
   520           $this->child_list[] = $pid;
   389           continue;
   521           continue;
   390         }
   522         }
   391         else
       
   392         {
       
   393           // this is the child
       
   394           define('HTTPD_WS_CHILD', 1);
       
   395           
       
   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           }
       
   405         }
       
   406       }
   523       }
   407       
   524       
   408       $this->in_keepalive = false;
   525       $this->in_keepalive = false;
   409       $this->headers_sent = false;
   526       $this->headers_sent = false;
   410       $this->in_scriptlet = false;
   527       $this->in_scriptlet = false;
   411       
   528       
   412       //
   529       ##
   413       // READ THE REQUEST
   530       ## STAGE 2: READ REQUEST
   414       //
   531       ##
   415       
   532       
   416       // this is a complicated situation because we need to keep enough ticks going to properly handle
   533       // 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
   534       // 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
   535       // logic. setting the timeout to a short period, say 200,000 usec, we can minimize CPU usage and
   419       // have a good response time.
   536       // have a good response time.
   425       while ( true )
   542       while ( true )
   426       {
   543       {
   427         if ( $start_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) || $remote->is_eof() )
   544         if ( $start_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) || $remote->is_eof() )
   428         {
   545         {
   429           // request expired -- end the process here
   546           // request expired -- end the process here
   430           if ( !defined('HTTPD_WS_CHILD') )
   547           if ( !$this->threader->is_child() )
   431             $remote->destroy();
   548             $remote->destroy();
   432           
   549           
   433           continue 2;
   550           continue 2;
   434         }
   551         }
   435         $line = strval($remote->read_normal());
   552         $line = strval($remote->read_normal());
   443           
   560           
   444         $client_headers .= $line;
   561         $client_headers .= $line;
   445         $last_line = $line;
   562         $last_line = $line;
   446       }
   563       }
   447       
   564       
       
   565       ##
       
   566       ## STAGE 3: PARSE REQUEST AND HEADERS
       
   567       ##
       
   568       
   448       // parse request
   569       // parse request
   449       $client_headers = trim($client_headers);
   570       $client_headers = trim($client_headers);
   450       
   571       
   451       if ( isset($last_finish_time) && empty($client_headers) && defined('HTTPD_WS_CHILD') && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) )
   572       if ( isset($last_finish_time) && empty($client_headers) && $this->threader->is_child() && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) )
   452       {
   573       {
   453         status('[debug] keep-alive connection timed out (checkpoint 2)');
   574         status('[debug] keep-alive connection timed out (checkpoint 2)');
   454         continue; // will jump back to the start of the loop and kill the child process
   575         continue; // will jump back to the start of the loop and kill the child process
   455       }
   576       }
   456       
   577       
   464         continue;
   585         continue;
   465       }
   586       }
   466       $method =& $match[1];
   587       $method =& $match[1];
   467       $uri =& $match[2];
   588       $uri =& $match[2];
   468       
   589       
   469       // set client headers
   590       // set client header SERVER variables
   470       foreach ( $_SERVER as $key => $_ )
   591       foreach ( $_SERVER as $key => $_ )
   471       {
   592       {
   472         if ( preg_match('/^HTTP_/', $key) )
   593         if ( preg_match('/^HTTP_/', $key) )
   473           unset($_SERVER[$key]);
   594           unset($_SERVER[$key]);
   474       }
   595       }
   480         $key = 'HTTP_' . strtoupper(str_replace('-', '_', $match[1]));
   601         $key = 'HTTP_' . strtoupper(str_replace('-', '_', $match[1]));
   481         $_SERVER[$key] = $match[2];
   602         $_SERVER[$key] = $match[2];
   482       }
   603       }
   483       
   604       
   484       // enable keep-alive if requested
   605       // enable keep-alive if requested
   485       if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') )
   606       if ( isset($_SERVER['HTTP_CONNECTION']) && $this->threader->is_child() )
   486       {
   607       {
   487         $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' );
   608         $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' );
   488       }
   609       }
   489       
   610       
   490       // process cookies
   611       // process cookies
   513       $postdata = '';
   634       $postdata = '';
   514       $_POST = array();
   635       $_POST = array();
   515       $_FILES = array();
   636       $_FILES = array();
   516       if ( $method == 'POST' )
   637       if ( $method == 'POST' )
   517       {
   638       {
   518         // read POST data
   639         $this->parse_post_data($remote);
   519         if ( isset($_SERVER['HTTP_CONTENT_TYPE']) && preg_match('#^multipart/form-data; ?boundary=([A-z0-9_-]+)$#i', $_SERVER['HTTP_CONTENT_TYPE'], $match) )
       
   520         {
       
   521           // this is a multipart request
       
   522           $boundary =& $match[1];
       
   523           $mode = 'data';
       
   524           $last_line = '';
       
   525           $i = 0;
       
   526           while ( $data = $remote->read_normal(8388608) )
       
   527           {
       
   528             $data_trim = trim($data, "\r\n");
       
   529             if ( $mode != 'data' )
       
   530             {
       
   531               $data = str_replace("\r", '', $data);
       
   532             }
       
   533             if ( ( $data_trim === "--$boundary" || $data_trim === "--$boundary--" ) && $i > 0 )
       
   534             {
       
   535               // trim off the first LF and the last CRLF
       
   536               $currval_data = substr($currval_data, 1, strlen($currval_data)-3);
       
   537               
       
   538               // this is the end of a part of the message; parse it into either $_POST or $_FILES
       
   539               if ( is_string($have_a_file) )
       
   540               {
       
   541                 
       
   542                 // write data to a temporary file
       
   543                 $errcode = UPLOAD_ERR_OK;
       
   544                 $tempfile = tempnam('phpupload', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' ));
       
   545                 if ( $fh = @fopen($tempfile, 'w') )
       
   546                 {
       
   547                   if ( empty($have_a_file) )
       
   548                   {
       
   549                     $errcode = UPLOAD_ERR_NO_FILE;
       
   550                   }
       
   551                   else
       
   552                   {
       
   553                     fwrite($fh, $currval_data);
       
   554                   }
       
   555                   fclose($fh);
       
   556                 }
       
   557                 else
       
   558                 {
       
   559                   $errcode = UPLOAD_ERR_CANT_WRITE;
       
   560                 }
       
   561                 $_FILES[$currval_name] = array(
       
   562                     'name' => $have_a_file,
       
   563                     'type' => $currval_type,
       
   564                     'size' => filesize($tempfile),
       
   565                     'tmp_name' => $tempfile,
       
   566                     'error' => $errcode
       
   567                   );
       
   568               }
       
   569               else
       
   570               {
       
   571                 $_POST[$currval_name] = $currval_data;
       
   572               }
       
   573             }
       
   574             
       
   575             if ( $data_trim === "--$boundary" )
       
   576             {
       
   577               // switch from "data" mode to "headers" mode
       
   578               $currval_name = '';
       
   579               $currval_data = '';
       
   580               $currval_type = '';
       
   581               $have_a_file = false;
       
   582               $mode = 'headers';
       
   583             }
       
   584             else if ( $data_trim === "--$boundary--" )
       
   585             {
       
   586               // end of request
       
   587               break;
       
   588             }
       
   589             else if ( ( empty($data_trim) && empty($last_line) ) && $mode == 'headers' )
       
   590             {
       
   591               // start of data
       
   592               $mode = 'data';
       
   593             }
       
   594             else if ( $mode == 'headers' )
       
   595             {
       
   596               // read header
       
   597               // we're only looking for Content-Disposition and Content-Type
       
   598               if ( preg_match('#^Content-Disposition: form-data; name="([^"\a\t\r\n]+)"(?:; filename="([^"\a\t\r\n]+)")?#i', $data_trim, $match) )
       
   599               {
       
   600                 // content-disposition header, set name and mode.
       
   601                 $currval_name = $match[1];
       
   602                 if ( isset($match[2]) )
       
   603                 {
       
   604                   $have_a_file = $match[2];
       
   605                 }
       
   606                 else
       
   607                 {
       
   608                   $have_a_file = false;
       
   609                 }
       
   610               }
       
   611               else if ( preg_match('#^Content-Type: ([a-z0-9-]+/[a-z0-9/-]+)$#i', $data_trim, $match) )
       
   612               {
       
   613                 $currval_type = $match[1];
       
   614               }
       
   615             }
       
   616             else if ( $mode == 'data' )
       
   617             {
       
   618               $currval_data .= $data;
       
   619             }
       
   620             $last_line = $data_trim;
       
   621             $i++;
       
   622           }
       
   623         }
       
   624         else
       
   625         {
       
   626           if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) )
       
   627           {
       
   628             $postdata = $remote->read_binary(intval($_SERVER['HTTP_CONTENT_LENGTH']));
       
   629           }
       
   630           else
       
   631           {
       
   632             $postdata = $remote->read_normal(8388608);
       
   633           }
       
   634           if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $postdata, $matches) )
       
   635           {
       
   636             if ( isset($matches[1]) )
       
   637             {
       
   638               foreach ( $matches[0] as $i => $_ )
       
   639               {
       
   640                 $_POST[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
       
   641               }
       
   642             }
       
   643           }
       
   644         }
       
   645       }
   640       }
   646       
   641       
   647       // parse URI
   642       // parse URI
   648       $params = '';
   643       $params = '';
   649       if ( strstr($uri, '?') )
   644       if ( strstr($uri, '?') )
   657       $_SERVER['REQUEST_METHOD'] = $method;
   652       $_SERVER['REQUEST_METHOD'] = $method;
   658       
   653       
   659       // get remote IP and port
   654       // get remote IP and port
   660       $remote->get_peer_info($_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
   655       $remote->get_peer_info($_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']);
   661       
   656       
       
   657       // process $_GET
   662       $_GET = array();
   658       $_GET = array();
   663       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) )
   659       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) )
   664       {
   660       {
   665         if ( isset($matches[1]) )
   661         if ( isset($matches[1]) )
   666         {
   662         {
   669             $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   665             $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   670           }
   666           }
   671         }
   667         }
   672       }
   668       }
   673       
   669       
       
   670       // Parse GET, POST, and FILES into multi-depth arrays
   674       $_GET = $this->parse_multi_depth_array($_GET);
   671       $_GET = $this->parse_multi_depth_array($_GET);
   675       $_POST = $this->parse_multi_depth_array($_POST);
   672       $_POST = $this->parse_multi_depth_array($_POST);
   676       $_FILES = $this->parse_multi_depth_array($_FILES);
   673       $_FILES = $this->parse_multi_depth_array($_FILES);
       
   674       
       
   675       ##
       
   676       ## STAGE 4: HANDLER RESOLUTION
       
   677       ##
   677       
   678       
   678       // init handler
   679       // init handler
   679       $handler = false;
   680       $handler = false;
   680       
   681       
   681       if ( $uri == '' )
   682       if ( $uri == '' )
   729           $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server.");
   730           $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server.");
   730           continue;
   731           continue;
   731         }
   732         }
   732       }
   733       }
   733       
   734       
       
   735       ##
       
   736       ## STAGE 5: HANDLER CALL
       
   737       ##
       
   738       
   734       $this->send_standard_response($remote, $handler, $uri, $params);
   739       $this->send_standard_response($remote, $handler, $uri, $params);
       
   740       
       
   741       ##
       
   742       ## STAGE 6: CLEANUP
       
   743       ##
   735       
   744       
   736       // now that we're done sending the response, delete any temporary uploaded files
   745       // now that we're done sending the response, delete any temporary uploaded files
   737       if ( !empty($_FILES) )
   746       if ( !empty($_FILES) )
   738       {
   747       {
   739         foreach ( $_FILES as $file_data )
   748         foreach ( $_FILES as $file_data )
   743             @unlink($file_data['tmp_name']);
   752             @unlink($file_data['tmp_name']);
   744           }
   753           }
   745         }
   754         }
   746       }
   755       }
   747       
   756       
   748       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   757       if ( !$this->in_keepalive && $this->threader->is_child() )
   749       {
   758       {
   750         // connection: close
   759         // connection: close
   751         // continue on to the shutdown handler
   760         // continue on to the shutdown handler
   752         continue;
   761         continue;
   753       }
   762       }
   754       else if ( defined('HTTPD_WS_CHILD') )
   763       else if ( $this->threader->is_child() )
   755       {
   764       {
   756         // if ( defined('HTTPD_WS_CHILD') )
   765         // if ( $this->threader->is_child() )
   757         //   status('Continuing connection');
   766         //   status('Continuing connection');
   758         // $remote->write("\r\n\r\n");
   767         // $remote->write("\r\n\r\n");
   759         $last_finish_time = microtime(true);
   768         $last_finish_time = microtime(true);
   760       }
   769       }
   761       else
   770       else
   762       {
   771       {
       
   772         // standalone process
   763         $remote->destroy();
   773         $remote->destroy();
       
   774         
       
   775         // if a reboot was fired and we're running in single-process mode, now is the time to respawn
       
   776         if ( !$this->threader->is_child() && $this->reboot_sent )
       
   777         {
       
   778           $this->respawn();
       
   779         }
       
   780       }
       
   781     }
       
   782   }
       
   783   
       
   784   /**
       
   785    * Parse POST data and format $_POST and $_FILES.
       
   786    * @param resource Remote socket
       
   787    */
       
   788   
       
   789   function parse_post_data($remote)
       
   790   {
       
   791     $postdata = '';
       
   792     
       
   793     // read POST data
       
   794     if ( isset($_SERVER['HTTP_CONTENT_TYPE']) && preg_match('#^multipart/form-data; ?boundary=([A-z0-9_-]+)$#i', $_SERVER['HTTP_CONTENT_TYPE'], $match) )
       
   795     {
       
   796       // this is a multipart request
       
   797       $boundary =& $match[1];
       
   798       $mode = 'data';
       
   799       $last_line = '';
       
   800       $i = 0;
       
   801       while ( $data = $remote->read_normal(8388608) )
       
   802       {
       
   803         $data_trim = trim($data, "\r\n");
       
   804         if ( $mode != 'data' )
       
   805         {
       
   806           $data = str_replace("\r", '', $data);
       
   807         }
       
   808         if ( ( $data_trim === "--$boundary" || $data_trim === "--$boundary--" ) && $i > 0 )
       
   809         {
       
   810           // trim off the first LF and the last CRLF
       
   811           if ( HTTPD_SOCKET_LAYER == 'Raw' )
       
   812             $currval_data = substr($currval_data, 1, strlen($currval_data)-3);
       
   813           else
       
   814             $currval_data = substr($currval_data, 0, strlen($currval_data)-2);
       
   815           
       
   816           // this is the end of a part of the message; parse it into either $_POST or $_FILES
       
   817           if ( is_string($have_a_file) )
       
   818           {
       
   819             // write data to a temporary file
       
   820             $errcode = UPLOAD_ERR_OK;
       
   821             $tempfile = tempnam('phpupload', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' ));
       
   822             if ( $fh = @fopen($tempfile, 'w') )
       
   823             {
       
   824               if ( empty($have_a_file) )
       
   825               {
       
   826                 $errcode = UPLOAD_ERR_NO_FILE;
       
   827               }
       
   828               else
       
   829               {
       
   830                 fwrite($fh, $currval_data);
       
   831               }
       
   832               fclose($fh);
       
   833             }
       
   834             else
       
   835             {
       
   836               $errcode = UPLOAD_ERR_CANT_WRITE;
       
   837             }
       
   838             $_FILES[$currval_name] = array(
       
   839                 'name' => $have_a_file,
       
   840                 'type' => $currval_type,
       
   841                 'size' => filesize($tempfile),
       
   842                 'tmp_name' => $tempfile,
       
   843                 'error' => $errcode
       
   844               );
       
   845           }
       
   846           else
       
   847           {
       
   848             if ( preg_match('/\[\]$/', $currval_name) )
       
   849             {
       
   850               if ( !isset($_POST[$currval_name]) || ( isset($_POST[$currval_name]) && !is_array($_POST[$currval_name]) ) )
       
   851                 $_POST[$currval_name] = array();
       
   852               
       
   853               $_POST[$currval_name][] = $currval_data;
       
   854             }
       
   855             else
       
   856             {
       
   857               $_POST[$currval_name] = $currval_data;
       
   858             }
       
   859           }
       
   860         }
       
   861         
       
   862         if ( $data_trim === "--$boundary" )
       
   863         {
       
   864           // switch from "data" mode to "headers" mode
       
   865           $currval_name = '';
       
   866           $currval_data = '';
       
   867           $currval_type = '';
       
   868           $have_a_file = false;
       
   869           $mode = 'headers';
       
   870         }
       
   871         else if ( $data_trim === "--$boundary--" )
       
   872         {
       
   873           // end of request
       
   874           break;
       
   875         }
       
   876         else if ( ( empty($data_trim) && ( ( HTTPD_SOCKET_LAYER == 'Raw' && empty($last_line) ) || HTTPD_SOCKET_LAYER != 'Raw' ) ) && $mode == 'headers' )
       
   877         {
       
   878           // start of data
       
   879           $mode = 'data';
       
   880         }
       
   881         else if ( $mode == 'headers' )
       
   882         {
       
   883           // read header
       
   884           // we're only looking for Content-Disposition and Content-Type
       
   885           if ( preg_match('#^Content-Disposition: form-data; name="([^"\a\t\r\n]+)"(?:; filename="([^"\a\t\r\n]+)")?#i', $data_trim, $match) )
       
   886           {
       
   887             // content-disposition header, set name and mode.
       
   888             $currval_name = $match[1];
       
   889             if ( isset($match[2]) )
       
   890             {
       
   891               $have_a_file = $match[2];
       
   892             }
       
   893             else
       
   894             {
       
   895               $have_a_file = false;
       
   896             }
       
   897           }
       
   898           else if ( preg_match('#^Content-Type: ([a-z0-9-]+/[a-z0-9/-]+)$#i', $data_trim, $match) )
       
   899           {
       
   900             $currval_type = $match[1];
       
   901           }
       
   902         }
       
   903         else if ( $mode == 'data' )
       
   904         {
       
   905           $currval_data .= $data;
       
   906         }
       
   907         $last_line = $data_trim;
       
   908         $i++;
       
   909       }
       
   910     }
       
   911     else
       
   912     {
       
   913       if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) )
       
   914       {
       
   915         $postdata = $remote->read_binary(intval($_SERVER['HTTP_CONTENT_LENGTH']));
       
   916       }
       
   917       else
       
   918       {
       
   919         $postdata = $remote->read_normal(8388608);
       
   920       }
       
   921       if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]%-]+)(=[^ &]+)?/i', $postdata, $matches) )
       
   922       {
       
   923         if ( isset($matches[1]) )
       
   924         {
       
   925           foreach ( $matches[0] as $i => $_ )
       
   926           {
       
   927             $currval_name =& $matches[2][$i];
       
   928             $currval_data = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
       
   929             $currval_name = urldecode($currval_name);
       
   930             
       
   931             if ( preg_match('/\[\]$/', $currval_name) )
       
   932             {
       
   933               $basename = preg_replace('/\[\]$/', '', $currval_name);
       
   934               if ( !isset($_POST[$basename]) || ( isset($_POST[$basename]) && !is_array($_POST[$basename]) ) )
       
   935                 $_POST[$basename] = array();
       
   936               
       
   937               $_POST[$basename][] = $currval_data;
       
   938             }
       
   939             else
       
   940             {
       
   941               $_POST[$currval_name] = $currval_data;
       
   942             }
       
   943           }
       
   944         }
   764       }
   945       }
   765     }
   946     }
   766   }
   947   }
   767   
   948   
   768   /**
   949   /**
  1601   
  1782   
  1602   function parse_multi_depth_array($array)
  1783   function parse_multi_depth_array($array)
  1603   {
  1784   {
  1604     foreach ( $array as $key => $value )
  1785     foreach ( $array as $key => $value )
  1605     {
  1786     {
  1606       if ( preg_match('/^([^\[\]]+)\[([^\]]+)\]/', $key, $match) )
  1787       if ( preg_match('/^([^\[\]]+)\[([^\]]*)\]/', $key, $match) )
  1607       {
  1788       {
  1608         $parent =& $match[1];
  1789         $parent =& $match[1];
  1609         $child =& $match[2];
  1790         $child =& $match[2];
  1610         if ( !isset($array[$parent]) || ( isset($array[$parent]) && !is_array($array[$parent]) ) )
  1791         if ( !isset($array[$parent]) || ( isset($array[$parent]) && !is_array($array[$parent]) ) )
  1611         {
  1792         {
  1612           $array[$parent] = array();
  1793           $array[$parent] = array();
  1613         }
  1794         }
  1614         $array[$parent][$child] = $value;
  1795         if ( empty($child) )
       
  1796         {
       
  1797           $array[$parent][] = $value;
       
  1798         }
       
  1799         else
       
  1800         {
       
  1801           $array[$parent][$child] = $value;
       
  1802         }
  1615         unset($array[$key]);
  1803         unset($array[$key]);
  1616         $array[$parent] = $this->parse_multi_depth_array($array[$parent]);
  1804         $array[$parent] = $this->parse_multi_depth_array($array[$parent]);
  1617       }
  1805       }
  1618     }
  1806     }
  1619     return $array;
  1807     return $array;
  1623    * Handle an IPC event. Called only upon SIGUSR2.
  1811    * Handle an IPC event. Called only upon SIGUSR2.
  1624    */
  1812    */
  1625   
  1813   
  1626   function _ipc_event()
  1814   function _ipc_event()
  1627   {
  1815   {
  1628     $pid = getmypid() . ':' . $this->parent_pid;
  1816     /*
  1629     
  1817     case 'set_addr':
  1630     // decide which socket to use
  1818       $this->bind_address = $param;
  1631     if ( defined('HTTPD_WS_CHILD') )
  1819       break;
  1632       $sock =& $this->parent_sock;
  1820     case 'set_port':
  1633     else
  1821       $this->port = intval($param);
  1634       $sock =& $this->child_sock;
  1822       break;
  1635     
  1823     case 'set_fork':
  1636     // try to read the event
  1824       $this->allow_fork = ( $param == '1' );
  1637     // this sometimes gets hung up because socket_set_timeout() doesn't seem to work on its own set of
  1825       break;
  1638     // functions (it only works on PHP's normal streams)
  1826     case 'reboot':
  1639     if ( $line = @fgets($sock, 1024) )
  1827       if ( !$this->threader->is_child() )
  1640     {
  1828       {
  1641       $line = trim($line);
  1829         list(, $addr, $port, $fork) = explode(' ', $line);
  1642       list($action, $param) = explode(' ', $line);
  1830         $fork = ( $fork === '1' );
  1643       switch($action)
  1831         $this->reboot($addr, intval($port), $fork);
  1644       {
  1832       }
  1645         case 'exit':
  1833       break;
  1646           // this is to prevent zombie children
  1834         default:
  1647           pcntl_waitpid(intval($param), $status);
  1835           if ( isset($this->ipc_handlers[$action]) )
  1648           // we know this child is dead now, remove them from the list
       
  1649           foreach ( $this->child_list as $i => $pid )
       
  1650           {
  1836           {
  1651             if ( $pid === intval($param) )
  1837             @call_user_func($this->ipc_handlers[$action], $line);
  1652             {
       
  1653               unset($this->child_list[$i]);
       
  1654               $this->child_list = array_values($this->child_list);
       
  1655               break;
       
  1656             }
       
  1657           }
  1838           }
  1658           break;
  1839           break;
  1659         case 'die':
  1840       }
  1660           // only do this if this is a child (both security and design)
  1841       */
  1661           if ( defined('HTTPD_WS_CHILD') )
  1842   }
  1662           {
       
  1663             if ( function_exists('status') )
       
  1664             {
       
  1665               status('Received shutdown request, complying');
       
  1666             }
       
  1667             $this->send_ipc_event("exit " . getmypid());
       
  1668             exit(0);
       
  1669           }
       
  1670           break;
       
  1671         default:
       
  1672           break;
       
  1673       }
       
  1674     }
       
  1675   }
       
  1676   
       
  1677   /**
       
  1678    * Send an IPC event.
       
  1679    * @param string Data to write to the socket, newline will be added automatically
       
  1680    */
       
  1681   
       
  1682   function send_ipc_event($data)
       
  1683   {
       
  1684     if ( defined('HTTPD_WS_CHILD') )
       
  1685       $sock =& $this->parent_sock;
       
  1686     else
       
  1687       $sock =& $this->child_sock;
       
  1688       
       
  1689     $data = rtrim($data, "\r\n") . "\n";
       
  1690     @fwrite($sock, $data);
       
  1691     
       
  1692     // if we're a child, signal the parent
       
  1693     if ( defined('HTTPD_WS_CHILD') )
       
  1694     {
       
  1695       posix_kill($this->parent_pid, SIGUSR2);
       
  1696     }
       
  1697     // if we're the parent, signal all children
       
  1698     else
       
  1699     {
       
  1700       foreach ( $this->child_list as $pid )
       
  1701       {
       
  1702         posix_kill($pid, SIGUSR2);
       
  1703       }
       
  1704     }
       
  1705   }
       
  1706   
       
  1707 }
  1843 }
  1708 
  1844 
  1709 /**
  1845 /**
  1710  * Socket abstraction layer - low-level socket functions (socket_*)
  1846  * Socket abstraction layer - low-level socket functions (socket_*)
  1711  */
  1847  */
  1715   var $sock;
  1851   var $sock;
  1716   var $socket_initted = false;
  1852   var $socket_initted = false;
  1717   
  1853   
  1718   function tcp_listen($address, $port)
  1854   function tcp_listen($address, $port)
  1719   {
  1855   {
       
  1856     // do we have socket functions?
       
  1857     if ( !function_exists('socket_create') )
       
  1858     {
       
  1859       burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
       
  1860     }
       
  1861     
  1720     $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
  1862     $this->sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
  1721     if ( !$this->sock )
  1863     if ( !$this->sock )
  1722       throw new Exception('Could not create socket');
  1864       throw new Exception('Could not create socket');
  1723     $result = @socket_bind($this->sock, $address, $port);
  1865     $result = @socket_bind($this->sock, $address, $port);
  1724     if ( !$result )
  1866     if ( !$result )
  1821   var $sock;
  1963   var $sock;
  1822   var $socket_initted = false;
  1964   var $socket_initted = false;
  1823   
  1965   
  1824   function tcp_listen($address, $port)
  1966   function tcp_listen($address, $port)
  1825   {
  1967   {
       
  1968     // does PHP support this?
       
  1969     if ( !function_exists('stream_socket_server') )
       
  1970     {
       
  1971       burnout('System does not support stream functions. Please rebuild your PHP or install an appropriate extension.');
       
  1972     }
       
  1973     
  1826     $this->sock = @stream_socket_server("tcp://$address:$port", $errno, $errstr);
  1974     $this->sock = @stream_socket_server("tcp://$address:$port", $errno, $errstr);
  1827     if ( !$this->sock )
  1975     if ( !$this->sock )
  1828       throw new Exception("Could not create the socket: error $errno: $errstr");
  1976       throw new Exception("Could not create the socket: error $errno: $errstr");
  1829   }
  1977   }
  1830   
  1978   
  1835       // PHP >= 5.2.1
  1983       // PHP >= 5.2.1
  1836       if ( function_exists('stream_socket_shutdown') )
  1984       if ( function_exists('stream_socket_shutdown') )
  1837       {
  1985       {
  1838         @stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR);
  1986         @stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR);
  1839       }
  1987       }
  1840       fclose($this->sock);
  1988       while ( !@fclose($this->sock) )
       
  1989       {
       
  1990         usleep(100000);
       
  1991       }
  1841     }
  1992     }
  1842   }
  1993   }
  1843   
  1994   
  1844   function accept()
  1995   function accept()
  1845   {
  1996   {
  1846     // the goal of a custom accept() with *_select() is to tick every 5 seconds to allow signals.
  1997     // the goal of a custom accept() with *_select() is to tick every 200ms to allow signals.
  1847     stream_set_blocking($this->sock, 1);
  1998     stream_set_blocking($this->sock, 1);
  1848     $timeout = 5;
  1999     $timeout = 5;
  1849     $selection = @stream_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout);
  2000     $selection = @stream_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout);
  1850     if ( !$selection )
  2001     if ( !$selection )
  1851     {
  2002     {