Location: PHPKode > scripts > bib_server > bib_server/_class_bib_server.inc.php
<?php

if(!isset($_cfg['server']['port']))						$_cfg['server']['port']						= '6677';
if(!isset($_cfg['server']['listen_address']))			$_cfg['server']['listen_address']			= '192.168.1.6';
if(!isset($_cfg['server']['basedir']))					$_cfg['server']['basedir']					= '';
if(!isset($_cfg['server']['max_clients']))				$_cfg['server']['max_clients']				= '100';
//if(!isset($_cfg['server']['keep_alive']))				$_cfg['server']['keep_alive']				= true;
//if(!isset($_cfg['server']['keep_alive_timeout']))		$_cfg['server']['keep_alive_timeout']		= '15';
if(!isset($_cfg['server']['timeout']))					$_cfg['server']['timeout']					= '100';
if(!isset($_cfg['server']['sysname']))					$_cfg['server']['sysname']					= 'bibivu';
if(!isset($_cfg['server']['listen_queue']))				$_cfg['server']['listen_queue']				= '30';

if(!isset($_cfg['server']['httpd_user']))				$_cfg['server']['httpd_user']				= 'bibivu';
if(!isset($_cfg['server']['httpd_pid']))				$_cfg['server']['httpd_pid']				= '/var/run/bibivu/bibivu-httpd.pid';

if(!isset($_cfg['server']['daemon']))					$_cfg['server']['daemon']					= '0';
if(!isset($_cfg['server']['logfile']))					$_cfg['server']['logfile']					= ini_get('error_log');
if(!isset($_cfg['server']['function_log']))				$_cfg['server']['function_log']				= '';

if(!isset($_cfg['server']['verbose']))					$_cfg['server']['verbose']					= false;


if(phpversion()>=5){
	require_once realpath(dirname(__FILE__).'/php5x/'.basename(__FILE__));
}else{
	require_once realpath(dirname(__FILE__).'/php4x/'.basename(__FILE__));
}

class _bib_server extends _bib_server_vars{
	function _bib_server($_cfg){
		$this->_cfg['basedir']					= $_cfg['server']['basedir'];
		$this->_cfg['port']						= $_cfg['server']['port'];
		$this->_cfg['listen_address']			= $_cfg['server']['listen_address'];
		$this->_cfg['max_clients']				= $_cfg['server']['max_clients']>2?$_cfg['server']['max_clients']:3;
//		$this->_cfg['keep_alive']				= $_cfg['server']['keep_alive'];
//		$this->_cfg['keep_alive_timeout']		= $_cfg['server']['keep_alive_timeout'];
		$this->_cfg['timeout']					= $_cfg['server']['timeout'];
		$this->_cfg['sysname']					= $_cfg['server']['sysname'];
		$this->_cfg['httpd_user']				= $_cfg['server']['httpd_user'];
		$this->_cfg['httpd_pid']				= $_cfg['server']['httpd_pid'];
		$this->_cfg['daemon']					= $_cfg['server']['daemon'];
		$this->_cfg['function_log']				= $_cfg['server']['function_log'];
		$this->_cfg['listen_queue']				= $_cfg['server']['listen_queue'];
		$this->_cfg['logfile']					= $_cfg['server']['logfile'];
		$this->_cfg['verbose']					= $_cfg['server']['verbose'];
		
		$error = array();
		if ((int)substr(preg_replace("/[^\d]*/", '', phpversion()), 0, 3) < 410)
			$error[] = "PHP Error: You must be running PHP version 4.1 or higher.\n";
		if (!extension_loaded('posix'))
			$error[] = "PHP Error: The POSIX module is required (configure PHP '--enable-posix')\n";
		if (!extension_loaded('pcntl'))
			$error[] = "PHP Error: The Process Control module is required (configure PHP '--enable-pcntl')\n";
		if (!extension_loaded('sockets'))
			$error[] = "PHP Error: The Sockets module is required (configure PHP '--enable-sockets')\n";
		if (!empty($error)) {
			$this->write_log(2,0,'bibivu-httpd',implode("\n", $error),0,1);
			exit;
		}

		$this->_init();
	}
	
	function write_log($level, $id, $extra, $log_msg, $remote_ip) {
		if (!$this->_cfg['daemon']) {
			$msg = '('.$extra.') '.$log_msg."\n";
			if (php_sapi_name() == 'cli') {
				fwrite(STDERR,$msg);
			} else {
				echo $msg;
			}
		}
	
		// file system
		if ($this->_cfg['logfile'] != '') {
			// open file as r/w & point at the end of the file
			$logfile=fopen($this->_cfg['logfile'], 'a+'); 
			fwrite($logfile,$level.'|'.$id.'|'.$extra.'|'.$log_msg.'|'.$remote_ip.'|'.date('YmdHis')."\n");
			fclose ($logfile);
		}

		if($this->_cfg['function_log']!='' && is_callable($this->_cfg['function_log'])){
			call_user_func_array($this->_cfg['function_log'],func_get_args());
		}
	}

