WebServer now does not attempt to destruct if threader is not up
<?php/** * Webserver class * * Greyhound - real web management for Amarok * Copyright (C) 2008 Dan Fuhry * * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details. */require('multithreading.php');/** * Version of the server * @const string */define('HTTPD_VERSION', '0.1b5');/** * Length of keep-alive connections * @const int */define('HTTPD_KEEP_ALIVE_TIMEOUT', 300);/** * Webserver system icons */define('HTTPD_ICON_SCRIPT', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGSSURBVCjPVVFNSwJhEF78Ad79Cf6PvXQRsotUlzKICosuRYmR2RJR0KE6lBFFZVEbpFBSqKu2rum6llFS9HHI4iUhT153n6ZtIWMOM+/MM88z7wwH7s9Ub16SJcnbmrNcxVm2q7Z8/QPvEOtntpj92NkCqITLepEpjix7xQtiLOoQ2b6+E7YAN/5nfOEJ2WbKqOIOJ4bYVMEQx4LfBBQDsvFMhUcCVU1/CxVXmDBGA5ZETrhDCQVcYAPbyEJBhvrnBVPiSpNr6cYDNCQwo4zzU/ySckkgDYuNuVpI42T9k4gLKGMPs/xPzzovQiY2hQYe0jlJfyNNhTqiWDYBq/wBMcSRpnyPzu1oS7WtxjVBSthU1vgVksiQ3Dn6Gp5ah2YOKQo5GiuHPA6xT1EKpxQNCNYejgIR457KKio0S56YckjSa9jo//3mrj+BV0QQagqGTOo+Y7gZIf1puP3WHoLhEb2PjTlCTCWGXtbp8DCX3hZuOdaIc9A+aQvWk4ihq95p67a7nP+u+Ws+r0dql9z/zv0NCYhdCPKZ7oYAAAAASUVORK5CYII=');define('HTTPD_ICON_FOLDER', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGrSURBVDjLxZO7ihRBFIa/6u0ZW7GHBUV0UQQTZzd3QdhMQxOfwMRXEANBMNQX0MzAzFAwEzHwARbNFDdwEd31Mj3X7a6uOr9BtzNjYjKBJ6nicP7v3KqcJFaxhBVtZUAK8OHlld2st7Xl3DJPVONP+zEUV4HqL5UDYHr5xvuQAjgl/Qs7TzvOOVAjxjlC+ePSwe6DfbVegLVuT4r14eTr6zvA8xSAoBLzx6pvj4l+DZIezuVkG9fY2H7YRQIMZIBwycmzH1/s3F8AapfIPNF3kQk7+kw9PWBy+IZOdg5Ug3mkAATy/t0usovzGeCUWTjCz0B+Sj0ekfdvkZ3abBv+U4GaCtJ1iEm6ANQJ6fEzrG/engcKw/wXQvEKxSEKQxRGKE7Izt+DSiwBJMUSm71rguMYhQKrBygOIRStf4TiFFRBvbRGKiQLWP29yRSHKBTtfdBmHs0BUpgvtgF4yRFR+NUKi0XZcYjCeCG2smkzLAHkbRBmP0/Uk26O5YnUActBp1GsAI+S5nRJJJal5K1aAMrq0d6Tm9uI6zjyf75dAe6tx/SsWeD//o2/Ab6IH3/h25pOAAAAAElFTkSuQmCC');define('HTTPD_ICON_FILE', 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAINSURBVBgZBcG/r55zGAfg6/4+z3va01NHlYgzEfE7MdCIGISFgS4Gk8ViYyM2Mdlsko4GSf8Do0FLRCIkghhYJA3aVBtEz3nP89wf11VJvPDepdd390+8Nso5nESBQoq0pfvXm9fzWf19453LF85vASqJlz748vInb517dIw6EyYBIIG49u+xi9/c9MdvR//99MPPZ7+4cP4IZhhTPbwzT2d+vGoaVRRp1rRliVvHq+cfvM3TD82+7mun0o/ceO7NT+/4/KOXjwZU1ekk0840bAZzMQ2mooqh0A72d5x/6sB9D5zYnff3PoYBoWBgFKPKqDKqjCpjKr//dcu9p489dra88cydps30KswACfNEKanSaxhlntjJ8Mv12Paie+vZ+0+oeSwwQ0Iw1xAR1CiFNJkGO4wu3ZMY1AAzBI0qSgmCNJsJUEOtJSMaCTBDLyQ0CknAGOgyTyFFiLI2awMzdEcSQgSAAKVUmAeNkxvWJWCGtVlDmgYQ0GFtgg4pNtOwbBcwQy/Rife/2yrRRVI0qYCEBly8Z+P4qMEMy7JaVw72N568e+iwhrXoECQkfH91kY7jwwXMsBx1L93ZruqrK6uuiAIdSnTIKKPLPFcvay8ww/Hh+ufeznTXu49v95IMoQG3784gYXdTqvRmqn/Wpa/ADFX58MW3L71SVU9ETgEIQQQIOOzub+fhIvwPRDgeVjWDahIAAAAASUVORK5CYII=');/** * Abstraction layer to use */define('HTTPD_SOCKET_LAYER', 'Stream');/** * Simple but full-featured embedded web server written in PHP. * @package Amarok * @subpackage WebControl * @author Dan Fuhry * @license GNU General Public License <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html> */class WebServer{ /** * IP address we're bound to * @var string */ var $bind_address = '127.0.0.1'; /** * Port we're listening on * @var int */ var $port = 8080; /** * Socket abstraction object * @var object */ var $server = null; /** * Server string * @var string */ var $server_string = 'PhpHttpd'; /** * Default document (well default handler) * @var string */ var $default_document = false; /** * List of filenames or handlers used when a directory listing is requested * @var array */ var $directory_index = array('index.html', 'index.htm', 'index', 'default.html', 'default.htm'); /** * HTTP response code set by the handler function * @var int */ var $response_code = 0; /** * Content type set by the current handler function * @var string */ var $content_type = ''; /** * Response headers to send back to the client * @var array */ var $response_headers = array(); /** * List of handlers * @var array */ var $handlers = array(); /** * Switch to control if directory listing is enabled * @var bool */ var $allow_dir_list = false; /** * Switch to control forking support. * @var bool */ var $allow_fork = true; /** * Multi-threading manager. * @var object */ var $threader = false; /** * Keep-alive support uses this to track what the client requested. * Only used if $allow_fork is set to true. * @var bool */ var $in_keepalive = false; /** * UUID for this server instance * @var string */ var $uuid = '00000000-0000-0000-0000-000000000000'; /** * Switch to track whether a scriptlet is running. If it is, send_http_error() does more than normal. * @var bool */ var $in_scriptlet = false; /** * Switch to track whether headers have been sent or not. * @var bool */ var $headers_sent = false; /** * Switch to track if the socket is bound and thus needs to be freed or not * @var bool */ var $socket_initted = false; /** * The list of child processes spawned by this server. * @var array */ var $child_list = array(); /** * The parent process's PID * @var int */ var $parent_pid = 0; /** * List of IPC request handlers * @var array * @access private */ var $ipc_handlers = array(); /** * Sockets for parent and child to communicate * @var resource * @var resource */ var $parent_sock = null; var $child_sock = null; /** * Switched on when a graceful reboot event is sent. * @var bool */ var $reboot_sent = false; /** * Constructor. * @param string IPv4 address to bind to * @param int Port number * @param int If port is under 1024, specify a user ID/name to switch to here * @param int If port is under 1024, specify a group ID/name to switch to here */ function __construct($address = '127.0.0.1', $port = 8080, $targetuser = null, $targetgroup = null) { @set_time_limit(0); @ini_set('memory_limit', '128M'); // make sure we're not running as root // note that if allow_root is true, you must specify a UID/GID (or user/group) to switch to once the socket is bound $allow_root = ( $port < 1024 ) ? true : false; if ( function_exists('posix_geteuid') ) { $euid = posix_geteuid(); $egid = posix_getegid(); $username = posix_getpwuid($euid); $username = $username['name']; $group = posix_getgrgid($egid); $group = $group['name']; if ( $euid == 0 && !$allow_root ) { // running as root but not on a privileged port - die for security burnout("Running as superuser (user \"$username\" and group \"$group\"). This is not allowed for security reasons."); } else if ( $euid == 0 && $allow_root ) { // running as root and port below 1024, so notify of the switch and verify that a target UID and GID were passed if ( $targetuser === null || $targetgroup === null ) { // no target user/group specified burnout("Must specify a target user and group when running server as root"); } // get info about target user/group if ( is_string($targetuser) ) { $targetuser = posix_getpwnam($targetuser); $targetuser = $targetuser['uid']; } if ( is_string($targetgroup) ) { $targetgroup = posix_getgrnam($targetgroup); $targetgroup = $targetgroup['gid']; } // make sure all info is valid if ( !is_int($targetuser) || !is_int($targetgroup) ) { burnout('Invalid user or group specified'); } $userinfo = posix_getpwuid($targetuser); $groupinfo = posix_getgrgid($targetgroup); if ( function_exists('status') ) status("Will switch to user \"{$userinfo['name']}\" and group \"{$groupinfo['name']}\" shortly after binding to socket"); } else if ( $allow_root && $euid > 0 ) { burnout("Must be superuser to bind to ports below 1024"); } } $socket_do_root = ( $allow_root ) ? function_exists('posix_geteuid') : false; $class = 'Socket_' . HTTPD_SOCKET_LAYER; $this->server = new $class(); if ( is_array($address) ) { foreach ( $address as $a ) { if ( is_array($port) ) { foreach ( $port as $p ) { $this->server->tcp_listen($a, $p); } } else { $this->server->tcp_listen($a, $port); } } } else { if ( is_array($port) ) { foreach ( $port as $p ) { $this->server->tcp_listen($address, $p); } } else { $this->server->tcp_listen($address, $port); } } // if running as root and we made it here, switch credentials if ( $socket_do_root ) { posix_setuid($targetuser); posix_setgid($targetgroup); posix_setegid($targetgroup); posix_seteuid($targetuser); if ( function_exists('status') ) status('Successfully switched user ID'); } $this->bind_address = $address; $this->port = $port; $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n"; $this->parent_pid = getmypid(); $this->threader = new Threader(); // create a UUID $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() )); $this->uuid = substr($uuid_base, 0, 8) . '-' . substr($uuid_base, 8, 4) . '-' . substr($uuid_base, 12, 4) . '-' . substr($uuid_base, 16, 4) . '-' . substr($uuid_base, 20, 20); } /** * Destructor. */ function __destruct() { if ( !is_object($this->threader) ) { return false; } if ( !$this->threader->is_child() && $this->socket_initted ) { if ( function_exists('status') ) status('WebServer: destroying socket'); $this->server->destroy(); // tell all children to shut down if ( $this->allow_fork ) { if ( function_exists('status') ) status('WebServer: asking all children to exit'); $this->threader->kill_all_children(); } } } /** * Reboot the server. Useful for applying new settings. * @param string Optional, new IP address to bind to * @param int Optional, new port to bind to * @param bool Optional, whether to allow forking or not */ function reboot($addr = null, $port = null, $allow_fork = null) { if ( function_exists('status') ) status('Reboot request has been received'); $addr = ( !is_string($addr) ) ? $this->bind_address : $addr; $port = ( !is_int($port) ) ? $this->port : $port; $fork = ( !is_bool($allow_fork) ) ? $this->allow_fork : $allow_fork; // // REBOOTING IS A COMPLICATED THING. // We need to ask all children to close any existing connections so // that all relevant socket resources can be freed. Then we need to // call the constructor again to respawn the server, and finally // re-enter the server loop. // // However, reboot() is often called from a PHP-based handler. This // means that some config page probably still needs to be sent. What // we can do here is send an IPC event that fires the actual reboot, // then return to allow the current page to finish up. We also need // to signal the current process to shut down any existing keep- // alive connections. This can be accomplished by setting in_keepalive // to false. // // Kill the entire child after this response is sent $this->in_keepalive = false; // If we're the parent process, we need to know that a reboot event // was fired, and thus the server's main socket needs to be destroyed // and recreated. This is just done with another boolean switch. $this->reboot_sent = true; // this is really to track if there are any children $oldfork = $this->allow_fork; // Set our new server flags $this->bind_address = $addr; $this->port = $port; $this->allow_fork = $fork; // If we're a child, we have to tell the parent what the hell is // going on, and then get out of here as quickly as possible // (and other children should do the same). If this is a child, // fire an IPC reboot event. Else, fire a "die all" event if ( $this->threader->is_child() ) { if ( function_exists('status') ) status('Signaling parent with parameter changes (fork = ' . intval($fork) . ') + reboot request'); // this is the child $this->threader->ipc_send(array( 'action' => 'ws_reboot', 'addr' => $addr, 'port' => $port, 'fork' => $fork )); } else if ( !$this->threader->is_child() && $oldfork ) { if ( function_exists('status') ) status('Waiting on all children'); // this is the parent, and there are children present $this->threader->kill_all_children(); // all children are dead, we are ok to respawn $this->respawn(); } else { // this is a childless parent; delay any action until the current // request has been sent (do nothing now) } } /** * Respawns the server. All children should be dead, and any client * connections must be closed. */ function respawn() { $this->reboot_sent = false; if ( function_exists('status') ) status('Respawn event sent'); $this->server->destroy(); unset($this->server); // try to spawn up to 10 times for ( $i = 0; $i < 10; $i++ ) { try { $this->__construct($this->bind_address, $this->port); } catch ( Exception $e ) { if ( $i == 9 ) { if ( function_exists('burnout') ) { burnout("Couldn't respawn because one of the child processes did not die, and thus the port was not freed."); } exit(1); } if ( function_exists('status') ) { status("Respawn failed, retrying in 2 seconds"); } usleep(2000000); continue; } break; } if ( function_exists('status') ) status('Respawn is complete, entering server loop with bind_address = ' . $this->bind_address . ' allow_fork = ' . strval(intval($this->allow_fork))); // all handlers should already be set up, so just break out and we should automatically continue the server loop } /** * Main server loop */ function serve() { while ( true ) { ## ## STAGE 0: CLEANUP FROM PREVIOUS RUN ## // if this is a child process, we're finished - close up shop if ( $this->threader->is_child() && !$this->in_keepalive ) { if ( function_exists('status') ) status('Exiting child process'); $remote->destroy(); exit(0); } ## ## STAGE 1: LISTENER AND INIT ## // wait for connection... if ( !$this->threader->is_child() ) { $remote = $this->server->accept(); } if ( !$remote ) { $this->in_keepalive = false; continue; } // fork off if possible if ( function_exists('pcntl_fork') && $this->allow_fork && !$this->in_keepalive ) { $fork_result = $this->threader->fork(); if ( $fork_result == Threader::FORK_CHILD ) { // this is the child define('HTTPD_WS_CHILD', 1); } else { // we are the parent, continue listening $remote->soft_shutdown(); $this->child_list[] = $fork_result; continue; } } $this->in_keepalive = false; $this->headers_sent = false; $this->in_scriptlet = false; ## ## STAGE 2: READ REQUEST ## // this is a complicated situation because we need to keep enough ticks going to properly handle // signals, meaning we can't use stream_set_timeout() and instead need to rely on our own timing // logic. setting the timeout to a short period, say 200,000 usec, we can minimize CPU usage and // have a good response time. $remote->set_timeout(0, 200000); $start_time = microtime(true); $client_headers = ''; $last_line = ''; while ( true ) { if ( $start_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) || $remote->is_eof() ) { // request expired -- end the process here if ( !$this->threader->is_child() ) $remote->destroy(); continue 2; } $line = strval($remote->read_normal()); $line = str_replace("\r", "", $line); // raw layer wants to send 2 empty lines through, stream layer wants to send one. $last_line_check = ( HTTPD_SOCKET_LAYER != 'Raw' || ( HTTPD_SOCKET_LAYER == 'Raw' && $last_line == "\n" ) ); if ( $line == "\n" && $last_line_check ) // we have two newlines in a row, break out since we have a full request break; $client_headers .= $line; $last_line = $line; } ## ## STAGE 3: PARSE REQUEST AND HEADERS ## // parse request $client_headers = trim($client_headers); if ( isset($last_finish_time) && empty($client_headers) && $this->threader->is_child() && $last_finish_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) ) { status('[debug] keep-alive connection timed out (checkpoint 2)'); continue; // will jump back to the start of the loop and kill the child process } $client_headers = explode("\n", $client_headers); // first line $request = $client_headers[0]; if ( !preg_match('/^(GET|POST) \/([^ ]*) HTTP\/1\.[01]$/', $request, $match) ) { $this->send_http_error($remote, 400, 'Your client issued a malformed or illegal request.'); continue; } $method =& $match[1]; $uri =& $match[2]; // set client header SERVER variables foreach ( $_SERVER as $key => $_ ) { if ( preg_match('/^HTTP_/', $key) ) unset($_SERVER[$key]); } if ( !isset($_SERVER['SERVER_SOFTWARE']) ) { $_SERVER['SERVER_SOFTWARE'] =& $this->server_string; } unset($client_headers[0]); foreach ( $client_headers as $line ) { if ( !preg_match('/^([A-z0-9-]+): (.+)$/is', $line, $match) ) continue; $key = 'HTTP_' . strtoupper(str_replace('-', '_', $match[1])); $_SERVER[$key] = $match[2]; } // enable keep-alive if requested if ( isset($_SERVER['HTTP_CONNECTION']) && $this->threader->is_child() ) { $this->in_keepalive = ( strtolower($_SERVER['HTTP_CONNECTION']) === 'keep-alive' ); } // process cookies $_COOKIE = array(); if ( isset($_SERVER['HTTP_COOKIE']) ) { preg_match_all('/([a-z0-9_-]+)=([^;]*)(?:;|$)/', trim($_SERVER['HTTP_COOKIE']), $matches); foreach ( $matches[0] as $i => $match ) { $_COOKIE[$matches[1][$i]] = str_replace('\\r', "\r", str_replace('\\n', "\n", str_replace(rawurlencode(';'), ';', $matches[2][$i]))); } } // parse authorization, if any unset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']); if ( isset($_SERVER['HTTP_AUTHORIZATION']) ) { $data = $_SERVER['HTTP_AUTHORIZATION']; $data = substr(strstr($data, ' '), 1); $data = base64_decode($data); $_SERVER['PHP_AUTH_USER'] = substr($data, 0, strpos($data, ':')); $_SERVER['PHP_AUTH_PW'] = substr(strstr($data, ':'), 1); } // anything on POST? $postdata = ''; $_POST = array(); $_FILES = array(); if ( $method == 'POST' ) { $this->parse_post_data($remote); } // parse URI $params = ''; if ( strstr($uri, '?') ) { $params = substr(strstr($uri, '?'), 1); $uri = substr($uri, 0, strpos($uri, '?')); } // set some server vars $_SERVER['REQUEST_URI'] = '/' . rawurldecode($uri); $_SERVER['REQUEST_METHOD'] = $method; // get remote IP and port $remote->get_peer_info($_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT']); // process $_GET $_GET = array(); if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]-]+)(=[^ &]+)?/', $params, $matches) ) { if ( isset($matches[1]) ) { foreach ( $matches[0] as $i => $_ ) { $_GET[$matches[2][$i]] = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true; } } } // Parse GET, POST, and FILES into multi-depth arrays $_GET = $this->parse_multi_depth_array($_GET); $_POST = $this->parse_multi_depth_array($_POST); $_FILES = $this->parse_multi_depth_array($_FILES); ## ## STAGE 4: HANDLER RESOLUTION ## // init handler $handler = false; if ( $uri == '' ) { // user requested the root (/). If there's a default document, use that; else, see if we can do a directory listing $uri = strval($this->default_document); if ( !$this->default_document && $this->allow_dir_list ) { // we can list directories and this was requested by the user, so list it out $handler = array('type' => 'rootdir'); } } $uri_parts = explode('/', $uri); // hook for the special UUID handler if ( $uri_parts[0] === $this->uuid && !$handler ) { $handler = array('type' => 'sysuuid'); } // loop through URI parts, see if a handler is set if ( !$handler ) { for ( $i = count($uri_parts) - 1; $i >= 0; $i-- ) { $handler_test = implode('/', $uri_parts); if ( isset($this->handlers[$handler_test]) ) { $handler = $this->handlers[$handler_test]; $handler['id'] = $handler_test; break; } unset($uri_parts[$i]); } } if ( !$handler ) { // try to make a fakie if ( $this->check_for_handler_children($uri) ) { $handler = array( 'type' => 'folder', 'dir' => "/{$this->uuid}/__fakie", 'id' => $uri ); } if ( !$handler ) { $this->send_http_error($remote, 404, "The requested URL /$uri was not found on this server."); continue; } } ## ## STAGE 5: HANDLER CALL ## $this->send_standard_response($remote, $handler, $uri, $params); ## ## STAGE 6: CLEANUP ## // now that we're done sending the response, delete any temporary uploaded files if ( !empty($_FILES) ) { foreach ( $_FILES as $file_data ) { if ( file_exists($file_data['tmp_name']) ) { @unlink($file_data['tmp_name']); } } } if ( !$this->in_keepalive && $this->threader->is_child() ) { // connection: close // continue on to the shutdown handler continue; } else if ( $this->threader->is_child() ) { // if ( $this->threader->is_child() ) // status('Continuing connection'); // $remote->write("\r\n\r\n"); $last_finish_time = microtime(true); } else { // standalone process $remote->destroy(); // if a reboot was fired and we're running in single-process mode, now is the time to respawn if ( !$this->threader->is_child() && $this->reboot_sent ) { $this->respawn(); } } } } /** * Parse POST data and format $_POST and $_FILES. * @param resource Remote socket */ function parse_post_data($remote) { $postdata = ''; // read POST data if ( isset($_SERVER['HTTP_CONTENT_TYPE']) && preg_match('#^multipart/form-data; ?boundary=([A-z0-9_-]+)$#i', $_SERVER['HTTP_CONTENT_TYPE'], $match) ) { // this is a multipart request $boundary =& $match[1]; $mode = 'data'; $last_line = ''; $i = 0; while ( $data = $remote->read_normal(8388608) ) { $data_trim = trim($data, "\r\n"); if ( $mode != 'data' ) { $data = str_replace("\r", '', $data); } if ( ( $data_trim === "--$boundary" || $data_trim === "--$boundary--" ) && $i > 0 ) { // trim off the first LF and the last CRLF if ( HTTPD_SOCKET_LAYER == 'Raw' ) $currval_data = substr($currval_data, 1, strlen($currval_data)-3); else $currval_data = substr($currval_data, 0, strlen($currval_data)-2); // this is the end of a part of the message; parse it into either $_POST or $_FILES if ( is_string($have_a_file) ) { // write data to a temporary file $errcode = UPLOAD_ERR_OK; $tempfile = tempnam('phpupload', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' )); if ( $fh = @fopen($tempfile, 'w') ) { if ( empty($have_a_file) ) { $errcode = UPLOAD_ERR_NO_FILE; } else { fwrite($fh, $currval_data); } fclose($fh); } else { $errcode = UPLOAD_ERR_CANT_WRITE; } $_FILES[$currval_name] = array( 'name' => $have_a_file, 'type' => $currval_type, 'size' => filesize($tempfile), 'tmp_name' => $tempfile, 'error' => $errcode ); } else { if ( preg_match('/\[\]$/', $currval_name) ) { if ( !isset($_POST[$currval_name]) || ( isset($_POST[$currval_name]) && !is_array($_POST[$currval_name]) ) ) $_POST[$currval_name] = array(); $_POST[$currval_name][] = $currval_data; } else { $_POST[$currval_name] = $currval_data; } } } if ( $data_trim === "--$boundary" ) { // switch from "data" mode to "headers" mode $currval_name = ''; $currval_data = ''; $currval_type = ''; $have_a_file = false; $mode = 'headers'; } else if ( $data_trim === "--$boundary--" ) { // end of request break; } else if ( ( empty($data_trim) && ( ( HTTPD_SOCKET_LAYER == 'Raw' && empty($last_line) ) || HTTPD_SOCKET_LAYER != 'Raw' ) ) && $mode == 'headers' ) { // start of data $mode = 'data'; } else if ( $mode == 'headers' ) { // read header // we're only looking for Content-Disposition and Content-Type if ( preg_match('#^Content-Disposition: form-data; name="([^"\a\t\r\n]+)"(?:; filename="([^"\a\t\r\n]+)")?#i', $data_trim, $match) ) { // content-disposition header, set name and mode. $currval_name = $match[1]; if ( isset($match[2]) ) { $have_a_file = $match[2]; } else { $have_a_file = false; } } else if ( preg_match('#^Content-Type: ([a-z0-9-]+/[a-z0-9/-]+)$#i', $data_trim, $match) ) { $currval_type = $match[1]; } } else if ( $mode == 'data' ) { $currval_data .= $data; } $last_line = $data_trim; $i++; } } else { if ( isset($_SERVER['HTTP_CONTENT_LENGTH']) ) { $postdata = $remote->read_binary(intval($_SERVER['HTTP_CONTENT_LENGTH'])); } else { $postdata = $remote->read_normal(8388608); } if ( preg_match_all('/(^|&)([a-z0-9_\.\[\]%-]+)(=[^ &]+)?/i', $postdata, $matches) ) { if ( isset($matches[1]) ) { foreach ( $matches[0] as $i => $_ ) { $currval_name =& $matches[2][$i]; $currval_data = ( !empty($matches[3][$i]) ) ? urldecode(substr($matches[3][$i], 1)) : true; $currval_name = urldecode($currval_name); if ( preg_match('/\[\]$/', $currval_name) ) { $basename = preg_replace('/\[\]$/', '', $currval_name); if ( !isset($_POST[$basename]) || ( isset($_POST[$basename]) && !is_array($_POST[$basename]) ) ) $_POST[$basename] = array(); $_POST[$basename][] = $currval_data; } else { $_POST[$currval_name] = $currval_data; } } } } } } /** * Sends the client appropriate response headers. * @param resource Socket connection to client * @param int HTTP status code, defaults to 200 * @param string Content type, defaults to text/html * @param string Additional headers to send, optional */ function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '') { global $http_responses; if ( $this->headers_sent ) return false; // this is reset after the request is completed (hopefully) $this->headers_sent = true; $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown'; $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)'; if ( function_exists('status') ) status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}"); $headers = str_replace("\r\n", "\n", $headers); $headers = str_replace("\n", "\r\n", $headers); $headers = preg_replace("#[\r\n]+$#", '', $headers); $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close'; $socket->write("HTTP/1.1 $http_code $reason_code\r\n"); $socket->write("Server: $this->server_string"); $socket->write("Connection: $connection\r\n"); $socket->write("Content-Type: $contenttype\r\n"); if ( !empty($headers) ) { $socket->write("$headers\r\n"); } $socket->write("\r\n"); } /** * Sends a normal response * @param resource Socket connection to client * @param array Handler */ function send_standard_response($socket, $handler) { switch ( $handler['type'] ) { case 'folder': // security $uri = str_replace("\000", '', $_SERVER['REQUEST_URI']); if ( preg_match('#(\.\./|\/\.\.)#', $uri) || strstr($uri, "\r") || strstr($uri, "\n") ) { $this->send_http_error($socket, 403, 'Access to this resource is forbidden.'); } // import mimetypes global $mime_types; // trim handler id from uri $uri_full = rtrim($uri, '/'); $uri = substr($uri, strlen($handler['id']) + 1); // get file path $file_path = rtrim($handler['dir'], '/') . $uri; if ( file_exists($file_path) || $this->check_for_handler_children($uri_full) ) { // found it :-D // is this a directory? if ( is_dir($file_path) || $this->check_for_handler_children($uri_full) ) { // allowed to list? if ( !$this->allow_dir_list ) { $this->send_http_error($socket, 403, "Directory listing is not allowed."); return true; } // yes, list contents try { $dir_list = $this->list_directory($uri_full, true); } catch ( Exception $e ) { $this->send_http_error($socket, 500, "Directory listing failed due to an error in the listing core method. This may indicate that the webserver process does not have filesystem access to the specified directory.<br /><br />Debugging details:<pre>$e</pre>"); return true; } $root = rtrim($uri_full, '/') . '/'; $parent = rtrim(dirname(rtrim($uri_full, '/')), '/') . '/'; $contents = <<<EOF<html> <head> <title>Index of: $root</title> <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" /> </head> <body> <h1>Index of $root</h1> <ul> <li><tt><a href="$parent">Parent directory</a></tt></li>EOF; foreach ( $dir_list as $filename => $info ) { $ts = ( $info['type'] == 'folder' ) ? '/' : ''; $contents .= ' <li><tt><a href="' . htmlspecialchars($root . basename($filename) . $ts) . '"><img alt="[ ]" src="/' . $this->uuid . '/' . $info['type'] . '.png" /> ' . htmlspecialchars($filename) . $ts . '</a></tt></li>' . "\n "; } $contents .= "\n </ul>\n <address>Served by {$this->server_string}</address>\n</body>\n</html>\n\n"; $sz = strlen($contents); $this->send_client_headers($socket, 200, 'text/html', "Content-length: $sz\r\n"); $socket->write($contents); return true; } // try to open the file $fh = @fopen($file_path, 'r'); if ( !$fh ) { // can't open it, send a 404 $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); } // get size $sz = filesize($file_path); // mod time $time = date('r', filemtime($file_path)); // all good, send headers $fileext = substr($file_path, strrpos($file_path, '.') + 1); $mimetype = ( isset($mime_types[$fileext]) ) ? $mime_types[$fileext] : 'application/octet-stream'; $this->send_client_headers($socket, 200, $mimetype, "Content-length: $sz\r\nLast-Modified: $time\r\n"); // send body while ( $blk = @fread($fh, 768000) ) { $socket->write($blk); } fclose($fh); return true; } else { $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); } break; case 'file': // import mimetypes global $mime_types; // get file path $file_path = $handler['file']; if ( file_exists($file_path) ) { // found it :-D // is this a directory? if ( is_dir($file_path) ) { $this->send_http_error($socket, 500, "Host script mapped a directory as a file entry."); return true; } // try to open the file $fh = @fopen($file_path, 'r'); if ( !$fh ) { // can't open it, send a 404 $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); } // get size $sz = filesize($file_path); // mod time $time = date('r', filemtime($file_path)); // all good, send headers $fileext = substr($file_path, strrpos($file_path, '.') + 1); $mimetype = ( isset($mime_types[$fileext]) ) ? $mime_types[$fileext] : 'application/octet-stream'; $this->send_client_headers($socket, 200, $mimetype, "Content-length: $sz\r\nLast-Modified: $time\r\n"); // send body while ( $blk = @fread($fh, 768000) ) { $socket->write($blk); } fclose($fh); return true; } else { $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); } break; case 'script': // init vars $this->content_type = 'text/html'; $this->response_code = 200; $this->response_headers = array(); // error handling @set_error_handler(array($this, 'function_error_handler'), E_ALL); try { ob_start(); $this->in_scriptlet = true; $result = @call_user_func($handler['function'], $this, $socket); $this->in_scriptlet = false; $output = ob_get_contents(); ob_end_clean(); } // 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() catch ( HttpExceptionFatal $e ) { ob_end_clean(); restore_error_handler(); $this->send_http_error($socket, 500, "A handler crashed reporting a fatal exception; see the command line for details."); if ( function_exists('status') ) status("fatal exception in handler {$handler['id']}:\n$e"); return true; } catch ( HttpSuccess $e ) { // just finish with success $this->in_scriptlet = false; $output = ob_get_contents(); ob_end_clean(); } catch ( Exception $e ) { ob_end_clean(); restore_error_handler(); $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."); if ( function_exists('status') ) status("uncaught exception in handler {$handler['id']}:\n$e"); return true; } restore_error_handler(); // the handler function should return this magic string if it writes its own headers and socket data if ( $output == '__break__' ) { return true; } if ( !$this->headers_sent ) { // $this->header('Transfer-encoding: chunked'); $this->header("Content-length: " . strlen($output)); $headers = implode("\r\n", $this->response_headers); // write headers $this->send_client_headers($socket, $this->response_code, $this->content_type, $headers); // chunk output // $output = dechex(strlen($output)) . "\r\n$output"; // write body $socket->write($output); $this->headers_sent = false; } break; case 'sysuuid': // requested one of the system's icon images $uri_parts = explode('/', $_SERVER['REQUEST_URI']); if ( count($uri_parts) != 3 ) { $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); } // load image data $filename =& $uri_parts[2]; switch ( $filename ) { case 'script.png': ( !isset($image_data) ) ? $image_data = HTTPD_ICON_SCRIPT : null; case 'folder.png': ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FOLDER : null; case 'file.png': ( !isset($image_data) ) ? $image_data = HTTPD_ICON_FILE : null; $image_data = base64_decode($image_data); $found = true; $type = 'image/png'; break; case 'dirlist.css': $type = 'text/css'; $found = true; $image_data = <<<EOF/** * PhpHttpd directory list visual style */html { background-color: #c9c9c9; margin: 0; padding: 0;}body { background-color: #ffffff; margin: 20px; padding: 10px; border: 1px solid #aaaaaa;}a { text-decoration: none;}a img { border-width: 0;}ul { list-style-type: none;}EOF; break; default: $found = false; } // ship it out if ( $found ) { $lm_date = date('r', filemtime(__FILE__)); $size = strlen($image_data); $this->send_client_headers($socket, 200, $type, "Last-Modified: $lm_date\r\nContent-Length: $size"); $socket->write($image_data); } else { $this->send_http_error($socket, 404, "The requested URL " . htmlspecialchars($_SERVER['REQUEST_URI']) . " was not found on this server."); } return true; break; case 'rootdir': // // list the contents of the document root // $handlers = $this->list_directory('/', true); $contents = <<<EOF<html> <head> <title>Index of: /</title> <link rel="stylesheet" type="text/css" href="/{$this->uuid}/dirlist.css" /> </head> <body> <h1>Index of /</h1> <ul>EOF; $html = ''; // generate content foreach ( $handlers as $uri => $handler ) { switch($handler['type']) { case 'folder': $image = 'folder.png'; $abbr = 'DIR'; $add = '/'; break; case 'file': default: $image = 'file.png'; $abbr = ' '; $add = ''; break; case 'script': $image = 'script.png'; $abbr = 'CGI'; $add = ''; break; } $html .= " <li><tt><a href=\"/$uri\"><img alt=\"[{$abbr}]\" src=\"/{$this->uuid}/{$image}\" /> {$uri}{$add}</a></tt></li>\n "; } $contents .= $html; $contents .= <<<EOF</ul> <address>Served by {$this->server_string}</address> </body></html>EOF; // get length $len = strlen($contents); // send headers $this->send_client_headers($socket, 200, 'text/html', "Content-Length: $len"); // write to the socket $socket->write($contents); return true; break; } } /** * Adds an HTTP header value to send back to the client * @var string Header */ function header($str) { if ( preg_match('#HTTP/1\.[01] ([0-9]+) (.+?)[\s]*$#', $str, $match) ) { $this->response_code = intval($match[1]); return true; } else if ( preg_match('#Content-type: ([^ ;]+)#i', $str, $match) ) { $this->content_type = $match[1]; return true; } $this->response_headers[] = $str; return true; } /** * Sets a cookie. Identical to PHP's setcookie() function. * @param string Cookie name * @param string Cookie value * @param int Expiration time of cookie as a UNIX timestamp; if omitted or set to zero, cookie will expire at the end of the user's browser session * @param string Path of the cookie * @param string Domain the cookie is available under * @param bool If true, browser will only send the cookie through an HTTPS connection. * @param bool If true, cookie will not be accessible to client-side code */ function setcookie($cookiename, $cookievalue, $expiry = false, $path = false, $domain = false, $secure = false, $httponly = false) { $header = "Set-Cookie: $cookiename=$cookievalue"; if ( !empty($expiry) ) $header .= "; expires=" . gmdate('D, d-M-Y H:i:s T', $expiry); if ( !empty($path) ) $header .= "; path=$path"; if ( !empty($domain) ) $header .= "; domain=$domain"; if ( $secure ) $header .= "; secure"; if ( $httponly ) $header .= "; httponly"; if ( is_int($expiry) && $expiry < time() ) { unset($_COOKIE[$cookiename]); } else { $_COOKIE[$cookiename] = $cookievalue; } $this->header($header); return $header; } /** * Sends the client an HTTP error page * @param resource Socket connection to client * @param int HTTP status code * @param string Detailed error string */ function send_http_error($socket, $http_code, $errstring) { global $http_responses; $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown'; // generate error page $html = <<<EOF<html> <head> <title>$http_code $reason_code</title> </head> <body> <h1>$http_code $reason_code</h1> <p>$errstring</p> <hr /> <address>Served by $this->server_string</address> </body></html>EOF; // length of the response (required if we want keep-alive to work) $this->header('Content-length: ' . strlen($html)); // if we're in a scriptlet, include custom headers if ( $this->in_scriptlet ) $headers = implode("\r\n", $this->response_headers); else $headers = 'Content-length: ' . strlen($html); $this->send_client_headers($socket, $http_code, 'text/html', $headers); $socket->write($html); } /** * Adds a new handler * @param string URI, minus the initial / * @param string Type of handler - function or dir * @param string Value - function name or absolute/relative path to directory */ function add_handler($uri, $type, $value) { if ( $type == 'dir' ) $type = 'folder'; if ( $type == 'function' ) $type = 'script'; switch($type) { case 'folder': $this->handlers[$uri] = array( 'type' => 'folder', 'dir' => $value ); break; case 'file': $this->handlers[$uri] = array( 'type' => 'file', 'file' => $value ); break; case 'script': $this->handlers[$uri] = array( 'type' => 'script', 'function' => $value ); break; } } /** * Error handling function * @param see <http://us.php.net/manual/en/function.set-error-handler.php> */ function function_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { echo '<div style="border: 1px solid #AA0000; background-color: #FFF0F0; padding: 10px;">'; echo "<b>PHP warning/error:</b> type $errno ($errstr) caught in <b>$errfile</b> on <b>$errline</b><br />"; // echo "Error context:<pre>" . htmlspecialchars(print_r($errcontext, true)) . "</pre>"; echo '</div>'; } /** * Lists out the contents of a directory, including virtual handlers. * @example * Example return data: (will be ksorted) <code> array( 'bar' => 'folder', 'baz' => 'script', 'foo' => 'file' ); </code> * @param string Directory name, relative to the server's document root * @param bool If true, sorts folders first (default: false) * @return array Exception thrown on failure */ function list_directory($dir, $folders_first = false) { // clean slashes from the directory name $dir = trim($dir, '/'); if ( $dir == '' ) { // // list the root, which can consist only of handlers // // copy the handlers array, which we need to ksort $handlers = $this->handlers; // get rid of multi-depth handlers foreach ( $handlers as $uri => $handler ) { if ( strpos($uri, '/') ) { unset($handlers[$uri]); $newuri = explode('/', $uri); if ( !isset($handlers[$newuri[0]]) ) { $handlers[$newuri[0]] = array( 'type' => 'folder' ); } } } ksort($handlers); if ( $folders_first ) { // sort folders first $handlers_sorted = array(); foreach ( $handlers as $uri => $handler ) { if ( $handler['type'] == 'folder' ) $handlers_sorted[$uri] = $handler; } foreach ( $handlers as $uri => $handler ) { if ( $handler['type'] != 'folder' ) $handlers_sorted[$uri] = $handler; } $handlers = $handlers_sorted; unset($handlers_sorted); } // done return $handlers; } else { // list something within the root $dir_stack = explode('/', $dir); // lookup handler $handler_search = $dir; $found_handler = false; $fake_handler = false; $i = 1; while ( $i > 0 ) { if ( isset($this->handlers[$handler_search]) ) { $found_handler = true; break; } $i = strrpos($handler_search, '/'); $handler_search = substr($handler_search, 0, strrpos($handler_search, '/')); } if ( $this->check_for_handler_children($dir) ) { $fake_handler = true; } else if ( !$found_handler ) { // nope. not there. throw new Exception("ERR_NO_SUCH_FILE_OR_DIRECTORY"); } // make sure this is a directory if ( !$fake_handler ) { $handler =& $handler_search; if ( $this->handlers[$handler]['type'] != 'folder' ) { throw new Exception("ERR_NOT_A_DIRECTORY"); } // determine real path $real_path = realpath($this->handlers[$handler]['dir'] . substr($dir, strlen($handler))); // directory is resolved; list contents $dir_contents = array(); if ( $dr = opendir($real_path) ) { while ( $dh = readdir($dr) ) { if ( $dh == '.' || $dh == '..' ) { continue; } $dir_contents[$dh] = array( 'type' => ( is_dir("$real_path/$dh") ) ? 'folder' : 'file', 'size' => filesize("$real_path/$dh"), 'time' => filemtime("$real_path/$dh") ); } } else { // only if directory open failed throw new Exception("ERR_PERMISSION_DENIED"); } closedir($dr); // some cleanup unset($handler, $handler_search); } // list any additional handlers in there foreach ( $this->handlers as $handler => $info ) { // parse handler name $handler_name = explode('/', trim($handler, '/')); // is this handler in this directory? if ( count($handler_name) != count($dir_stack) + 1 ) { continue; } foreach ( $dir_stack as $i => $_ ) { if ( $dir_stack[$i] != $handler_name[$i] ) { continue 2; } } // it's in here! $dir_contents[ basename($handler) ] = array( 'type' => $info['type'] ); } // list "fake" handlers foreach ( $this->handlers as $handler => $info ) { // parse handler name $handler_name = explode('/', trim($handler, '/')); // is this handler somewhere underneath this directory? if ( count($handler_name) < count($dir_stack) + 2 ) { continue; } // path check foreach ( $dir_stack as $i => $_ ) { if ( $dir_stack[$i] != $handler_name[$i] ) { continue 2; } } // create a "fake" directory $fakie_name = $handler_name[ count($dir_stack) ]; $dir_contents[$fakie_name] = array( 'type' => 'folder' ); } if ( $folders_first ) { // perform folder sorting $unsorted = $dir_contents; ksort($unsorted); $dir_contents = array(); foreach ( $unsorted as $name => $info ) { if ( $info['type'] == 'folder' ) $dir_contents[$name] = $info; } foreach ( $unsorted as $name => $info ) { if ( $info['type'] != 'folder' ) $dir_contents[$name] = $info; } } else { // not sorting with folders first, so just alphabetize ksort($dir_contents); } // done return $dir_contents; } } /** * Searches deeper to see if there are sub-handlers within a path to see if a fake handler can be created * @param string URI * @return bool */ function check_for_handler_children($file_path) { $file_path = trim($file_path, '/'); $dir_stack = explode('/', $file_path); // make sure this isn't a "real" handler if ( isset($this->handlers[$file_path]) ) { return false; } // list any additional handlers in there foreach ( $this->handlers as $handler => $info ) { // parse handler name $handler_name = explode('/', trim($handler, '/')); // is this handler in this directory? if ( count($handler_name) != count($dir_stack) + 1 ) { continue; } foreach ( $dir_stack as $i => $_ ) { if ( $dir_stack[$i] != $handler_name[$i] ) { continue 2; } } // it's in here! return true; } return false; } /** * Takes a flat array with keys of format foo[bar] and parses it into multiple depths. * @param array * @return array */ function parse_multi_depth_array($array) { foreach ( $array as $key => $value ) { if ( preg_match('/^([^\[\]]+)\[([^\]]*)\]/', $key, $match) ) { $parent =& $match[1]; $child =& $match[2]; if ( !isset($array[$parent]) || ( isset($array[$parent]) && !is_array($array[$parent]) ) ) { $array[$parent] = array(); } if ( empty($child) ) { $array[$parent][] = $value; } else { $array[$parent][$child] = $value; } unset($array[$key]); $array[$parent] = $this->parse_multi_depth_array($array[$parent]); } } return $array; } /** * Handle an IPC event. Called only upon SIGUSR2. */ function _ipc_event() { /* case 'set_addr': $this->bind_address = $param; break; case 'set_port': $this->port = intval($param); break; case 'set_fork': $this->allow_fork = ( $param == '1' ); break; case 'reboot': if ( !$this->threader->is_child() ) { list(, $addr, $port, $fork) = explode(' ', $line); $fork = ( $fork === '1' ); $this->reboot($addr, intval($port), $fork); } break; default: if ( isset($this->ipc_handlers[$action]) ) { @call_user_func($this->ipc_handlers[$action], $line); } break; } */ }}/** * Socket abstraction layer - low-level socket functions (socket_*) */class Socket_Raw{ var $sock = array(); var $socket_initted = false; function tcp_listen($address, $port) { // do we have socket functions? if ( !function_exists('socket_create') ) { burnout('System does not support socket functions. Please rebuild your PHP or install an appropriate extension.'); } $sockid = count($this->sock); $socktype = ( strstr($address, ':') ) ? AF_INET6 : AF_INET; $this->sock[$sockid] = @socket_create($socktype, SOCK_STREAM, getprotobyname('tcp')); if ( !$this->sock[$sockid] ) throw new Exception('Could not create socket'); $result = @socket_bind($this->sock[$sockid], $address, $port); if ( !$result ) throw new Exception("Could not bind to $address:$port"); $this->socket_initted = true; $result = @socket_listen($this->sock[$sockid], SOMAXCONN); if ( !$result ) throw new Exception("Could not listen for connections $address:$port"); $this->socket_initted = true; } function destroy() { if ( $this->socket_initted ) { // http://us3.php.net/manual/en/function.socket-bind.php if ( is_array($this->sock) ) { foreach ( $this->sock as $sock ) { if ( !@socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1) ) { echo socket_strerror(socket_last_error($sock)) . "\n"; } @socket_shutdown($sock, 2); @socket_close($sock); } } else { if ( !@socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1) ) { echo socket_strerror(socket_last_error($this->sock)) . "\n"; } @socket_shutdown($this->sock, 2); @socket_close($this->sock); } } } function accept() { $remote = false; foreach ( $this->sock as $sock ) { $timeout = 200000; switch(@socket_select($r = array($sock), $w = array($sock), $e = array($sock), 0, $timeout)) { case 2: return false; case 1: $remote = @socket_accept($sock); $return = new Socket_Raw(); $return->sock = $remote; $return->socket_initted = true; return $return; break; case 0: continue; } } } /** * Closes the socket but doesn't destroy it. */ function soft_shutdown() { @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1); socket_close($this->sock); } function set_timeout($timeout, $usec = false) { // doesn't work in this. } function read_normal($length = 1024) { return @socket_read($this->sock, $length, PHP_NORMAL_READ); } function read_binary($length = 1024) { return @socket_read($this->sock, $length, PHP_BINARY_READ); } function timed_out() { $md = @socket_get_status($this->sock); return ( isset($md['timed_out']) ) ? $md['timed_out'] : false; } function get_peer_info(&$addr, &$port) { socket_getpeername($this->sock, $addr, $port); } function write($data) { return @socket_write($this->sock, $data); } function is_eof() { // feof() not supported return false; }}/** * Socket abstraction layer - PHP stream support */class Socket_Stream{ var $sock = array(); var $socket_initted = false; function tcp_listen($address, $port) { // does PHP support this? if ( !function_exists('stream_socket_server') ) { burnout('System does not support stream functions. Please rebuild your PHP or install an appropriate extension.'); } if ( strstr($address, ':') ) { // ipv6 address (probably) $address = "[$address]"; } $sockid = count($this->sock); $this->sock[$sockid] = @stream_socket_server("tcp://$address:$port", $errno, $errstr); if ( !$this->sock[$sockid] ) { throw new Exception("Could not create the socket: error $errno: $errstr"); } } function destroy() { if ( $this->socket_initted ) { // PHP >= 5.2.1 if ( is_array($this->sock) ) { foreach ( $this->sock as $sock ) { if ( function_exists('stream_socket_shutdown') ) { @stream_socket_shutdown($sock, STREAM_SHUT_RDWR); } while ( !@fclose($sock) ) { usleep(100000); } } } else { if ( function_exists('stream_socket_shutdown') ) { @stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR); } while ( !@fclose($this->sock) ) { usleep(100000); } } } } function accept() { // the goal of a custom accept() with *_select() is to tick every 200ms to allow signals. foreach ( $this->sock as $sock ) stream_set_blocking($sock, 1); $timeout = 200000; $r = $this->sock; $selection = @stream_select($r, $w = array($sock), $e = array($sock), 0, $timeout); if ( !$selection ) { return false; } $remote = stream_socket_accept($r[0]); $return = new Socket_Stream(); $return->sock = $remote; $return->socket_initted = true; return $return; } function soft_shutdown() { fclose($this->sock); } function set_timeout($timeout, $usec = false) { return ( $usec ) ? @stream_set_timeout($this->sock, 0, $usec) : @stream_set_timeout($this->sock, $timeout); } function read_normal($length = 1024) { return @fgets($this->sock, $length); } function read_binary($length = 1024) { return @fread($this->sock, $length); } function timed_out() { $md = @stream_get_meta_data($this->sock); return ( isset($md['timed_out']) ) ? $md['timed_out'] : false; } function get_peer_info(&$addr, &$port) { $peer = stream_socket_get_name($this->sock, true); $addr = substr($peer, 0, strrpos($peer, ':')); $port = substr($peer, strrpos($peer, ':') + 1); } function write($data) { $data = str_split($data, 8096); foreach ( $data as $chunk ) { while ( !@fwrite($this->sock, $chunk) ) { usleep(50000); } } return true; } function is_eof() { return feof($this->sock); }}/** * Exception class that allows breaking directly out of a scriptlet. */class HttpExceptionFatal extends Exception{}/** * Exception class that will be treated as a scriptlet ending with success. */class HttpSuccess extends Exception{}/** * Array of known HTTP status/error codes */$http_responses = array( 200 => 'OK', 302 => 'Found', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 500 => 'Internal Server Error', 501 => 'Not Implemented' );/** * Array of default extension->mimetype mappings */$mime_types = array( 'html' => 'text/html', 'htm' => 'text/html', 'png' => 'image/png', 'gif' => 'image/gif', 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'js' => 'text/javascript', 'json' => 'text/x-javascript-json', 'css' => 'text/css', 'php' => 'application/x-httpd-php' );