<?php

/*
   +-----------------+------------------------------------------------------------+
   |  Class          | PHProxy                                                    |
   |  Author         | ultimategamer00 (Abdullah A.)                              |
   |  Last Modified  | 7:09 AM 7/22/2004                                          |
   +-----------------+------------------------------------------------------------+
   |  This program is free software; you can redistribute it 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 more details.                                |
   |                                                                              |
   |  You should have received a copy of the GNU General Public License           |
   |  along with this program; if not, write to the Free Software                 |
   |  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. |
   +------------------------------------------------------------------------------+
*/


class PHProxy
{
    //
    // Configurable vars
    //

    var $allowed_hosts = array();
    var $banned_hosts = array();
    var $flags = array('include_form' => 1, 'remove_scripts' => 1, 'accept_cookies' => 1, 'show_images' => 1, 'show_referer' => 1);

    //
    // End Configurable vars
    //

    var $version;
    var $script_url;
    var $url;
    var $url_segments;

    var $socket;


    var $request_method;
    var $request_headers;
    var $basic_auth_header;
    var $basic_auth_realm;
    var $data_boundary;
    var $post_body;

    var $response_headers;
    var $response_code;
    var $content_type;
    var $response_body;


    function PHProxy($flags = 'previous')
    {
        $this->version = '0.3';
        $this->script_url = 'http' 
                          . (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '')
                          . "://{$_SERVER['HTTP_HOST']}{$_SERVER['PHP_SELF']}";
        $this->request_method = $_SERVER['REQUEST_METHOD'];
        $this->set_flags($flags);
    }

    function start_transfer($url)
    {
        $this->set_url($url);
        $this->open_socket();

        $this->http_basic_auth();
        $this->set_request_headers();
        $this->set_response();
        $this->http_basic_auth();

        if ($this->follow_location() === false)
        {
            if ($this->flags['accept_cookies'] == 1)
            {
                $this->set_cookies();
            }
        }
        else
        {
            $this->start_transfer($this->url);
        }
    }

    function open_socket()
    {
        $this->socket = @fsockopen($this->url_segments['host'], $this->url_segments['port'], $err_no, $err_str, 5);

        if ($this->socket === false)
        {
            $this->trigger_error("$err_no: $err_str (<b>URL:</b> {$this->url_segments['host']})");
        }
    }

    function set_response()
    {
        fwrite($this->socket, $this->request_headers);

        // Reset response headers and response body.

        $this->response_headers = '';
        $this->response_body = '';

        // Get the response headers first to extract content-type.
        do
        {
            $line = fgets($this->socket, 4096);
            $this->response_headers .= $line;
        }
        while ($line != "\r\n");

        $this->set_content_type();

        // If content-type isn't html or xhtml, just dump the rest of the response back to the client since
        // we don't need to do any further operations on it. And if the file were like a large movie file, it
        // wouldn't fit in a variable without exceeding the memory limit alloted for the scipt.

        if (!in_array($this->content_type, array('text/html', 'application/xml+xhtml')))
        {
            // Impose no time limit since it might be a large file that would take a long while to download.
            set_time_limit(0);
            $this->send_response_headers();
            fpassthru($this->socket);
            fclose($this->socket);
            exit();
        } 

        // Read the HTML response in $this->response_body
        do
        {
            $data = fread($this->socket, 8192);
            $this->response_body .= $data;
        }   
        while (strlen($data) != 0);

        fclose($this->socket);

        $this->response_code = next(explode(' ', $this->response_headers));

    }

    function set_content_type()
    {
        if (preg_match("#content-type:([^\r\n]*)#i", $this->response_headers, $matches) && trim($matches[1]) != '')
        {
            $content_type_array = explode(';', $matches[1]);
            $this->content_type = strtolower(trim($content_type_array[0]));
        }
        else
        {
            $this->content_type = 'text/html';
        }
    }

    function http_basic_auth()
    {
        if (empty($this->response_code))
        {
            if (isset($this->url_segments['user'], $this->url_segments['pass']))
            {
                $this->set_authorization($this->url_segments['user'], $this->url_segments['pass']);
                return true;
            }
            else if (($stored_auth = $this->get_cookies('AUTH')) != '')
            {
                $this->basic_auth_header = $stored_auth;
                return true;
            }
        }
        else if ($this->response_code == 401 && preg_match('#www-authenticate:\s*basic\s+(?:realm="(.*?)")?#i', $this->response_headers, $matches))
        {
            header('Location: '. sprintf('%s?action=auth&server=%s&realm=%s&auth_url=%s', $this->script_url, $this->encode_url($this->url_segments['host']), $matches[1], $this->encode_url($this->url)));
            exit();
        }
        return false;
    }

