true, CURLOPT_HEADER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_HTTPHEADER => $headersOut, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_ENCODING => '', CURLOPT_TIMEOUT => 30, ]); if ($rawBody !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, $rawBody); } elseif ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $_POST); } // Cookies if (!empty($_COOKIE)) { $cookieParts = []; foreach ($_COOKIE as $k => $v) $cookieParts[] = $k . '=' . $v; curl_setopt($ch, CURLOPT_COOKIE, implode('; ', $cookieParts)); } $response = curl_exec($ch); if ($response === false) { $err = curl_error($ch); curl_close($ch); http_response_code(502); header('Content-Type: text/plain; charset=utf-8'); echo "Erro ao contatar o servidor de destino: " . $err; exit; } $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: 'application/octet-stream'; $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $rawHeaders = substr($response, 0, $headerSize); $body = substr($response, $headerSize); curl_close($ch); // Parse headers $headersAssoc = []; $setCookies = []; foreach (explode("\r\n", $rawHeaders) as $line) { $line = trim($line); if ($line === '' || str_starts_with($line, 'HTTP/')) continue; if (stripos($line, 'Set-Cookie:') === 0) { $setCookies[] = trim(substr($line, strlen('Set-Cookie:'))); continue; } $parts = explode(':', $line, 2); if (count($parts) === 2) { $headersAssoc[trim($parts[0])] = trim($parts[1]); } } return [$status, $headersAssoc, $body, $contentType, $setCookies]; } function rewriteHtml(string $html, string $targetOrigin, string $requestPathOnTarget, string $proxyBase): string { libxml_use_internal_errors(true); $dom = new DOMDocument(); $html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'); if (!$dom->loadHTML($html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD)) return $html; $xp = new DOMXPath($dom); $map = [ ['//a[@href]', 'href'], ['//link[@href]', 'href'], ['//script[@src]', 'src'], ['//img[@src]', 'src'], ['//form[@action]', 'action'] ]; foreach ($map as [$query, $attr]) { foreach ($xp->query($query) as $node) { /** @var DOMElement $node */ $val = $node->getAttribute($attr); if ($val === '') continue; $abs = absolutizeUrl($val, $targetOrigin, $requestPathOnTarget); if (isAllowedUrl($abs, [$targetOrigin])) { $node->setAttribute($attr, rewriteToProxy($abs, $proxyBase)); } else if ($node->tagName === 'a') { $node->setAttribute('target','_blank'); $node->setAttribute('rel','noopener'); } } } // base para ajudar resolução de relativos em runtime $heads = $dom->getElementsByTagName('head'); if ($heads->length) { $head = $heads->item(0); $base = $dom->createElement('base'); $base->setAttribute('href', rtrim($proxyBase ?: 'index.php?path=', '/') . '/'); $head->insertBefore($base, $head->firstChild); } $out = $dom->saveHTML(); libxml_clear_errors(); return $out; } function injectRefresh(string $html, int $seconds): string { if ($seconds <= 0) return $html; $snippet = << (function(){ var s = $seconds; function tick(){ if (s <= 0){ location.reload(); return; } var el = document.getElementById('proxy-refresh'); if(!el){ el = document.createElement('div'); el.id = 'proxy-refresh'; el.style.cssText = 'position:fixed;right:8px;bottom:8px;background:#0b5;color:#fff;padding:6px 10px;border-radius:8px;font:12px system-ui;opacity:.85;z-index:2147483647'; document.body.appendChild(el); } el.textContent = 'Auto-refresh em ' + s + 's'; s--; setTimeout(tick,1000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', tick); } else { tick(); } })(); HTML; // insere antes de ; senão, anexa no fim $pos = stripos($html, ''); if ($pos !== false) { return substr($html, 0, $pos) . $snippet . substr($html, $pos); } return $html . $snippet; } function relaySetCookies(array $setCookies, string $proxyHost, string $proxyBasePath): void { foreach ($setCookies as $cookieLine) { $parts = array_map('trim', explode(';', $cookieLine)); $kv = array_shift($parts); // name=value $attrs = []; foreach ($parts as $p) { if (stripos($p,'Domain=') === 0) { $attrs[] = 'Domain=' . $proxyHost; // força domínio do seu site } elseif (stripos($p,'Path=') === 0) { $attrs[] = 'Path=' . ($proxyBasePath ?: '/'); // garante path do proxy } else { $attrs[] = $p; } } if (!preg_grep('/^Path=/i', $attrs)) { $attrs[] = 'Path=' . ($proxyBasePath ?: '/'); } header('Set-Cookie: ' . $kv . '; ' . implode('; ', $attrs), false); } } /* ========================= MAIN ========================= */ if (empty($TARGET_ORIGIN)) { http_response_code(500); echo 'TARGET_ORIGIN não configurado.'; exit; } $path = getRequestedPath($PROXY_BASE_PATH, $DEFAULT_PATH); $targetUrl = buildTargetUrl($TARGET_ORIGIN, $path); if (!isAllowedUrl($targetUrl, $ALLOW_LIST)) { http_response_code(403); header('Content-Type: text/plain; charset=utf-8'); echo "Bloqueado: domínio não permitido."; exit; } [$status, $respHeaders, $body, $contentType, $setCookies] = fetchFromTarget($targetUrl); http_response_code($status); foreach (['Content-Language','Last-Modified','ETag','Cache-Control','Expires'] as $h) { if (isset($respHeaders[$h])) header($h . ': ' . $respHeaders[$h], true); } header('Content-Type: ' . $contentType); // cookies de sessão reescritos para seu domínio $proxyHost = $_SERVER['HTTP_HOST'] ?? ''; relaySetCookies($setCookies, $proxyHost, $PROXY_BASE_PATH); // auto-refresh (?refresh=60; 0 desliga) $refresh = isset($_GET['refresh']) ? max(0, (int)$_GET['refresh']) : 60; if (stripos($contentType, 'text/html') !== false) { $html = rewriteHtml($body, $TARGET_ORIGIN, $path, $PROXY_BASE_PATH); $html = injectRefresh($html, $refresh); echo $html; } else { echo $body; // css/js/img/pdf... }