	function _init(){
		/* Run from root only */
		if (posix_getuid() != 0) {
			$this->write_log(1,0,'bibivu-httpd',"This program must be started as root (uid 0).\n",0);
			exit();
		}
		/* check if the user that we need to be Exist, and get the info*/
		if (!($this->_cfg['user'] = posix_getpwnam($this->_cfg['httpd_user'])) || !($group = posix_getgrgid($this->_cfg['user']['gid']))) {
			$this->write_log(0,0,'bibivu-httpd','User '.$this->_cfg['httpd_user'].' does not exist.',0);
			exit();
		}
		//starting the socket
		$this->start_server();
		/* change to a user with lower permissions than root. We should do this as soon as possible. */
//		pcntl_setpriority(-20);
		posix_setgid($this->_cfg['user']['gid']);
		posix_setuid($this->_cfg['user']['uid']);
		
		/* define max length to use when reading from sockets/files */
		$this->MAX_LENGTH = 2*4096;
		
		/* php 4.3 pcntl doesn't work without ticks declaration */
		declare(ticks=1);
		
		/* which functions should we run when we catch a posix signal
		 * stop_server() before shutdown, or run reload_server() on SIGHUP */
		pcntl_signal(SIGHUP, array($this,'reload_server'));	// sig# 1
		pcntl_signal(SIGINT, array($this,'stop_server'));	// sig# 2
		pcntl_signal(SIGTERM, array($this,'stop_server'));	// sig# 15

		/* increase our memory limit to 16 megabytes (standard is 8M) */
		ini_set('memory_limit', '16M');
		
		/* CLI automatically sets time limit to unlimited, but we need this for CGI */
		set_time_limit(0);

		/* fork the script into a background process */
		if ($this->_cfg['daemon'] === 1) {
			$pid = pcntl_fork();
			if ($pid == -1) {
				$this->write_log(0,0,'bibivu-httpd','Failed to spawn a daemon',0,1);
				exit(0);
			} elseif ($pid) {
				/* kill parent and return success */
				$this->write_log(0,0,'bibivu-httpd','Daemon started, killing the Main process',0,1);
				exit(0);
			}
			/* become session leader / detach from the controlling terminal */
			if (!posix_setsid()) {
				$this->write_log(0,0,'bibivu-httpd',"Could not detach from terminal",0,1);
				exit(1);
			} else
				$this->write_log(2,0,'bibivu-httpd',"bibivu-httpd daemon has been started.",0,1);
		}
		$this->set_pid_file(getmypid());
		$this->http_status_map=array(
				'ok'			=> '200 OK',
				'redirect'		=> '302 Found',
				'forbidden'		=> '403 Forbidden',
				'not found'		=> '404 Not Found',
				'default'		=> '400 Bad Request',
				);
		/* set some initial status variables */
		$this->httpd_status['started']		= $this->get_microtime();
		$this->httpd_status['hits']			= 0;
		$this->httpd_status['connections']	= 0;
		$this->httpd_status['sent']			= 0;
		$this->httpd_status['child']		= 0;
		/* initialize the client socket array */
//		$this->csocket = array();
		$this->cdata = array();
	}
	
	function doit(){
		//this function needs to check for a connection via socket
		//and when receive a new connection, fork the script and load the required file
		while(true){
			//check for any incoming comincations

			//close any old child connections

			if (isset($this->csocket) && is_resource($this->csocket)) {
				//there is a connection running
				if (($this->get_microtime() - $this->cdata['established']) > $this->_cfg['timeout']) {
					//timeout
					$this->close_client();
					exit(0); //exit the child
				}
			}

			$set_r = array($this->lsocket);
			$ready = @socket_select($set_r, $set_w = NULL, $set_e = NULL, $to_sec = 1);
//			if ($ready == 0 || $ready === false) {
				/* there are zero updates on the listening socket, or we caught a posix signal */
//				continue;
//			}
			/* if there is an update on the listening socket create a new connection */
			if (($ready != 0 && $ready !== false) && in_array($this->lsocket, $set_r)) {
//			if (in_array($this->lsocket, $set_r)) {
				//now I fork
				$this->httpd_status['connections']++;
				$this->httpd_status['child']++;
				if($this->_cfg['verbose'])
					$this->write_log(0,0,'bibivu-httpd','Fork child: '.$this->httpd_status['child'].'/'.$this->_cfg['max_clients'],0,1);
				$pid = pcntl_fork();
				if ($pid == -1) {
					$this->write_log(0,0,'bibivu-httpd','Failed to spawn a daemon',0,1);
					exit(1);
				} elseif ($pid) {
					//wait for a new connection
					$ret = 0;
					if($this->httpd_status['child']>$this->_cfg['max_clients']){
						$ret = pcntl_wait($status, WUNTRACED);
						$this->httpd_status['child']--;
					}
					usleep(10000);
				} else {
					$this->new_client();
					//exit the child
//					if($this->_cfg['verbose'])
//						$this->write_log(0,0,'bibivu-httpd','::Exiting: '.posix_getpid(),0,1);
					exit(0);
				}
			}
			$ret = pcntl_wait($status, WNOHANG);
			if($ret>0 && $this->_cfg['verbose']){
				$this->httpd_status['child']--;
				$this->write_log(0,0,'bibivu-httpd','::::back from: '.$ret.' - '.$this->httpd_status['child'].'/'.$this->_cfg['max_clients'],0,1);
			}
		}
	}
	function new_client(){
		set_time_limit(1);		//two seconds to open the connection
		if($this->open_client()){
			set_time_limit(5);
			$this->talk_client();
			$this->close_client();
		}
	}
	