    function set_authorization($username, $password)
    {
        $this->basic_auth_header = base64_encode(sprintf('%s:%s', $username, $password));
        setcookie(urlencode("AUTH;{$this->url_segments['host']}"), $this->basic_auth_header, 0, '', $_SERVER['HTTP_HOST']);
    }

    function set_url($url)
    {
         $this->url = $this->decode_url($url);

         if (strpos($this->url, '://') === false)
         {
             $this->url = 'http://' . $this->url;
         }

        $url_segments = @parse_url($this->url);
        
        if (!empty($url_segments))
        {
            $url_segments['port']     = isset($url_segments['port']) ? $url_segments['port'] : 80;
            $url_segments['path']     = isset($url_segments['path']) ? $url_segments['path'] : '/';
            $url_segments['file']     = substr($url_segments['path'], strrpos($url_segments['path'], '/')+1);
            $url_segments['dir']      = substr($url_segments['path'], 0, strrpos($url_segments['path'], '/'));
            $url_segments['base']     = $url_segments['scheme'] . '://' . $url_segments['host'] . $url_segments['dir'];
            $url_segments['prev_dir'] = $url_segments['path'] != '/' ? substr($url_segments['base'], 0, strrpos($url_segments['base'], '/')+1) : $url_segments['base'] . '/';

            $this->url_segments = $url_segments;

            /*
                 URL: http://username:password@www.example.com:80/dir/dir/page.php?foo=bar&foo2=bar2#bookmark
                 scheme   // http
                 host     // www.example.com
                 port     // 80
                 user     // username
                 pass     // password
                 path     // /dir/dir/page.php
                 query    // ? 'foo=bar&foo2=bar2'
                 fragment // # 'bookmark'

                 file     // page.php
                 dir      // /dir/dir
                 base     // http://www.example.com/dir/dir
                 prev_dir // http://www.example.com/dir/
             */

             if (!$this->is_allowed_host())
             {
                 $this->trigger_error('You are not allowed to browse this host: ' . $this->url_segments['host']);
             }
         }
         else
         {
             $this->trigger_error('Please supply a valid URL');
         }
    }

    function is_allowed_host()
    {
        if (!empty($this->banned_hosts))
        {
            $hosts = &$this->banned_hosts;
            $return = false;
        }
        else if (!empty($this->allowed_hosts))
        {
            $hosts = &$this->allowed_hosts;
            $return = true;
        }
        else
        {
            return true;
        }

        foreach ($hosts as $host)
        {
            if (($host{0} == '.' && preg_match('#' . trim($host, '.') . '$#i', $this->url_segments['host'])) || strcasecmp($host, $this->url_segments['host']) == 0)
            {
                return $return;
            }
        }
        return $return ? false : true;
    }

    function encode_url($url)
    {
        return str_rot13(rawurlencode(preg_replace('#^([\w+.-]+)://#i', "$1/", $url)));
    }

    function decode_url($url)
    {
        return preg_replace('#^([\w+.-]+)/#i', "$1://", rawurldecode(str_rot13($url)));
    }

    function modify_urls()
    {
        preg_match_all("#\s(href|src|action|codebase|url)=([\"\'])?(.*?)([\"\'])?([\s\>])#i", $this->response_body, $matches, PREG_SET_ORDER);

        foreach ($matches as $match)
        {
            $uri = trim($match[3]);
            $fragment = ($hash_pos = strpos($uri, '#') !== false) ? '#' . substr($uri, $hash_pos) : ''; 

            if (!preg_match('#^[\w+.-]+://#i', $uri))
            {
                switch (substr($uri, 0, 1))
                {
                    case '/':
                        $uri = $this->url_segments['scheme'] . '://' . $this->url_segments['host'] . $uri;
                        break;
                    case '?':
                        $uri = $this->url_segments['base'] . '/' . $this->url_segments['file'] . $uri;
                        break;
                    case '#':
                        continue 2;
                        break;
                    case 'm':
                         if (substr($uri, 0, 7) == 'mailto:')
                         {
                             continue 2;
                         }
                    default:
                        $uri = $this->url_segments['base'] . '/' . $uri;
                        break;
                }
            }

            $uri     = $this->encode_url($uri);
            $replace = ' ' . $match[1] . '=' . $match[2] . $this->script_url . '?url=' . $uri . $fragment . $match[4] . $match[5];

            $this->response_body = str_replace($match[0], $replace, $this->response_body);
        }
    }

