├── Dockerfile ├── LICENSE ├── README.md └── proxy.php /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official PHP image as the base image 2 | FROM php:7.4-cli 3 | 4 | # Copy the proxy.php file into the Docker image 5 | COPY proxy.php /var/www/html/proxy.php 6 | 7 | # Expose port 80 for the web server 8 | EXPOSE 80 9 | 10 | # Use CMD to start the PHP built-in web server 11 | CMD ["php", "-S", "0.0.0.0:80", "-t", "/var/www/html"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Heiswayi Nrird 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-proxy-script 2 | 3 | A minimalist, private web proxy script written in PHP code. 4 | 5 | ## Running the Project Locally 6 | 7 | To run the project locally, you need to have PHP installed on your machine. The required PHP version is 7.4 or higher. Additionally, you need to have the cURL extension enabled. 8 | 9 | 1. Clone the repository: 10 | ```sh 11 | git clone https://github.com/heiswayi/web-proxy-script.git 12 | cd web-proxy-script 13 | ``` 14 | 15 | 2. Start the PHP built-in web server: 16 | ```sh 17 | php -S localhost:8000 18 | ``` 19 | 20 | 3. Open your web browser and navigate to `http://localhost:8000/proxy.php`. 21 | 22 | ## Using Docker 23 | 24 | To build and run the Docker container, follow these steps: 25 | 26 | 1. Build the Docker image: 27 | ```sh 28 | docker build -t web-proxy-script . 29 | ``` 30 | 31 | 2. Run the Docker container: 32 | ```sh 33 | docker run -p 80:80 web-proxy-script 34 | ``` 35 | 36 | 3. Open your web browser and navigate to `http://localhost/proxy.php`. 37 | 38 | ## License 39 | 40 | [MIT](LICENSE) 41 | -------------------------------------------------------------------------------- /proxy.php: -------------------------------------------------------------------------------- 1 | $value) { 16 | if (substr($key, 0, 5) == "HTTP_") { 17 | $key = str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($key, 5))))); 18 | $result[$key] = $value; 19 | } else { 20 | $result[$key] = $value; 21 | } 22 | } 23 | return $result; 24 | } 25 | } 26 | 27 | define("PROXY_PREFIX", "http" . (isset($_SERVER['HTTPS']) ? "s" : "") . "://" . $_SERVER["SERVER_NAME"] . ($_SERVER["SERVER_PORT"] != 80 ? ":" . $_SERVER["SERVER_PORT"] : "") . $_SERVER["SCRIPT_NAME"] . "/"); 28 | 29 | //Makes an HTTP request via cURL, using request data that was passed directly to this script. 30 | function makeRequest($url) { 31 | 32 | //Tell cURL to make the request using the brower's user-agent if there is one, or a fallback user-agent otherwise. 33 | $user_agent = $_SERVER["HTTP_USER_AGENT"]; 34 | if (empty($user_agent)) { 35 | $user_agent = "Mozilla/5.0 (compatible; nrird.xyz/proxy)"; 36 | } 37 | $ch = curl_init(); 38 | curl_setopt($ch, CURLOPT_USERAGENT, $user_agent); 39 | 40 | //Proxy the browser's request headers. 41 | $browserRequestHeaders = getallheaders(); 42 | //(...but let cURL set some of these headers on its own.) 43 | //TODO: The unset()s below assume that browsers' request headers 44 | //will use casing (capitalizations) that appear within them. 45 | unset($browserRequestHeaders["Host"]); 46 | unset($browserRequestHeaders["Content-Length"]); 47 | //Throw away the browser's Accept-Encoding header if any; 48 | //let cURL make the request using gzip if possible. 49 | unset($browserRequestHeaders["Accept-Encoding"]); 50 | curl_setopt($ch, CURLOPT_ENCODING, ""); 51 | //Transform the associative array from getallheaders() into an 52 | //indexed array of header strings to be passed to cURL. 53 | $curlRequestHeaders = array(); 54 | foreach ($browserRequestHeaders as $name => $value) { 55 | $curlRequestHeaders[] = $name . ": " . $value; 56 | } 57 | curl_setopt($ch, CURLOPT_HTTPHEADER, $curlRequestHeaders); 58 | 59 | //Proxy any received GET/POST/PUT data. 60 | switch ($_SERVER["REQUEST_METHOD"]) { 61 | case "GET": 62 | $getData = array(); 63 | foreach ($_GET as $key => $value) { 64 | $getData[] = urlencode($key) . "=" . urlencode($value); 65 | } 66 | if (count($getData) > 0) { 67 | //Remove any GET data from the URL, and re-add what was read. 68 | //TODO: Is the code in this "GET" case necessary? 69 | //It reads, strips, then re-adds all GET data; this may be a no-op. 70 | $url = substr($url, 0, strrpos($url, "?")); 71 | $url .= "?" . implode("&", $getData); 72 | } 73 | break; 74 | case "POST": 75 | curl_setopt($ch, CURLOPT_POST, true); 76 | //For some reason, $HTTP_RAW_POST_DATA isn't working as documented at 77 | //http://php.net/manual/en/reserved.variables.httprawpostdata.php 78 | //but the php://input method works. This is likely to be flaky 79 | //across different server environments. 80 | //More info here: http://stackoverflow.com/questions/8899239/http-raw-post-data-not-being-populated-after-upgrade-to-php-5-3 81 | curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents("php://input")); 82 | break; 83 | case "PUT": 84 | curl_setopt($ch, CURLOPT_PUT, true); 85 | curl_setopt($ch, CURLOPT_INFILE, fopen("php://input")); 86 | break; 87 | } 88 | 89 | //Other cURL options. 90 | curl_setopt($ch, CURLOPT_HEADER, true); 91 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 92 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 93 | curl_setopt ($ch, CURLOPT_FAILONERROR, true); 94 | 95 | //Set the request URL. 96 | curl_setopt($ch, CURLOPT_URL, $url); 97 | 98 | //Make the request. 99 | $response = curl_exec($ch); 100 | $responseInfo = curl_getinfo($ch); 101 | $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); 102 | curl_close($ch); 103 | 104 | //Setting CURLOPT_HEADER to true above forces the response headers and body 105 | //to be output together--separate them. 106 | $responseHeaders = substr($response, 0, $headerSize); 107 | $responseBody = substr($response, $headerSize); 108 | 109 | return array("headers" => $responseHeaders, "body" => $responseBody, "responseInfo" => $responseInfo); 110 | } 111 | 112 | //Converts relative URLs to absolute ones, given a base URL. 113 | //Modified version of code found at http://nashruddin.com/PHP_Script_for_Converting_Relative_to_Absolute_URL 114 | function rel2abs($rel, $base) { 115 | if (empty($rel)) $rel = "."; 116 | if (parse_url($rel, PHP_URL_SCHEME) != "" || strpos($rel, "//") === 0) return $rel; //Return if already an absolute URL 117 | if ($rel[0] == "#" || $rel[0] == "?") return $base.$rel; //Queries and anchors 118 | extract(parse_url($base)); //Parse base URL and convert to local variables: $scheme, $host, $path 119 | $path = isset($path) ? preg_replace('#/[^/]*$#', "", $path) : "/"; //Remove non-directory element from path 120 | if ($rel[0] == '/') $path = ""; //Destroy path if relative url points to root 121 | $port = isset($port) && $port != 80 ? ":" . $port : ""; 122 | $auth = ""; 123 | if (isset($user)) { 124 | $auth = $user; 125 | if (isset($pass)) { 126 | $auth .= ":" . $pass; 127 | } 128 | $auth .= "@"; 129 | } 130 | $abs = "$auth$host$path$port/$rel"; //Dirty absolute URL 131 | for ($n = 1; $n > 0; $abs = preg_replace(array("#(/\.?/)#", "#/(?!\.\.)[^/]+/\.\./#"), "/", $abs, -1, $n)) {} //Replace '//' or '/./' or '/foo/../' with '/' 132 | return $scheme . "://" . $abs; //Absolute URL is ready. 133 | } 134 | 135 | //Proxify contents of url() references in blocks of CSS text. 136 | function proxifyCSS($css, $baseURL) { 137 | return preg_replace_callback( 138 | '/url\((.*?)\)/i', 139 | function($matches) use ($baseURL) { 140 | $url = $matches[1]; 141 | //Remove any surrounding single or double quotes from the URL so it can be passed to rel2abs - the quotes are optional in CSS 142 | //Assume that if there is a leading quote then there should be a trailing quote, so just use trim() to remove them 143 | if (strpos($url, "'") === 0) { 144 | $url = trim($url, "'"); 145 | } 146 | if (strpos($url, "\"") === 0) { 147 | $url = trim($url, "\""); 148 | } 149 | if (stripos($url, "data:") === 0) return "url(" . $url . ")"; //The URL isn't an HTTP URL but is actual binary data. Don't proxify it. 150 | return "url(" . PROXY_PREFIX . rel2abs($url, $baseURL) . ")"; 151 | }, 152 | $css); 153 | } 154 | 155 | // Create log 156 | function recordLog($url) { 157 | $userip = $_SERVER['REMOTE_ADDR']; 158 | $rdate = date("d-m-Y", time()); 159 | $data = $rdate.','.$userip.','.$url.PHP_EOL; 160 | $logfile = 'logs/'.$userip.'_log.txt'; 161 | $fp = fopen($logfile, 'a'); 162 | fwrite($fp, $data); 163 | } 164 | 165 | $proxy_prefix = PROXY_PREFIX; 166 | $htmlcode = << 168 | 169 | 170 | 171 | Upload 172 | 184 | 185 | 186 |
187 |
Enter the full URL to proxify:
188 | 189 | 190 |
191 | 192 | 193 | ENDHTML; 194 | 195 | $url = substr($_SERVER["REQUEST_URI"], strlen($_SERVER["SCRIPT_NAME"]) + 1); 196 | if (empty($url)) die($htmlcode); 197 | 198 | if (strpos($url, "//") === 0) $url = "http:" . $url; //Assume that any supplied URLs starting with // are HTTP URLs. 199 | if (!preg_match("@^.*://@", $url)) $url = "http://" . $url; //Assume that any supplied URLs without a scheme are HTTP URLs. 200 | 201 | // recordLog($url); 202 | 203 | $response = makeRequest($url); 204 | $rawResponseHeaders = $response["headers"]; 205 | $responseBody = $response["body"]; 206 | $responseInfo = $response["responseInfo"]; 207 | 208 | //cURL can make multiple requests internally (while following 302 redirects), and reports 209 | //headers for every request it makes. Only proxy the last set of received response headers, 210 | //corresponding to the final request made by cURL for any given call to makeRequest(). 211 | $responseHeaderBlocks = array_filter(explode("\r\n\r\n", $rawResponseHeaders)); 212 | $lastHeaderBlock = end($responseHeaderBlocks); 213 | $headerLines = explode("\r\n", $lastHeaderBlock); 214 | foreach ($headerLines as $header) { 215 | if (stripos($header, "Content-Length") === false && stripos($header, "Transfer-Encoding") === false) { 216 | header($header); 217 | } 218 | } 219 | 220 | $contentType = ""; 221 | if (isset($responseInfo["content_type"])) $contentType = $responseInfo["content_type"]; 222 | 223 | //This is presumably a web page, so attempt to proxify the DOM. 224 | if (stripos($contentType, "text/html") !== false) { 225 | 226 | //Attempt to normalize character encoding. 227 | $responseBody = mb_convert_encoding($responseBody, "HTML-ENTITIES", mb_detect_encoding($responseBody)); 228 | 229 | //Parse the DOM. 230 | $doc = new DomDocument(); 231 | @$doc->loadHTML($responseBody); 232 | $xpath = new DOMXPath($doc); 233 | 234 | //Rewrite forms so that their actions point back to the proxy. 235 | foreach($xpath->query('//form') as $form) { 236 | $method = $form->getAttribute("method"); 237 | $action = $form->getAttribute("action"); 238 | //If the form doesn't have an action, the action is the page itself. 239 | //Otherwise, change an existing action to an absolute version. 240 | $action = empty($action) ? $url : rel2abs($action, $url); 241 | //Rewrite the form action to point back at the proxy. 242 | $form->setAttribute("action", PROXY_PREFIX . $action); 243 | } 244 | //Profixy