	function talk_client(){
		/* read data from the client into a buffer. if read fails close the connection */
		$this->cdata['clength'] = 0;
		$this->cdata['hlength'] = 0;
		while($data = @socket_read($this->csocket, $this->MAX_LENGTH)) {
			$this->cdata['buffer'] .= $data;

			/* check for http header double carriage return / line feed */
			if (($this->cdata['hlength'] = strpos($this->cdata['buffer'], "\r\n\r\n")) !== false) {
				$this->cdata['hlength'] += 4;
				/* According to what I've read in the HTTP RFC's (rfc1945, rfc2068, rfc2616)
				 * all client requests with a body, i.e. POSTs, are REQUIRED to have the
				 * Content-Length header. This would also apply for HTTP 1.1, despite
				 * chunking. So I guess we'll just make sure all the data arrived here. */

				if ($this->cdata['clength'] = stristr($this->cdata['buffer'], 'Content-Length:')) {
					$this->cdata['clength'] = substr($this->cdata['clength'], 0, strpos($this->cdata['clength'], "\r\n"));
					$this->cdata['clength'] = explode(':', $this->cdata['clength']);
					$this->cdata['clength'] = trim($this->cdata['clength'][1]);
				}
				if (strlen($this->cdata['buffer']) >= ($this->cdata['hlength'] + $this->cdata['clength'])) {
					//received everything
					break;
				}
			} else {
				//header not received
//				return;
			}

	
			/* If the full body content hasn't arrived yet, skip the next part. */


		}
		/* this is where we parse the request headers */
		$request = $this->parse_request($this->cdata['buffer']);

		/* request has been parsed, erase the buffer. */
		$this->cdata['buffer'] = '';
		unset($this->cdata['hlength']);
		unset($this->cdata['clength']);

		/* build response array based on the request array */
		$response = $this->build_response($request);
			
		/* then send our response */
		$this->send_response($response, $request);

		/* close the connection if we're done */
		
		//log which file was requested
		if($this->_cfg['verbose'])
			$this->write_log(3,0,$this->cdata['remote_ip'],'file requested: '.str_replace($this->_cfg['basedir'],'',$response['translated_file']).' at '.date('g:i:s a', $this->cdata['established']),0);
		if ($response['connection_close']) {
			$this->close_client();
			exit(0);
		}
	}
	function start_server() {
		/* open a socket to listen for connections on */
		/* create a listening socket */
		if (!$this->lsocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) {
			$this->write_log(0,0,'bibivu-httpd','Failed to create a listening socket because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
			exit();
		}
		
		/* able to reuse our listening socket */
		if (!socket_set_option($this->lsocket, SOL_SOCKET, SO_REUSEADDR, 1)) {
			$this->write_log(0,0,'bibivu-httpd','Failed to make listening address reusable because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
			exit();
		}
		/* setting other options */
		if (!socket_set_option($this->lsocket, SOL_SOCKET, SO_SNDTIMEO, array('sec'=>10, 'usec'=>100)) ||		//time before timeout
			!socket_set_option($this->lsocket, SOL_SOCKET, SO_SNDBUF, 100*1024)
			) {
			$this->write_log(0,0,'bibivu-httpd','Failed to set options for the socket: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
			exit();
		}
		
		/* make our socket non-blocking */
		if (!socket_set_block($this->lsocket)) {
			$this->write_log(0,0,'bibivu-httpd','Failed to set listening socket to blocking because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
			exit();
		}
		
		/* bind the listening socket to the specified address and port */
		if (!socket_bind($this->lsocket, $this->_cfg['listen_address'], $this->_cfg['port'])) {
			$this->write_log(0,0,'bibivu-httpd','Failed to bind to '.$this->_cfg['listen_address'].' on port '.$this->_cfg['port']." because: ".socket_strerror(socket_last_error($this->lsocket)),0,1);
			exit();
		}
		
		/* start listening on our socket */
		if (!@socket_listen($this->lsocket, $this->_cfg['listen_queue'])) {
			$this->write_log(0,0,'bibivu-httpd','Failed to start listening on socket because: '.socket_strerror(socket_last_error($this->lsocket)),0,1);
			exit();
		}
		
		$this->write_log(2,0,'bibivu-httpd',"bibivu-httpd daemon has been started.",0);
		return $this->lsocket;
	}
	function stop_server($signo='') {
		/* shutdown the listening socket and close all client sockets. */
		
		// shut down client sockets
		$this->close_client();
		
		// shut down listening socket
		@socket_shutdown($this->lsocket, 2);
		@socket_close($this->lsocket);
		
		if (file_exists($this->_cfg['httpd_pid']) && !unlink($this->_cfg['httpd_pid'])) {
			$this->write_log(1,0,'bibivu-httpd','Unable to remove '.$this->_cfg['httpd_pid'],0);
		}
		
		$this->write_log(2,0,'bibivu-httpd',"bibivu-httpd daemon has been stopped.",0);
		exit;
	}
	function reload_server($signo='') {
		/* on SIGHUP we reload the server configuration */
		/* reset configuration */
		
		//still to finish..
		//now doesn't wotk
		$this->_bib_server($GLOBALS['_cfg']);
		
		$this->write_log(2,0,'bibivu-httpd',"SIGHUP received, server was reloaded.",0);
	}
	function open_client() {
	/* open a connection between the server and a client machine.*/
	
		/* find an open slot and fill it with the new connection.*/
		if (!$this->csocket = @socket_accept($this->lsocket)) {
			$this->write_log(1,0,'bibivu-httpd','Client failed to connect because: '.socket_strerror(socket_last_error($this->lsocket)).'['.socket_last_error($this->lsocket).']',0);
			exit(1);
			return false;
		}
	
		/* get ip and port of the client and local server */
		if (!@socket_getpeername($this->csocket, $this->cdata['remote_ip'], $this->cdata['remote_port'])) {
			$this->close_client();
			exit(1);
			return false;
		}
		if (!@socket_getsockname($this->csocket, $this->cdata['local_ip'], $this->cdata['local_port'])) {
			$this->close_client();
			exit(1);
			return false;
		}
		/* assign initial client data */
		$this->cdata['established'] = $this->get_microtime();
		$this->cdata['buffer'] = '';

		if($this->_cfg['verbose'])
			$this->write_log(3,0,$this->cdata['remote_ip'],'connected to '.$this->cdata['local_ip'].' on port '.$this->cdata['local_port'].' at '.date('g:i:s a', $this->cdata['established']),0);
		return true;
	}

	function close_client() {
	/* close a connection between the server and a client machine. */	
		
		/* if still connected shutdown and close the socket */
//		if (isset($this->csocket) && is_resource($this->csocket)) {
			@socket_shutdown($this->csocket, 2);
			@socket_close($this->csocket);
//		} else {
//			$this->write_log(1,0,$this->cdata['remote_ip'],'Socket not found while attempting to close the connection',0);
//		}
	
		if (isset($this->csocket)) {
			unset($this->csocket);
		}
	
		/* clean out data associated with the socket */
		if (!empty($this->cdata)) {
			if($this->_cfg['verbose'])
				$this->write_log(3,0,$this->cdata['remote_ip'],'disconnected from '.$this->cdata['local_ip'].' on port '.$this->cdata['local_port'].' at '.date('g:i:s a', $this->get_microtime()),0);
			unset($this->cdata);
		}
		/*now I can exit the child*/
//		exit(0);
	}

	function parse_request(&$data) {
	/******************************************************************
	 * take the raw request data from a client and return something like:
	 *
	 * $request
	 * (
	 *	[method]		=> GET  
	 *	[protocol]		=> HTTP/1.1
	 *	[file]			=> test.php
	 *	[connection_close] 	=> false
	 *	[get]
	 *	(
	 *		[key1]	=> val1
	 *		[key2]	=> val2
	 *	)
	 * )
	 *******************************************************************/
		
	
		$data = explode("\r\n\r\n", $data, 2);
		
		$header = explode("\r\n", $data[0]);
		/* if using ssl grab the custom SSL_Remote_IP: header from stunnel.
		   To make sure it isn't faked by the client we only accept the first instance
		   of this header, directly on 'top'.
		*/
//		if ($this->_cfg['ssl'])
//			$request['remote_ip'] = trim(array_shift($header));
	
		$request_line = explode(' ', $header[0]);
		unset($header[0]);
		
		/* make sure it's a valid HTTP request */
		if (!isset($request_line[2]) || strpos($request_line[2], "HTTP/") === false) {
			return false;
		}
		
		$request['method'] = $request_line[0];
		$request['protocol'] = $request_line[2];
	
		$request['cookie'] = array();
		$request['connection_close'] = false;
	
		/* Here we're going through the headers one by one
		 * looking for the few that we actually care about */
		foreach ($header as $val) {
			if (stristr($val, 'User-Agent:')) {
				/* pass user-agent string on to use
				 * with the $_SERVER variable */
				$val = substr($val, 11);
				$request['user_agent'] = trim($val);
			} elseif (stristr($val, 'Accept-Language:')) {
				/* pass accept-language string on to
				 * use with the $_SERVER variable */
				$val = substr($val, 16);
				$request['language'] = trim($val);
			} elseif (stristr($val, 'Cookie:')) {
				/* parse any cookies */
				$val = substr($val, 7);
				$request['cookie'] = $this->parse_query(trim($val), '; ');
				continue;
			} elseif (stristr($val, 'Connection: close')) {
				/* see if they want us to close the connection */
				$request['connection_close'] = true;
				continue;
			} elseif (stristr($val, 'Content-Type:')) {
				/* get content-type */
				$val = substr($val, 13);
				$request['content_type'] = trim($val);
				continue;
			}
		}
		
		/* save requested URI for $_SERVER */
		$request['uri'] = $request_line[1];
	
		/* split any GET querries from the requested path */
		$path_query = explode('?', $request_line[1]);
		$request['file'] = $path_query[0];
	
		/* save query string for $_SERVER */
		$request['query_string'] = '';
		if (isset($path_query[1])) {
			$request['query_string'] = $path_query[1];
		}
	
		// initialize file, post, and get data arrays
		$request['get'] = array();
		$request['post'] = array();
		$request['files'] = array();
	
		/* determine if there are POST or GET queries, and parse them */
		if (isset($path_query[1]) && strpos($path_query[1], '=') !== false) {
			$request['get'] = $this->parse_query($path_query[1]);
		}
	
		// check for included request body
		if (isset($data[1]) && isset($request['content_type'])) {
			if (strpos($request['content_type'], 'application/x-www-form-urlencoded') !== false) {
				// normal form
				$request['post'] = $this->parse_query($data[1]);
			} elseif (strpos($request['content_type'], 'multipart/form-data') !== false) {
				// rfc 2388 covers multipart/form-data
				$boundary = explode(';', $request['content_type']);
				$boundary = explode('=', $boundary[1]);
				$boundary = trim($boundary[1]);
	 
				$this->parse_multi_part($data[1], $boundary, $request['post'], $request['files']);
			}
		}
	
		return $request;
	}
	function build_response($request) {
	/******************************************************************
	 * build the array that we will base our response on:
	 *
	 * $response
	 * (
	 *	[status]		=> ok  
	 *	[protocol]		=> HTTP/1.1
	 *	[translated_file]	=> /home/bibivu/web/index.php
	 *	[file_type]		=> php
	 *	[connection_close]	=> false
	 * )
	 *******************************************************************/
	
		$response['connection_close'] = true;

/*	
		$response['connection_close'] = false;
		// the http 1.1 protocol uses keep alive connections. We should
		// close a socket only when it has timed out, or one of the
		// following conditions is met. 
		if (!$request) {
			// we received a request we don't understand
			$response['connection_close'] = true;
		} elseif ($request['protocol'] !== 'HTTP/1.1') {
			// they are using a http 1.0 or unknown protocol
			$response['connection_close'] = true;
		} elseif ($request['connection_close']) {
			// they asked us to close it with a Connection: close header
			$response['connection_close'] = true;
		} elseif (!$this->_cfg['keep_alive']) {
			// keep alive is turned off in the configuration file
			$response['connection_close'] = true;
		}
*/
	
		/* if the request we got is invalid respond as such */
		if (!$request) {
			$response['protocol'] = 'HTTP/1.1';
			$response['status'] = 'malformed';
			return $response;
		}
	
		if ($request['protocol'] == 'HTTP/1.1') {
			/* If they're using HTTP version 1.1 then respond with 1.1 */
			$response['protocol'] = 'HTTP/1.1';
		} else {
			/* If they're using any other version drop to 1.0 compatibility */
			$response['protocol'] = 'HTTP/1.0';
		}
	
		/* extremely basic security check, remove any ../ from requested file
		 * so they can't ascend into forbidden directories */
		$response['translated_file'] = str_replace('../', '', $request['file']);
	
		/* remove leading/trailing slash(es) */
		$response['translated_file'] = trim($response['translated_file'], '/');
	
		/* append default path from config file */
		$response['translated_file'] = $this->_cfg['basedir'].$response['translated_file'];
	
		/* load index.php for directories */
		if (is_readable($response['translated_file']) && is_dir($response['translated_file'])) {
			$response['translated_file'] = rtrim($response['translated_file'], '/').'/index.php';
		}
	
		/* Get a file's extension by grabbing the characters
		 * from after the last period to the last character. */
		$response['file_type'] = substr($response['translated_file'], (strrpos($response['translated_file'], '.') + 1));
	
		/* we're working with a lot of files that may change */
		clearstatcache();
		
		/* see if requested file exists and can be served */
		if (basename($response['translated_file']) == '--status') {
			$response['status'] = 'status';
		} elseif (!file_exists($response['translated_file'])) {
			$response['status'] = 'not found';
		} elseif (!is_readable($response['translated_file'])) {
			$response['status'] = 'forbidden';
		} elseif (!is_dir($response['translated_file'])) {
			$response['status'] = 'ok';
	
			/* change to file's working directory */
			chdir(dirname($response['translated_file']));
		}
	
		return $response;
	}
	function build_headers($response) {
	/* compile an appropriate list of headers to send to client */
		
		/* Status line */
		if (!in_array($response['status'], array_keys($this->http_status_map))) {
			$response['status']='default';
		}
		$headers = $response['protocol'].' '.$this->http_status_map[$response['status']]."\r\n";
	
		/* Date line, Sat, 06 Sep 2014 23:50:08 GMT*/
		$headers .= 'Date: '.gmdate("D, d M Y H:i:s T")."\r\n";
	
		/* Server line */
		$headers .= "Server: bibivu-httpd - web://cp \r\n";
		$headers .= 'X-Powered-By: PHP/'.phpversion()."\r\n";
	
		/* Add content length */
		$headers .= 'Content-length: '.$response['content_length']."\r\n";
	
		/* Connection close header */
		if ($response['connection_close'] && $response['protocol'] == 'HTTP/1.1') {
			$headers .= "Connection: close\r\n";
		}
	
		/* MIME type */
		if ($response['status'] !== 'ok') {
			$headers .= "Content-type: text/html\r\n";
		} else {
			$headers .= 'Content-type: '.$this->mime_type($response['file_type'])."\r\n";
		}
	
		$headers .= "\r\n";
	
		return $headers;
	}
	function send_response($response, $request) {
	/* handle the client's request */
	
		if ($response['status'] == 'ok' && $response['file_type'] == 'php') {
			/* if the requested resource is a php file. */
/*
			$pid = pcntl_fork();
			if ($pid == -1) {
				$this->write_log(0,0,$this->cdata['remote_ip'],'Failed to spawn a daemon',0,1);
				exit;
			} elseif ($pid) {
				exit;
			}
*/
			$content = $this->load_page($this->cdata, $response, $request);
	
			// add up the content-length
			$response['content_length'] = intval(strlen($content));
			$headers = $this->build_headers($response);

			if (@socket_write($this->csocket, $headers.$content) === false) {
				$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
				$this->close_client();
				exit();
			}
//			echo socket_strerror(socket_last_error($this->csocket))."\n";
		} elseif ($response['status'] == 'ok') {
			/* if the requested resource is a normal file and can be returned */
	
			$response['content_length'] = filesize($response['translated_file']);
//			$response['content_length'] = strlen($response['translated_file']);
			$headers = $this->build_headers($response);
	
			if (@socket_write($this->csocket[$slot], $headers) === false) {
				$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
				$this->close_client();
				exit();
			} else {
				$this->socket_write_file($response['translated_file'], $this->cdata);
			}
		} elseif ($response['status'] == 'forbidden') {
			/* if the requested resource isn't readable */
	
			$content = "<html><head>\r\n";
			$content .= "<title>403 Forbidden</title>\r\n";
			$content .= "</head><body>\r\n";
			$content .= "<h1>Forbidden</h1>\r\n";
			$content .= "<p>You do not have permission to access $request[file].</p>\r\n";
			$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\r\n";
			$content .= "</body></html>";
	
			$response['content_length'] = strlen($content);
			$headers = $this->build_headers($response);
	
			if (@socket_write($this->csocket, $headers.$content) === false) {
				$this->write_log(0,0,$this->cdata['remote_ip'],"Failed to send because: ".socket_strerror(socket_last_error($this->csocket[$slot])),0);
				$this->close_client();
				exit();
			}
		} elseif ($response['status'] == 'not found') {
			/* if the requested resource doesn't exist */
	
			$content = "<html><head>\r\n";
			$content .= "<title>404 Not Found</title>\r\n";
			$content .= "</head><body>\r\n";
			$content .= "<h1>Not Found</h1>\r\n";
			$content .= "<p>The requested URL $request[file] was not found.</p>\r\n";
			$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\r\n";
			$content .= "</body></html>";
	
			$response['content_length'] = strlen($content);
			$headers = $this->build_headers($response);
	
			if (@socket_write($this->csocket, $headers.$content) === false) {
				$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
				$this->close_client();
				exit();
			}
		} elseif ($response['status'] == 'status') {
			/* they requested server status */
	
			/* convert uptime into days, hours, minutes, and seconds */
			$uptime = $this->get_microtime() - $this->httpd_status['started'];
			$uptime_str = '';
			if (($days = floor($uptime / 86400)) > 0) $uptime_str .= "$days days";
			if (($hours = floor(($uptime % 86400) / (3600))) > 0) $uptime_str .= " $hours hours";
			if (($mins = floor((($uptime % 86400) % 3600) / 60)) > 0) $uptime_str .= " $mins min";
			if (($secs = floor((($uptime % 86400) % 3600) % 60)) > 0) $uptime_str .= " $secs sec";
	
			/* convert bytes sent into terra, giga, mega, kilo, or bytes */
			$sent_str = ($this->httpd_status['sent']);
			
			// Count number of code lines in web://cp
/*
			$numlines = 0;
			$dir = $this->dirlist($this->_cfg['basedir'].'/server', 'file');
			do {
				if (strstr(current($dir),'.php') && !strstr(current($dir),'phpmyadmin') && !strstr(current($dir),'phppgadmin') && !strstr(current($dir),'errors'))
					$numlines += count(file(current($dir)));
			} while (next($dir));
			$dir = $this->dirlist($this->_cfg['basedir'].'/web', 'file');
			do {
				if (strstr(current($dir),'.php') && !strstr(current($dir),'phpmyadmin') && !strstr(current($dir),'phppgadmin') && !strstr(current($dir),'errors'))
					$numlines += count(file(current($dir)));
			} while (next($dir));
*/			
			
			$content = "<html><head>\n";
			$content .= "<title>web://bibivu status</title>\n";
			$content .= "</head><body>\n";
			$content .= "<h1>web://bibivu status</h1>\n";
//			$content .= "<b>Started:</b> ".date('r', $this->httpd_status['started'])."<br />\n";
			$content .= "<b>Uptime:</b> $uptime_str<br />\n";
			$content .= "<b>on IP:</b> ".$this->_cfg['listen_address'].' : '.$this->_cfg['port']."<br />\n";
//			$content .= "<b>Data Served:</b> $sent_str<br />\n";
//			$content .= "<b>Hits:</b> ".$this->httpd_status['hits']."<br />\n";
			$content .= "<b>Connections:</b> ".$this->httpd_status['connections']."<br />\n";
			$content .= "<b>Childrens:</b> ".$this->httpd_status['child'].'/'.$this->_cfg['max_clients']."<br />\n";
			$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\n";
			$content .= "</body></html>";
	
			$response['content_length'] = strlen($content);
			$headers = $this->build_headers($response);
	
			if (@socket_write($this->csocket, $headers.$content) === false) {
				$this->write_log(1,0,$this->cdata['remote_ip'],'Failed to send because: '.socket_strerror(socket_last_error($this->csocket)),0);
				$this->close_client();
				exit();
			}
		} else {
			/* if we had an invalid request */
			$content = "<html><head>\r\n";
			$content .= "<title>400 Bad Request</title>\r\n";
			$content .= "</head><body>\r\n";
			$content .= "<h1>Bad Request</h1>\r\n";
			$content .= "<p>You sent a malformed header. Goodbye.</p>\r\n";
			$content .= "<p><i>".$this->_cfg['sysname']."</i></p>\r\n";
			$content .= "</body></html>";
	
			$response['content_length'] = strlen($content);
			$headers = $this->build_headers($response);
			
			if (@socket_write($this->csocket, $headers.$content) === false) {
				$this->write_log(0,0,$this->cdata['remote_ip'],'Failed to send to because: '.socket_strerror(socket_last_error($this->csocket)),0);
				$this->close_client();
				exit();
			}
		}
	
		/* update internal statistics */
		$this->httpd_status['hits']++;
		$this->httpd_status['sent'] += strlen($headers) + $response['content_length'];
	
		// log this request:
//		$this->log_http_request($this->cdata, $response, $request);
	}
	function load_page($client, $response, $request) {
	/* Interpret a seperate php file and return it in a variable. */
	
		/* copy globals, work around for not being able to use $GLOBALS directly due to recursion */
		$_SERVER = array();
		$_REQUEST = array();
	
		/* pass HTTP_SERVER_VARS array to subscript */
		$_SERVER['HTTP_HOST']				= $this->_cfg['sysname'];
		$_SERVER['HTTP_USER_AGENT']			= (isset($request['user_agent']) ? $request['user_agent'] : '');
		$_SERVER['HTTP_ACCEPT_LANGUAGE']	= (isset($request['language']) ? $request['language'] : '');
		$_SERVER['SERVER_SOFTWARE']			= 'bibivu-httpd php/'.phpversion();
		$_SERVER['BIBIVU-HTTPD']			= true;
		$_SERVER['SERVER_NAME']				= $_SERVER['HTTP_HOST'];
		$_SERVER['SERVER_ADDR']				= $client['local_ip'];
		$_SERVER['SERVER_PORT']				= $this->_cfg['port'];
		$_SERVER['REMOTE_ADDR']				= $client['remote_ip'];
		$_SERVER['REMOTE_PORT']				= $client['remote_port'];
		$_SERVER['SERVER_PROTOCOL']			= $response['protocol'];
		$_SERVER['DOCUMENT_ROOT']			= $this->_cfg['basedir'];
		$_SERVER['SCRIPT_FILENAME']			= $response['translated_file'];
		$_SERVER['REQUEST_METHOD']			= $request['method'];
		$_SERVER['QUERY_STRING']			= $request['query_string']; 
		$_SERVER['REQUEST_URI']				= $request['uri'];
		$_SERVER['SCRIPT_NAME']				= substr($response['translated_file'], strlen($this->_cfg['basedir'])-1);	
		$_SERVER['PHP_SELF']				= $_SERVER['SCRIPT_NAME'];
//		$HTTP_SERVER_VARS = $_SERVER;

		/* pass cookie array to subscript */
		$_COOKIE = $request['cookie'];
//		$HTTP_COOKIE_VARS = $_COOKIE;
		$_REQUEST = array_merge($_REQUEST, $_COOKIE);
	
		/* pass post array to subscript */
		$_POST = $request['post'];
//		$HTTP_POST_VARS = $_POST;
		$_REQUEST = array_merge($_REQUEST, $_POST);
	
		/* pass get array to subscript */
		$_GET = $request['get'];
//		$HTTP_GET_VARS = $_GET;
		$_REQUEST = array_merge($_REQUEST, $_GET);
	
		/* pass HTTP_ENV_VARS array to subscript */
//		$HTTP_ENV_VARS = $_ENV;
	
		/* pass files array to subscript */
		$_FILES = $request['files'];
//		$HTTP_POST_FILES = $_FILES;
	
		/* set error reporting to the php.ini default */
		restore_error_handler();
		$default_error_level = get_cfg_var('error_reporting');
		$error_level = error_reporting($default_error_level);
	
		/* evaluate the requested script and cache it to a variable. */
		if($this->_cfg['verbose'])
			$this->write_log(3,0,$this->cdata['remote_ip'],'PHP PAGE:'.$response['translated_file'],0);
		ob_start();
		$result = include $response['translated_file'];
		$page = ob_get_contents();
		while(ob_get_level()>0){
			ob_end_clean();
		}
		
		/* restore error reporting */
		set_error_handler(array($this,'error_handler'));
		error_reporting($error_level);
	
		/* See if there is anything returned from running the script. */
		if (strlen($result) > 1) {
			/* if so, print it out */
			$page .= $result;
		}
	
		/* remove temporary files that were uploaded by client */
		foreach($_FILES as $val) {
			/* make sure that this script can still access the file */
			if (is_writable($val['tmp_name'])) {
				unlink($val['tmp_name']);
			}
		}
	
		/* return header info by reference */

//		$header_redirect = (isset($GLOBALS['header_redirect']) ? $GLOBALS['header_redirect'] : false);
//		$send_headers = (isset($GLOBALS['send_headers']) ? $GLOBALS['send_headers'] : false);

		return $page;
	}
	function socket_write_file($file, $client) {
	/* send a file line by line. */
	
		$fp = fopen($file, 'rb');
		while(!feof($fp)) {
			if (@socket_write($this->csocket, fread($fp, $this->MAX_LENGTH)) === false) {
				$this->write_log(0,0,$this->cdata['remote_ip'],'Failed to send to '.$client['remote_ip'].' because: '.socket_strerror(socket_last_error($this->csocket)),0);
				$this->close_client();
				exit();
			}
		}
		fclose($fp);
	}
	function mime_type($file_type) {
	/* return the correct mime type based on the file type. */
	
		switch($file_type) {
			case 'php':
			case 'html':
				return 'text/html';
			case 'jpg':
			case 'jpeg':
				return 'image/jpeg';
			case 'png':
				return 'image/png';
			case 'gif':
				return 'image/gif';
			default:
				return 'text/plain';
		}
	}
	function parse_query($query_line, $delimiter='&') {
	/* take a query line from a POST, GET or COOKIE and return an
	 * array holding each query name and value */
	
		if ($delimiter !== '&') {
			$query_line = str_replace($delimiter, '&', $query_line);
		}
		parse_str(trim($query_line), $result);
	
		return $result;
	}
	function parse_multi_part(&$body, $boundary, &$post, &$files) {
	/* This function will parse posted multipart form data
	 * into temporary files or post queries which will be
	 * passed back by reference */
	
		// split the multiple parts into an array
		$body = preg_split("/(\r\n)?--$boundary(--)?(\r\n)?/", $body, -1, PREG_SPLIT_NO_EMPTY);
		// cycle through the parts and parse each one
		foreach($body as $part) {
	
			$part = explode("\r\n\r\n", $part);
			if (count($part) < 2) {
				$part[1] = '';
			}
	
			// separate the part's headers into an array
			$part[0] = explode("\r\n", $part[0]);
			foreach($part[0] as $val) {
					$arr = explode(':', $val, 2);
					if (count($arr) > 1) {
						$mime_header[strtolower($arr[0])] = trim($arr[1]);
					}
			}
	
			// separate all attributes of the content disposition into an array
			if (isset($mime_header['content-disposition'])) {
				$attribute = explode(';', $mime_header['content-disposition']);
	
				foreach ($attribute as $val) {
					$arr = explode('=', trim($val));
					if (count($arr) > 1) {
						$cont_disp[strtolower($arr[0])] = trim($arr[1], '"');
					}
				}
			}
	
			// check and see if this part is a file
			if (!empty($cont_disp['filename'])) {
				// generate a temporary name and get the size
				$tmp_name = '/tmp/php'.uniqid(getmypid());
				$size = strlen($part[1]);
	
				// add it and it's associated data to the files array that we're going to need
				$files[$cont_disp['name']]['name'] = $cont_disp['filename'];
				$files[$cont_disp['name']]['tmp_name'] = $tmp_name;
				$files[$cont_disp['name']]['size'] = $size;
				if (isset($mime_header['content-type'])) {
					$files[$cont_disp['name']]['type'] = $mime_header['content-type'];
				}
	
				// save the uploaded file to the tmp directory		
				// we will delete the file when execution of this
				// request is finished.
				if ($fp = fopen($tmp_name, 'w')) {
					fwrite($fp, $part[1], $size);
					fclose($fp);
				}
			// if it's not a file it must be a posted query
//			} elseif (!empty($part[1])) {
			} else{
				if (isset($cont_disp['name'])) {
					$post[$cont_disp['name']] = $part[1];
				}
			}
		}
	}
	function parse_multi_part_old(&$body, $boundary, &$post, &$files) {
	/* This function will parse posted multipart form data
	 * into temporary files or post queries which will be
	 * passed back by reference */
	
		// split the multiple parts into an array
		$body = preg_split('/(\r\n)?--'.$boundary.'(--)?(\r\n)?/', $body, -1, PREG_SPLIT_NO_EMPTY);
//		$this->write_log(2,0,'bibivu-httpd',$body."\n".'--'.$boundary,0,1);
//		$body = explode('--'.$boundary, $body);
		// cycle through the parts and parse each one
		foreach($body as $part) {
			$part = ltrim($part);
	
			$part = explode("\r\n\r\n", $part);
			if (count($part) < 2) {
				if($part[0]==0){
					//nothing to do here
					continue;
				}
				$part[1] = '';
			} else {
				$part[1] = substr($part[1],0,-2);
			}
	
			// separate the part's headers into an array
			$part[0] = explode("\r\n", $part[0]);
			foreach($part[0] as $val) {
					$arr = explode(':', $val, 2);
					if (count($arr) > 1) {
						$mime_header[strtolower($arr[0])] = trim($arr[1]);
					}
			}
	
			// separate all attributes of the content disposition into an array
			if (isset($mime_header['content-disposition'])) {
				$attribute = explode('; ', $mime_header['content-disposition']);
	
				foreach ($attribute as $val) {
					$arr = explode('=', trim($val));
					if (count($arr) > 1) {
						$cont_disp[strtolower($arr[0])] = trim($arr[1], '"');
					}
				}
			}
			// check and see if this part is a file
			if (!empty($cont_disp['filename'])) {
				// generate a temporary name and get the size
				$tmp_name = '/tmp/php'.uniqid(getmypid());
				$size = strlen($part[1]);
	
				// add it and it's associated data to the files array that we're going to need
				$files[$cont_disp['name']]['name'] = $cont_disp['filename'];
				$files[$cont_disp['name']]['tmp_name'] = $tmp_name;
				$files[$cont_disp['name']]['size'] = $size;
				if (isset($mime_header['content-type'])) {
					$files[$cont_disp['name']]['type'] = $mime_header['content-type'];
				}
	
				// save the uploaded file to the tmp directory		
				// we will delete the file when execution of this
				// request is finished.
				if ($fp = fopen($tmp_name, 'w')) {
					fwrite($fp, $part[1], $size);
					fclose($fp);
				}
			// if it's not a file it must be a posted query
			} elseif (!empty($part[1])) {
				//need to split the arrays
				if (isset($cont_disp['name'])){
					if(strpos($cont_disp['name'],'[')!==false){
						$start = strpos($cont_disp['name'],'[');
						$end = strpos($cont_disp['name'],']');
						$len = $end-$start;
						if(strpos($cont_disp['name'],'[]')!==false){
							$post[substr($cont_disp['name'],0,$start)][] = $part[1];
						} else {
							$index = substr($cont_disp['name'],$start,$len);
						}
					} else {
						//normal variable
						$post[$cont_disp['name']] = $part[1];
					}
				}
			}
		}
//		var_dump($post);
	}
	function set_pid_file($pid) {
	/* handle pid file details */
	
		/* check for an existing pid file */
		if (file_exists($this->_cfg['httpd_pid']) && !is_dir($this->_cfg['httpd_pid'])) {
			$fp = fopen($this->_cfg['httpd_pid'], 'r');
			$old_pid = fgets($fp, $this->MAX_LENGTH);
			fclose($fp);
	
			/* if there is a corresponding process, cancel execution. */
			if ($old_pid > 0 && posix_kill($old_pid, 0)) {
				$this->write_log(0,0,$this->cdata['remote_ip'],"Server already running with PID: $old_pid",0);
				exit;
			}
		}
	
		/* create a pid file */
		if ($fp = fopen($this->_cfg['httpd_pid'], 'w')) {
			fputs($fp, $pid);
			fclose($fp);
	
			/* make sure we can delete the pid file later */
			chown($this->_cfg['httpd_pid'], $this->_cfg['user']['uid']);
			chgrp($this->_cfg['httpd_pid'], $this->_cfg['user']['gid']);
			chmod($this->_cfg['httpd_pid'], 0644);
			
		} else {
			$this->write_log(0,0,$this->cdata['remote_ip'],'Unable to write to '.$this->_cfg['httpd_pid'],0);
			exit;
		}
	}
	function error_handler($errno, $errstr, $errfile, $errline) {
		// make sure there is no \ sign before the error line
		if (error_reporting()) {
			switch($errno) {
				// Fatal Errors
				case 1:
				case 256:
					$this->write_log(0,0,$this->cdata['remote_ip'],"Fatal Error ($errno) in file $errfile($errline): $errstr",0);
					break;
				// Warning & errors
				case 2:
				case 4:
				case 8:
				case 512:
				case 1024:
					$this->write_log(1,0,$this->cdata['remote_ip'],"Error ($errno) in file $errfile($errline): $errstr",0);
					break;
				// anything else
				default:
					$this->write_log(1,0,$this->cdata['remote_ip'],"Error unknown ($errno) in file $errfile($errline): $errstr",0);
			}
		}
	}
	function get_microtime() {
	/* used to calculate timeouts, also useful for testing */
		list($usec, $sec) = explode(' ', microtime());
		return ((float)$usec + (float)$sec);
	}
}
?>
Return current item: bib_server