|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * Copyright (c) 2011 Dino Ciuffetti, NuvolaBase, TuxWeb S.r.l. |
| 4 | + * |
| 5 | + * ajax_proxy is free software: you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU Lesser General Public License as published by |
| 7 | + * the Free Software Foundation, either version 3 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * ajax_proxy is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU Lesser General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU Lesser General Public License |
| 16 | + * along with ajax_proxy. If not, see <http://www.gnu.org/licenses/>. |
| 17 | +*/ |
| 18 | + |
| 19 | +/** |
| 20 | + * Welcome to ajax_proxy, the simple HTTP Proxy for cross domain ajax requests. |
| 21 | + * This is a simple and secure PHP script that aims to resolve the cross domain security |
| 22 | + * enforcement imposed by web browsers. That is done by handling the cross domain javascript |
| 23 | + * HTTP request locally. In other words, the browser will let your javascript code to GET/POST |
| 24 | + * the given uri because it's your PHP driven server to accomplish with the request to the real server. |
| 25 | + * |
| 26 | + * For security reasons ajax_proxy do not let your javascript to choose the destination server |
| 27 | + * at runtime, but that's imposed on a configuration variable. |
| 28 | + * We must do this because otherwise your server would become an open proxy, and that is absolutely not desiderable! |
| 29 | + * |
| 30 | + * The script is also good with HTTP GET/POST/PUT/DELETE requests, for example for REST. |
| 31 | + */ |
| 32 | + |
| 33 | +// You have to define here your real destination server without a trailing slash following this syntax: |
| 34 | +// <http|https>://<user>:<password>@<host> |
| 35 | +// Example: http://jack:[email protected] |
| 36 | +$real_destination_host = "https://user:[email protected]"; |
| 37 | + |
| 38 | +// We split the response into small chunks (in kbyte) so that the browser (javascript) can parse data while we are |
| 39 | +// reading the response from the server. Do not set it too small, 32 is ok for most cases! |
| 40 | +$chunk_size_kb = 32; |
| 41 | + |
| 42 | +// User-Agent for the client side of the proxy. The real server will see this User-Agent instead of the client's one. |
| 43 | +// If this is left empty, the real server will see the User-Agent of the client. |
| 44 | +// $user_agent = "ajax_proxy HTTP agent"; |
| 45 | +$user_agent = 'ajax_proxy cross domain PHP script'; |
| 46 | + |
| 47 | +// Server side request timeout in ms. If the browser (javascript) does not complete the request into this timeout the |
| 48 | +// connection with the client will be aborted. Please note that timeout cannot exceed PHP limits (php.ini). |
| 49 | +// If the variable is setted to 0 the timeout will be disabled. |
| 50 | +$request_client_timeout_ms = 6000; |
| 51 | + |
| 52 | +// Client side response timeout in ms. If the destination server does not complete the response into this timeout the |
| 53 | +// connection with the server will be aborted. Please note that timeout cannot exceed PHP limits (php.ini). |
| 54 | +// If the variable is setted to 0 the timeout will be disabled. |
| 55 | +// WARNING: you may send partial data to the browser (javascript) in case of timeout |
| 56 | +// (the connection will be closed before finish to complete the read from the destination server). |
| 57 | +$response_server_timeout_ms = 6000; |
| 58 | + |
| 59 | + |
| 60 | +// CLIENT SIDE PROXY (open and handle the request to the backend real HTTP server and return back headers and body) |
| 61 | +function send_datachunk_to_client($arg) { // this callback is assigned by ob_start and invoked on every ob_flush |
| 62 | + // if we like it we can modify the data before sending it to the browser... but now we don't. |
| 63 | + return false; |
| 64 | +} |
| 65 | +function check_timeout($handle) { // check if there was a timeout error with the backend server |
| 66 | + $info = @stream_get_meta_data($handle); |
| 67 | + if ($info['timed_out']) { |
| 68 | + return true; // there was a timeout |
| 69 | + } else { |
| 70 | + return false; // there was not a timeout |
| 71 | + } |
| 72 | +} |
| 73 | +function strip_header($h) { // strip out any HTTP backend server's response header we don't like |
| 74 | + $to_be_stripped = array("Content-Length", "Connection", "Accept-Ranges", "Date"); |
| 75 | + $name = substr($h, 0, strpos($h, ":")); |
| 76 | + if ($name == "") { // this is a header without ":", return it full |
| 77 | + return $h; |
| 78 | + } |
| 79 | + if (in_array($name, $to_be_stripped)) { // this is a header that we don't like so we return false |
| 80 | + return false; |
| 81 | + } |
| 82 | + $value = substr($h, strpos($h, ":")+2); |
| 83 | + return "$name: $value"; // that's a header we like (may be...) so we are returning it |
| 84 | +} |
| 85 | +function handle_response_timeout($type) { // handle a timeout error. If type == 0 the problem is on header, else it's on body |
| 86 | + switch ($type) { |
| 87 | + case 0: // timeout connecting or reading headers |
| 88 | + generate_http_error("HTTP/1.1 500 Timeout waiting for destination server"); |
| 89 | + break; |
| 90 | + default: |
| 91 | + case 1: // timeout on body read |
| 92 | + generate_http_error("HTTP/1.1 500 Timeout waiting for destination server"); |
| 93 | + break; |
| 94 | + } |
| 95 | + return true; |
| 96 | +} |
| 97 | +function parse_headers($handle) { // here we parse HTTP backend server's response headers |
| 98 | + $info = @stream_get_meta_data($handle); |
| 99 | + if ($info['timed_out']) { // there was a connection/read timeout |
| 100 | + handle_response_timeout(0); |
| 101 | + return false; |
| 102 | + } |
| 103 | + for ($i=0; $i<count($info['wrapper_data']); $i++) { // for each response header, check if we like it or not |
| 104 | + $h = $info['wrapper_data'][$i]; |
| 105 | + $hd = strip_header($h); |
| 106 | + if ($hd) { // we like it, and so we decide to pass it to the browser |
| 107 | + Header ($hd); |
| 108 | + } |
| 109 | + } |
| 110 | + // we add our custom headers here |
| 111 | + Header ("Connection: close"); |
| 112 | + return $info; |
| 113 | +} |
| 114 | +function proxypass($url, $method, $parameters, $header_timeout_ms, $body_timeout_ms) { |
| 115 | + global $user_agent, $chunk_size_kb; |
| 116 | + |
| 117 | + $opts = array(); |
| 118 | + $opts['http'] = array(); |
| 119 | + $opts['http']['method'] = $method; |
| 120 | + if ($user_agent == "") { // setting the user agent according to the user choise |
| 121 | + $opts['http']['user_agent'] = $_SERVER['HTTP_USER_AGENT']; |
| 122 | + } else { |
| 123 | + $opts['http']['user_agent'] = $user_agent; |
| 124 | + } |
| 125 | + if (($method == "POST") || ($method == "PUT") || ($method == "DELETE")) { |
| 126 | + $opts['http']['content'] = http_build_query($parameters); |
| 127 | + } |
| 128 | + |
| 129 | + $context = stream_context_create($opts); |
| 130 | + |
| 131 | + $handle = @fopen($url, "rb", false, $context); |
| 132 | + if (!$handle) { |
| 133 | + @fclose($handle); |
| 134 | + generate_http_error("HTTP/1.1 500 Backend Server Error"); |
| 135 | + echo ": caused by: "; |
| 136 | + echo $http_response_header[0]; |
| 137 | + // echo " opening url: $url\n"; // security issue here because the password is printed in the output |
| 138 | + echo " opening url: " . parse_url($url, PHP_URL_SCHEME) . "://_hidden_server_" . parse_url($url, PHP_URL_PATH); |
| 139 | + ob_end_flush(); |
| 140 | + flush(); |
| 141 | + exit; |
| 142 | + } |
| 143 | + if ($header_timeout_ms > 0) { |
| 144 | + @stream_set_timeout($handle, 0, $header_timeout_ms * 1000); |
| 145 | + } |
| 146 | + $info = parse_headers($handle); |
| 147 | + if ($body_timeout_ms > 0) { |
| 148 | + @stream_set_timeout($handle, 0, $body_timeout_ms * 1000); |
| 149 | + } |
| 150 | + while (!feof($handle)) { |
| 151 | + $res = @fread($handle, $chunk_size_kb); // read a chunk of data from the backend server |
| 152 | + if ($res === FALSE) { |
| 153 | + //generate_http_error("HTTP/1.1 500 Backend Server Error"); |
| 154 | + ob_end_flush(); |
| 155 | + flush(); |
| 156 | + exit; |
| 157 | + } |
| 158 | + $i = check_timeout($handle); // check if there was a timeout error |
| 159 | + if (!$i) { // good! The server's response chunk was in time with our need |
| 160 | + print $res; // send it to the client |
| 161 | + ob_flush(); // be sure to send it to the client |
| 162 | + flush(); // be extra sure to send it to the client :-) |
| 163 | + } else { // mhhh... :-( there was a timeout reading data |
| 164 | + handle_response_timeout(1); |
| 165 | + break; |
| 166 | + } |
| 167 | + } |
| 168 | + @fclose($handle); |
| 169 | + ob_end_flush(); |
| 170 | +} |
| 171 | +// END CLIENT SIDE PROXY |
| 172 | + |
| 173 | +// SERVER SIDE PROXY (accept and handle the client request (browser/javascript) |
| 174 | +function generate_http_error($err) { |
| 175 | + Header ($err); |
| 176 | + echo "\n"; |
| 177 | + echo $err; |
| 178 | +} |
| 179 | +function get_client_headers() { // here we get client (browser, javascript) headers |
| 180 | + $headers = array(); |
| 181 | + foreach ($_SERVER as $k => $v) { |
| 182 | + if (substr($k, 0, 5) == "HTTP_") { |
| 183 | + $k = str_replace('_', ' ', substr($k, 5)); |
| 184 | + $k = str_replace(' ', '-', ucwords(strtolower($k))); |
| 185 | + $headers[$k] = $v; |
| 186 | + } |
| 187 | + } |
| 188 | + return $headers; |
| 189 | +} |
| 190 | +function parse_client_parameters($method) { // parsing a client HTTP request |
| 191 | + switch ($method) { |
| 192 | + case "POST": |
| 193 | + return $_POST; |
| 194 | + break; |
| 195 | + case "PUT": |
| 196 | + return $_POST; |
| 197 | + break; |
| 198 | + case "GET": |
| 199 | + return $_GET; |
| 200 | + break; |
| 201 | + case "HEAD": |
| 202 | + return $_GET; |
| 203 | + break; |
| 204 | + case "DELETE": |
| 205 | + return $_GET; |
| 206 | + break; |
| 207 | + } |
| 208 | +} |
| 209 | +// END SERVER SIDE PROXY |
| 210 | + |
| 211 | + |
| 212 | +// SCRIPT STARTS HERE |
| 213 | +ob_start("send_datachunk_to_client", $chunk_size_kb); |
| 214 | +ini_set('user_agent', "MyUserAgent\r\nX-Powered-By: dAm2K"); |
| 215 | +if ($chunk_size_kb < 1) $chunk_size_kb = 1; |
| 216 | +$chunk_size_kb = $chunk_size_kb * 1024; |
| 217 | + |
| 218 | +// SERVER SIDE PROXY |
| 219 | +$client_headers = array(); |
| 220 | +$client_headers = get_client_headers(); |
| 221 | +$parameters = parse_client_parameters($_SERVER['REQUEST_METHOD']); |
| 222 | +if ($response_server_timeout_ms > 0) { |
| 223 | + ini_set("max_execution_time", $response_server_timeout_ms / 1000); |
| 224 | + ini_set("max_input_time", $response_server_timeout_ms / 1000); |
| 225 | +} |
| 226 | + |
| 227 | +// CLIENT SIDE PROXY |
| 228 | +$url = $real_destination_host . $_SERVER['REQUEST_URI']; |
| 229 | +$url = str_replace($_SERVER["SCRIPT_NAME"], "", $url); |
| 230 | +$header_timeout_ms = $response_server_timeout_ms; |
| 231 | +$body_timeout_ms = $response_server_timeout_ms; |
| 232 | + |
| 233 | +// SENDING DATA TO THE SERVER AND GETTING BACK DATA FOR THE CLIENT |
| 234 | +proxypass($url, $_SERVER['REQUEST_METHOD'], $parameters, $header_timeout_ms, $body_timeout_ms); |
| 235 | + |
| 236 | +?> |
0 commit comments