webserver.php
changeset 23 08225c2eb0b6
parent 21 74edc873234f
child 24 d275dc8f4203
equal deleted inserted replaced
22:3a4f0cd0794e 23:08225c2eb0b6
    31 /**
    31 /**
    32  * Simple web server written in PHP.
    32  * Simple web server written in PHP.
    33  * @package Amarok
    33  * @package Amarok
    34  * @subpackage WebControl
    34  * @subpackage WebControl
    35  * @author Dan Fuhry
    35  * @author Dan Fuhry
    36  * @license Public domain
    36  * @license GNU General Public License <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
    37  */
    37  */
    38 
    38 
    39 class WebServer
    39 class WebServer
    40 {
    40 {
    41   
    41   
   161    */
   161    */
   162   
   162   
   163   function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null)
   163   function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null)
   164   {
   164   {
   165     @set_time_limit(0);
   165     @set_time_limit(0);
   166     @ini_set('memory_limit', '256M');
   166     @ini_set('memory_limit', '128M');
   167     
   167     
   168     // do we have socket functions?
   168     // do we have socket functions?
   169     if ( !function_exists('socket_create') )
   169     if ( !function_exists('socket_create') )
   170     {
   170     {
   171       burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
   171       burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.');
   404       }
   404       }
   405       
   405       
   406       // anything on POST?
   406       // anything on POST?
   407       $postdata = '';
   407       $postdata = '';
   408       $_POST = array();
   408       $_POST = array();
       
   409       $_FILES = array();
   409       if ( $method == 'POST' )
   410       if ( $method == 'POST' )
   410       {
   411       {
   411         // read POST data
   412         // read POST data
   412         if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) )
   413         if ( isset($_SERVER['HTTP_CONTENT_TYPE']) && preg_match('#^multipart/form-data; ?boundary=([A-z0-9_-]+)$#i', $_SERVER['HTTP_CONTENT_TYPE'], $match) )
   413         {
   414         {
   414           $postdata = socket_read($remote, intval($_SERVER['HTTP_CONTENT_LENGTH']), PHP_BINARY_READ);
   415           // this is a multipart request
       
   416           $boundary =& $match[1];
       
   417           $mode = 'data';
       
   418           $last_line = '';
       
   419           $i = 0;
       
   420           while ( $data = socket_read($remote, 8388608, PHP_NORMAL_READ) )
       
   421           {
       
   422             $data_trim = trim($data, "\r\n");
       
   423             if ( $mode != 'data' )
       
   424             {
       
   425               $data = str_replace("\r", '', $data);
       
   426             }
       
   427             if ( ( $data_trim === "--$boundary" || $data_trim === "--$boundary--" ) && $i > 0 )
       
   428             {
       
   429               // trim off the first LF and the last CRLF
       
   430               $currval_data = substr($currval_data, 1, strlen($currval_data)-3);
       
   431               
       
   432               // this is the end of a part of the message; parse it into either $_POST or $_FILES
       
   433               if ( is_string($have_a_file) )
       
   434               {
       
   435                 
       
   436                 // write data to a temporary file
       
   437                 $errcode = UPLOAD_ERR_OK;
       
   438                 $tempfile = tempnam('phpupload', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' ));
       
   439                 if ( $fh = @fopen($tempfile, 'w') )
       
   440                 {
       
   441                   if ( empty($have_a_file) )
       
   442                   {
       
   443                     $errcode = UPLOAD_ERR_NO_FILE;
       
   444                   }
       
   445                   else
       
   446                   {
       
   447                     fwrite($fh, $currval_data);
       
   448                   }
       
   449                   fclose($fh);
       
   450                 }
       
   451                 else
       
   452                 {
       
   453                   $errcode = UPLOAD_ERR_CANT_WRITE;
       
   454                 }
       
   455                 $_FILES[$currval_name] = array(
       
   456                     'name' => $have_a_file,
       
   457                     'type' => $currval_type,
       
   458                     'size' => filesize($tempfile),
       
   459                     'tmp_name' => $tempfile,
       
   460                     'error' => $errcode
       
   461                   );
       
   462               }
       
   463               else
       
   464               {
       
   465                 $_POST[$currval_name] = $currval_data;
       
   466               }
       
   467             }
       
   468             
       
   469             if ( $data_trim === "--$boundary" )
       
   470             {
       
   471               // switch from "data" mode to "headers" mode
       
   472               $currval_name = '';
       
   473               $currval_data = '';
       
   474               $currval_type = '';
       
   475               $have_a_file = false;
       
   476               $mode = 'headers';
       
   477             }
       
   478             else if ( $data_trim === "--$boundary--" )
       
   479             {
       
   480               // end of request
       
   481               break;
       
   482             }
       
   483             else if ( ( empty($data_trim) && empty($last_line) ) && $mode == 'headers' )
       
   484             {
       
   485               // start of data
       
   486               $mode = 'data';
       
   487             }
       
   488             else if ( $mode == 'headers' )
       
   489             {
       
   490               // read header
       
   491               // we're only looking for Content-Disposition and Content-Type
       
   492               if ( preg_match('#^Content-Disposition: form-data; name="([^"\a\t\r\n]+)"(?:; filename="([^"\a\t\r\n]+)")?#i', $data_trim, $match) )
       
   493               {
       
   494                 // content-disposition header, set name and mode.
       
   495                 $currval_name = $match[1];
       
   496                 if ( isset($match[2]) )
       
   497                 {
       
   498                   $have_a_file = $match[2];
       
   499                 }
       
   500                 else
       
   501                 {
       
   502                   $have_a_file = false;
       
   503                 }
       
   504               }
       
   505               else if ( preg_match('#^Content-Type: ([a-z0-9-]+/[a-z0-9/-]+)$#i', $data_trim, $match) )
       
   506               {
       
   507                 $currval_type = $match[1];
       
   508               }
       
   509             }
       
   510             else if ( $mode == 'data' )
       
   511             {
       
   512               $currval_data .= $data;
       
   513             }
       
   514             $last_line = $data_trim;
       
   515             $i++;
       
   516           }
   415         }
   517         }
   416         else
   518         else
   417         {
   519         {
   418           $postdata = socket_read($remote, 8388608, PHP_NORMAL_READ);
   520           if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) )
   419         }
   521           {
   420         if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $postdata, $matches) )
   522             $postdata = socket_read($remote, intval($_SERVER['HTTP_CONTENT_LENGTH']), PHP_BINARY_READ);
   421         {
   523           }
   422           if ( isset($matches[1]) )
   524           else
   423           {
   525           {
   424             foreach ( $matches[0] as $i => $_ )
   526             $postdata = socket_read($remote, 8388608, PHP_NORMAL_READ);
       
   527           }
       
   528           if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $postdata, $matches) )
       
   529           {
       
   530             if ( isset($matches[1]) )
   425             {
   531             {
   426               $_POST[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   532               foreach ( $matches[0] as $i => $_ )
       
   533               {
       
   534                 $_POST[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
       
   535               }
   427             }
   536             }
   428           }
   537           }
   429         }
   538         }
   430       }
   539       }
   431       
   540       
   453           {
   562           {
   454             $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   563             $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true;
   455           }
   564           }
   456         }
   565         }
   457       }
   566       }
       
   567       
       
   568       $_GET = $this->parse_multi_depth_array($_GET);
       
   569       $_POST = $this->parse_multi_depth_array($_POST);
       
   570       $_FILES = $this->parse_multi_depth_array($_FILES);
   458       
   571       
   459       // init handler
   572       // init handler
   460       $handler = false;
   573       $handler = false;
   461       
   574       
   462       if ( $uri == '' )
   575       if ( $uri == '' )
   511           continue;
   624           continue;
   512         }
   625         }
   513       }
   626       }
   514       
   627       
   515       $this->send_standard_response($remote, $handler, $uri, $params);
   628       $this->send_standard_response($remote, $handler, $uri, $params);
       
   629       
       
   630       // now that we're done sending the response, delete any temporary uploaded files
       
   631       if ( !empty($_FILES) )
       
   632       {
       
   633         foreach ( $_FILES as $file_data )
       
   634         {
       
   635           if ( file_exists($file_data['tmp_name']) )
       
   636           {
       
   637             @unlink($file_data['tmp_name']);
       
   638           }
       
   639         }
       
   640       }
   516       
   641       
   517       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   642       if ( !$this->in_keepalive && defined('HTTPD_WS_CHILD') )
   518       {
   643       {
   519         // if ( defined('HTTPD_WS_CHILD') )
   644         // if ( defined('HTTPD_WS_CHILD') )
   520         //   status('Closing connection');
   645         //   status('Closing connection');
   759           $result = @call_user_func($handler['function'], $this, $socket);
   884           $result = @call_user_func($handler['function'], $this, $socket);
   760           $this->in_scriptlet = false;
   885           $this->in_scriptlet = false;
   761           $output = ob_get_contents();
   886           $output = ob_get_contents();
   762           ob_end_clean();
   887           ob_end_clean();
   763         }
   888         }
       
   889         // throw an HttpExceptionFatal when you need to break out of an in-progress scriptlet due to an error, use it in place of die() or exit()
       
   890         catch ( HttpExceptionFatal $e )
       
   891         {
       
   892           restore_error_handler();
       
   893           $this->send_http_error($socket, 500, "A handler crashed reporting a fatal exception; see the command line for details.");
       
   894           if ( function_exists('status') )
       
   895             status("fatal exception in handler {$handler['id']}:\n$e");
       
   896           return true;
       
   897         }
   764         catch ( Exception $e )
   898         catch ( Exception $e )
   765         {
   899         {
   766           restore_error_handler();
   900           restore_error_handler();
   767           $this->send_http_error($socket, 500, "A handler crashed with an exception; see the command line for details.");
   901           $this->send_http_error($socket, 500, "There was an uncaught exception during the execution of a scripted handler function. See the command line for details.");
   768           if ( function_exists('status') )
   902           if ( function_exists('status') )
   769             status("caught exception in handler {$handler['id']}:\n$e");
   903             status("uncaught exception in handler {$handler['id']}:\n$e");
   770           return true;
   904           return true;
   771         }
   905         }
   772         restore_error_handler();
   906         restore_error_handler();
   773         
   907         
   774         // the handler function should return this magic string if it writes its own headers and socket data
   908         // the handler function should return this magic string if it writes its own headers and socket data
  1300     }
  1434     }
  1301     
  1435     
  1302     return false;
  1436     return false;
  1303   }
  1437   }
  1304   
  1438   
       
  1439   /**
       
  1440    * Takes a flat array with keys of format foo[bar] and parses it into multiple depths.
       
  1441    * @param array
       
  1442    * @return array
       
  1443    */
       
  1444   
       
  1445   function parse_multi_depth_array($array)
       
  1446   {
       
  1447     foreach ( $array as $key => $value )
       
  1448     {
       
  1449       if ( preg_match('/^([^\[\]]+)\[([^\]]+)\]/', $key, $match) )
       
  1450       {
       
  1451         $parent =& $match[1];
       
  1452         $child =& $match[2];
       
  1453         if ( !isset($array[$parent]) || ( isset($array[$parent]) && !is_array($array[$parent]) ) )
       
  1454         {
       
  1455           $array[$parent] = array();
       
  1456         }
       
  1457         $array[$parent][$child] = $value;
       
  1458         unset($array[$key]);
       
  1459         $array[$parent] = $this->parse_multi_depth_array($array[$parent]);
       
  1460       }
       
  1461     }
       
  1462     return $array;
       
  1463   }
       
  1464   
       
  1465 }
       
  1466 
       
  1467 /**
       
  1468  * Exception class that allows breaking directly out of a scriptlet.
       
  1469  */
       
  1470 
       
  1471 class HttpExceptionFatal extends Exception
       
  1472 {
  1305 }
  1473 }
  1306 
  1474 
  1307 /**
  1475 /**
  1308  * Array of known HTTP status/error codes
  1476  * Array of known HTTP status/error codes
  1309  */
  1477  */