<?php
class BaseProxy {
    protected 
$addr NULL;
    protected 
$port NULL;
    protected 
$debug false;
    protected 
$clients = array();
    protected 
$buffer = array();
    public 
$maxConnections 50;
    public 
$pid = -1;
    
    function 
__construct($addr='0.0.0.0',$port='8000') {
        
$this->debug defined('DEBUG') ? DEBUG false;
        
$this->log('__construct()');
        
set_time_limit(0);
        if (
defined('THREADING') && (THREADING == true)) {
            if (!
extension_loaded('pcntl')) {
                
$prefix = (PHP_SHLIB_SUFFIX === 'dll') ? 'php_' '';
                if (!@
dl($prefix 'pcntl.' PHP_SHLIB_SUFFIX)) $this->pid = -2;
            }
        }
        else {
            
$this->pid = -2;
        }
        
$this->addr $addr;
        
$this->port $port;
        
        try {
            
$this->socket socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
            if (
$this->socket === false$this->halt('Unable to create main socket');
            
socket_set_option($this->socket,SOL_SOCKET,SO_REUSEADDR,1);
            if (!
socket_set_nonblock($this->socket)) $this->log($this->lastError());
            if (!
socket_bind($this->socket,$this->addr,$this->port)) $this->halt($this->lastError());
            
socket_getsockname($this->socket,$this->addr,$this->port);
            
$this->log(sprintf('Starting HTTP Proxy on %s port %s',$this->addr,$this->port));
            if (!
socket_listen($this->socket)) $this->halt($this->lastError());
        }
        catch (
Exception $e) {
            
$this->halt($e->getMessage());
        }
        
$this->main();
    }
    
    function 
__destruct() {
        
$this->log('__destruct()');
        if (
$this->pid !== 0) {
            
socket_close($this->socket);
            foreach (
$this->clients as $s$this->destroy($s);
            
$this->clients = array();
            
$this->buffer = array();
        }
        
posix_kill(getmypid(),9);
    }
    
    public function 
lastError($s=NULL) {
        if (
$s === NULL$s =& $this->socket;
        return @
socket_strerror(socket_last_error($s));
    }
    
    public function 
main() {
        
$write NULL;
        while (
true) {
            if ((
count($this->clients) <= $this->maxConnections) && ($s = @socket_accept($this->socket))) {
                
socket_getpeername($s,$raddr,$rport); 
                
$this->log("Received Connection from $raddr:$rport");
                
$this->buffer[$s] = '';
                if (!
socket_set_block($s)) $this->log($this->lastError($s));
                if (!
socket_set_option($s,SOL_SOCKET,SO_RCVTIMEO,array('sec'=>10,'usec'=>0))) $this->log($this->lastError($s));
                if (!
socket_set_option($s,SOL_SOCKET,SO_SNDTIMEO,array('sec'=>10,'usec'=>0))) $this->log($this->lastError($s));
                
$this->clients[$s] = $s;
                
$this->log(sprintf('%s active connections',count($this->clients)));
            }
            
$read $except array_values($this->clients);
            if (@
socket_select($read,$write,$except,0)) {
                foreach (
$read as $s) {
                    
$this->read($s);
                }
                foreach (
$except as $s) {
                    
$this->log($this->lastError($s));
                    
$this->destroy($s);
                }
            }
            
usleep(100000);
        }
    }
    
    protected function 
destroy($s) {
        
socket_close($s);
        unset(
$this->clients[$s]);
        unset(
$this->buffer[$s]);
    }
    
    protected function 
read($s) {
        
$this->buffer[$s] .= socket_read($s,8192,PHP_BINARY_READ);
        if (
preg_match('#\r\n\r\n$#s',$this->buffer[$s])) {
            if (
trim($this->buffer[$s]) == 'QUIT') exit;
            unset(
$this->clients[$s]);
            if (
$this->pid != -2$this->pid pcntl_fork();
            if (
$this->pid <= 0$this->handle($s);
        }
    }
    
    protected function 
handle($s) {
        
$req preg_split('#\r\n#s',$this->buffer[$s],-1);
        unset(
$this->buffer[$s]);
        list(
$method,$url,$protocol) = sscanf(array_shift($req),'%s %s %s');
        
$this->log("handle() :: $method $url $protocol");
        switch (
$method) {
            case 
'CONNECT':
                
$this->do_CONNECT($s,$url,$protocol,$req);
                break;
            case 
'GET':
            case 
'POST':
            case 
'DELETE':
            case 
'PUT':
            case 
'HEAD':
                
$this->do_GET($s,$method,$url,$protocol,$req);
                break;
            default:
                
$this->do_ERROR($s,'Not Implemented',501,$protocol,$req);
                break;
        }
        
$this->log('handle() :: done');
        if (
$this->pid === 0) {
            
$this->log('Killing child process');
            
posix_kill(getmypid(),9);
            exit;
        }
    }
    
    protected function 
do_ERROR($s,$errmsg,$errcode,$protocol,$req) {
        
$this->log(sprintf('do_ERROR(%s,%s)',$errmsg,$errcode));
        
$html "<html><head><title>$errcode $errmsg</title></head><body><h1>$errmsg</h1></body></html>";
        
$length strlen($html);
        
socket_write($s,"Status: $errcode $errmsg\r\nContent-Type: text/html\r\nContent-Length: $length\r\n\r\n$html");
        
$this->destroy($s);
    }
    
    protected function 
do_CONNECT($s,$url,$protocol,$req) {
        
$this->log(sprintf('do_CONNECT(%s)',$url));
        list(
$host,$port) = explode(':',$url,2);
        
$d socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
        if ((
$d === false) || !@socket_connect($d,$host,$port)) {
            if (
$d$this->log($this->lastError($d));
            @
socket_close($d);
            
$this->do_ERROR($s,'Not Found',404,$protocol,$req);
        }
        else {
            
socket_write($s,"$protocol 200 Connection established\r\n\r\n");
            
$this->read_write($s,$d,30);
        }        
    }
    
    protected function 
do_GET($s,$method,$url,$protocol,$req) {
        
$this->log(sprintf('do_GET(%s,%s)',$method,$url));
        @
extract(parse_url($url));
        if ((
strtolower($scheme) != 'http') || $fragment || !$host || !$path) {
            
$this->do_ERROR($s,'Bad Request',400,$protocol,$req);
        }
        else {
            if (!
$port$port 80;
            
$d socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
            if ((
$d == false) || !@socket_connect($d,$host,$port)) {
                if (
$d$this->log($this->lastError($d));
                @
socket_close($d);
                
$this->do_ERROR($s,'Not Found',404,$protocol,$req);
            }
            else {
                
// todo : auth requests
                
if ($query$query "?$query";
                
socket_write($d,sprintf("%s %s %s\r\n",$method,$path.$query,$protocol));
                foreach (
$req as $h) {
                    if (empty(
$h)) socket_write($d,"Connection: close\r\n\r\n");
                    elseif (!
preg_match('#^(Proxy-Connection)|(Connection ?:)#si',$h)) socket_write($d,"$h\r\n");
                }
                
$this->read_write($s,$d);
            }
        }
    }
    
    protected function 
read_write($client,$server,$max_idling=20) {
        
$this->log('read_write()');
        if (!
socket_set_block($server)) $this->log($this->lastError($server));
        if (!
socket_set_option($server,SOL_SOCKET,SO_RCVTIMEO,array('sec'=>10,'usec'=>0))) $this->log($this->lastError($server));
        if (!
socket_set_option($server,SOL_SOCKET,SO_SNDTIMEO,array('sec'=>10,'usec'=>0))) $this->log($this->lastError($server));
        
$n 0;
        
$write NULL;
        while (
true) {
            
$n++;
            
$read $except = array($client,$server);
            if (
socket_select($read,$write,$except,3,0)) {
                foreach (
$read as $in) {
                    
$data = @socket_read($in,8192,PHP_BINARY_READ);
                    if (
$data === false) {
                        
$this->log($this->lastError($in));
                        break;
                    }
                    elseif (
$data) {
                        if (!@
socket_write($in == $client $server $client,$data)) {
                            
$this->log($this->lastError($in == $client $server $client));
                            break;
                        }
                        else 
$n 0;
                    }
                }
                if (
count($except)) {
                    foreach (
$except as $e$this->log($this->lastError($e));
                    break;
                }
            }
            if (
$n >= $max_idling) break;
        }
        
$this->destroy($client);
        
$this->destroy($server);
    }
    
    protected function 
log($msg) {
        if (
$this->debugprintf("%s %s [PID=%s/%s]\n",date('Y-m-d H:i:s'),$msg,getmypid(),$this->pid);
    }
    
    protected function 
halt($errmsg) {
        die(
"$errmsg\n");
    }
}

define('DEBUG',true);
define('THREADING',true);

$proxy $argc == ? new BaseProxy($argv[1],$argv[2]) : new BaseProxy;
?>