    function set_flags($flags)
    {
        if (is_numeric($flags))
        {
            setcookie('flags', $flags, time()+(4*7*24*60*60), '', $_SERVER['HTTP_HOST']); 
            $this->flags['include_form']   = $flags{0} == 1 ? 1 : 0;
            $this->flags['remove_scripts'] = $flags{1} == 1 ? 1 : 0;
            $this->flags['accept_cookies'] = $flags{2} == 1 ? 1 : 0;
            $this->flags['show_images']    = $flags{3} == 1 ? 1 : 0;
            $this->flags['show_referer']   = $flags{4} == 1 ? 1 : 0;
        }
        else if (isset($_COOKIE['flags']) && is_numeric($_COOKIE['flags']))
        {
            $this->set_flags($_COOKIE['flags']);
        }
    }

    function set_request_headers()
    {
        $headers  = "{$this->request_method} ". str_replace(' ', '%20', $this->url_segments['path']) . (isset($this->url_segments['query']) ? "?{$this->url_segments['query']}" : '') . " HTTP/1.0\r\n";
        $headers .= "Host: {$this->url_segments['host']}:{$this->url_segments['port']}\r\n";
        $headers .= "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)\r\n";
        $headers .= "Referer: WWW.IRPHC.COM\r\n";
        $headers .= "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1\r\n";

        if (($cookies = $this->get_cookies('COOKIE')) != '')
        {
            $headers .= "Cookie: $cookies\r\n";
        }
        if (!empty($this->basic_auth_header))
        {
            $headers .= "Authorization: Basic {$this->basic_auth_header}\r\n";
        }
        if ($this->request_method == 'POST')
        {
            if (!empty($_FILES) && (bool)ini_get('file_uploads'))
            {
                $this->data_boundary = 'PHProxy-' . md5(uniqid(rand(), true));
                $this->set_post_body('FILES/VARS', $_POST);
                $this->set_post_body('FILES/FILES', $_FILES);
                $headers .= "Content-Type: multipart/form-data; boundary={$this->data_boundary}\r\n";
                $headers .= "Content-Length: " . strlen($this->post_body) . "\r\n\r\n";
                $headers .= $this->post_body;
                $headers .= "--{$this->data_boundary}--";
            }
            else
            {
                $this->set_post_body('POST/VARS', $_POST);
                $headers .= "Content-Type: application/x-www-form-urlencoded\r\n";
                $headers .= "Content-Length: " . strlen($this->post_body) . "\r\n\r\n";
                $headers .= $this->post_body;
            }
        }

        $headers .= "\r\n";

        $this->request_headers = $headers;
    }

    function set_post_body($type, $array)
    {
        if ($type == 'FILES/FILES')
        {
            $array = $this->set_post_files($array);
            foreach ($array as $key => $file_info)
            {
                if (is_readable($file_info['tmp_name']))
                {
                    $this->post_body .= "--{$this->data_boundary}\r\n";
                    $this->post_body .= "Content-Disposition: form-data; name=\"$key\"; filename=\"{$file_info['name']}\"\r\n";
                    $this->post_body .= "Content-Type: {$file_info['type']}\r\n\r\n";
                    $handle = fopen($file_info['tmp_name'], 'rb');
                    $this->post_body .= fread($handle, filesize($file_info['tmp_name'])) . "\r\n";
                    fclose($handle);
                }
            }
        }
        else if ($type == 'FILES/VARS')
        {
            $array = $this->set_post_vars($array);
            foreach ($array as $key => $value)
            {
                $this->post_body .= "--{$this->data_boundary}\r\n";
                $this->post_body .= "Content-Disposition: form-data; name=\"$key\"\r\n\r\n";
                $this->post_body .= urldecode($value) . "\r\n";
            }
        }
        else if ($type == 'POST/VARS')
        {  
            $array = $this->set_post_vars($array);
            foreach ($array as $key => $value)
            {
                $this->post_body .= !empty($this->post_body) ? '&' : '';
                $this->post_body .= $key . '=' . $value;
            }
        }
    }

    function set_post_vars($array, $parent_key = null)
    {
        $tmp = array();

        foreach ($array as $key => $value)
        {
            $key = isset($parent_key) ? sprintf('%s[%s]', $parent_key, urlencode($key)) : urlencode($key);
            if (is_array($value))
            {
                $tmp = array_merge($tmp, $this->set_post_vars($value, $key));
            }
            else
            {
                $tmp[$this->stripslashes($key)] = urlencode($this->stripslashes($value));
            }
        }
        return $tmp;
    }

    function set_post_files($array, $parent_key = null)
    {
        $tmp = array();

        foreach ($array as $key => $value)
        {
            $key = isset($parent_key) ? sprintf('%s[%s]', $parent_key, urlencode($key)) : urlencode($key);
            if (is_array($value))
            {
                $tmp = array_merge_recursive($tmp, $this->set_post_files($value, $key));
            }
            else if (preg_match('#^([^\[\]]+)\[(name|type|tmp_name)\]#', $key, $m))
            {
                $tmp[$this->stripslashes(str_replace($m[0], $m[1], $key))][$m[2]] = $value;
            }
        }
        return $tmp;
    }

    function follow_location()
    {
        if (preg_match("#(location|uri):([^\r\n]*)#i", $this->response_headers, $matches))
        {
            if (($uri = trim($matches[2])) == '')
            {
                return false;
            }

            if (!preg_match('#^[\w+.-]+://#i', $uri))
            {
                if (substr($uri, 0, 1) == '/')
                {
                    $uri = $this->url_segments['scheme'] . '://' . $this->url_segments['host'] . $uri;
                }
                else
                {
                    $uri = $this->url_segments['base'] . '/' . $uri;
                }
            }

            $this->url = $this->encode_url($uri);
            return true;
        }
        return false;
    }

    function set_cookies()
    {
        if (preg_match_all("#set-cookie:([^\r\n]*)#i", $this->response_headers, $matches))
        {
            foreach ($matches[1] as $cookie_info)
            {
                preg_match('#^\s*([^=;,\s]*)=?([^;,\s]*)#', $cookie_info, $match)  && list(, $name, $value) = $match;
                preg_match('#;\s*expires\s*=([^;]*)#i', $cookie_info, $match)      && list(, $expires)      = $match;
                preg_match('#;\s*path\s*=\s*([^;,\s]*)#i', $cookie_info, $match)   && list(, $path)         = $match;
                preg_match('#;\s*domain\s*=\s*([^;,\s]*)#i', $cookie_info, $match) && list(, $domain)       = $match;
                preg_match('#;\s*(secure\b)#i', $cookie_info, $match)              && list(, $secure)       = $match;

                $expires = isset($expires) ? strtotime($expires) : 0;
                $path    = isset($path)    ? $path : $this->url_segments['dir'];
                $domain  = isset($domain)  ? $domain : $this->url_segments['host'];
                $domain  = rtrim($domain, '.');

                if (!preg_match("#$domain$#i", $this->url_segments['host']))
                {
                    continue;
                }
                if (preg_match('#\.(com|edu|net|org|gov|mil|int|aero|biz|coop|info|museum|name|pro)$#i', $domain))
                {
                    if (substr_count($domain, '.') < 2)
                    {
                        continue;
                    }
                }
                else if (substr_count($domain, '.') < 3) 
                {
                    continue;
                }

                setcookie(urlencode("COOKIE;$name;$domain;$path"), $value, $expires, '', $_SERVER['HTTP_HOST']);
            }

            $this->response_headers = str_replace($matches[0], '', $this->response_headers);
        }
    }

    function get_cookies($type = 'COOKIE', $restrict = true)
    {
        if (!empty($_COOKIE))
        {
            $cookies = '';

            foreach ($_COOKIE as $cookie_name => $cookie_value)
            {
                $cookie_args = explode(';', urldecode($cookie_name));

                if ($cookie_args[0] != $type || count($cookie_args) < 2)
                {
                    continue;
                }

                if ($type == 'AUTH')
                {
                    list(, $server) = $cookie_args;
                    $server = str_replace('_', '.', $server);

                    if ($this->url_segments['host'] == $server)
                    {
                        return $cookie_value;
                    }
                    else
                    {
                        continue;
                    }
                }
                else if ($type == 'COOKIE')
                {
                    $cookie_args[2] = str_replace('_', '.', $cookie_args[2]);

                    if ($restrict)
                    {
                        list(, $name, $domain, $path) = $cookie_args;

                        if (preg_match("#$domain$#i", $this->url_segments['host']) && preg_match("#^$path#i", $this->url_segments['path']))
                        {
                            $cookies .= $cookies != '' ? '; ' : '';
                            $cookies .= "$name=$cookie_value";
                        }
                    }
                    else
                    {
                        array_shift($cookie_args);
                        $cookie_args[] = $cookie_value;
                        $cookies[] = $cookie_args;
                    }
                }
            }
            return $cookies;
        }
    }

    function delete_cookies($hash)
    {
        $cookies = $this->get_cookies(false);

        foreach ($cookies as $args)
        {
            if ($hash == 'all' || $hash == md5($args[0].$args[1].$args[2].$args[3]))
            {
                setcookie(urlencode("COOKIE;$args[0];$args[1];$args[2]"), '', 1, '', $_SERVER['HTTP_HOST']);
            }
        }
    }

    function send_response_headers()
    {
        $headers = explode("\r\n", $this->response_headers);
        $headers[] = 'Content-Disposition: ' . ($this->content_type == 'application/octet_stream' ? 'attachment' : 'inline') . '; filename=' . $this->url_segments['file'];

        if (!empty($this->response_body))
        {
            $headers[] = 'Content-Length: ' . strlen($this->response_body);
        }

        $headers = array_filter($headers);

        foreach ($headers as $header)
        {
            header($header);
        }
    }

    function return_response($send_headers = true)
    {
        if ($this->flags['remove_scripts'] == 1)
        {
            $this->remove_scripts();
        }
        if ($this->flags['show_images'] == 0)
        {
            $this->remove_images();
        }

        $this->modify_urls();

        if ($this->flags['include_form'] == 1)
        {
            $this->include_form();
        }

        if ($send_headers)
        {
            $this->send_response_headers();
        }

        return $this->response_body;
    }

    function remove_scripts()
    {
        $this->response_body = preg_replace('#<script[^>]*?>.*?</script>#si', '', $this->response_body); // Remove any scripts enclosed between <script />
        $this->response_body = preg_replace("#\s*(\bon\w+)=([\"\'])?(.*?)([\"\'])?([\s\>])#i", "$5", $this->response_body); // Remove javascript event handlers
        $this->response_body = preg_replace('#<noscript>(.*?)</noscript>#si', "$1", $this->response_body); //expose any html between <noscript />

    }

    function remove_images()
    {
        $this->response_body = preg_replace('#<(img|image)[^>]*?>#si', '', $this->response_body);
    }

    function include_form()
    {
        ob_start();
            include_once 'url_form.inc';
            $form_html = ob_get_contents();
        ob_end_clean();
        $this->response_body = preg_replace("#\<body(.*?)\>#si", "$0\n$form_html", $this->response_body, 1);
    }

    function trigger_error($error)
    {
        header("Location: $this->script_url?error=$error");
        exit(); 
    }

    function options_list($tabulate = false, $comments_on = false)
    {
        $output   = '';
        $comments = array();
        $comments['include_form']   = 'Includes a mini URL-form on every HTML page';
        $comments['remove_scripts'] = 'Remove all sorts of client-side scripting';
        $comments['accept_cookies'] = 'Accept HTTP cookies';
        $comments['show_images']    = 'Show images';
        $comments['show_referer']   = 'Show referring website in HTTP headers';

        foreach ($this->flags as $flag_code => $flag_status)
        {
            $interface = array(ucwords(str_replace('_', ' ', $flag_code)),
                               ' <input type="checkbox" name="ops[]"'
                               . ($flag_status == 1 ? ' checked="checked"' : '') . ' /> '
                               );

            if (!$tabulate)
            {
                $interface = array_reverse($interface);
            }

            $output .= ($tabulate    ? '<tr><td class="option">'  : '') 
                     . $interface[0]
                     . ($tabulate    ? '</td><td class="option">' : '') 
                     . $interface[1]
                     . ($comments_on ? $comments[$flag_code]      : '') 
                     . ($tabulate    ? '</td></tr>'               : '');
        }

        return $output;
    }

/*
 *
 * Wrappers, Missing Functions...
 *
 */

    function stripslashes($str)
    {
        return get_magic_quotes_gpc() ? stripslashes($str) : $str;
    }
}

?>