File size: 5,082 Bytes
21b190a
96401e0
21b190a
96401e0
21b190a
 
 
 
 
 
96401e0
21b190a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96401e0
 
 
 
 
 
 
21b190a
 
96401e0
21b190a
 
 
96401e0
 
 
 
 
 
 
 
21b190a
 
96401e0
 
21b190a
 
96401e0
 
 
 
 
 
 
 
 
 
 
 
21b190a
 
 
 
 
 
 
96401e0
21b190a
96401e0
 
21b190a
 
96401e0
21b190a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96401e0
21b190a
 
 
96401e0
21b190a
 
96401e0
21b190a
 
96401e0
21b190a
 
 
 
 
96401e0
 
 
21b190a
 
 
 
 
 
 
96401e0
21b190a
 
 
96401e0
 
 
 
 
 
 
 
 
21b190a
 
 
 
96401e0
21b190a
 
96401e0
21b190a
 
 
 
 
 
 
96401e0
21b190a
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?php
// By: Colime – PHP reverse proxy, tuned for Google

// Root page
if ($_SERVER['REQUEST_URI'] === '/' || $_SERVER['REQUEST_URI'] === '') {
    header('Content-Type: text/html; charset=utf-8');
    echo '<p>about:blank</p>';
    exit;
}

// CORS preflight
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(204);
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS');
    header('Access-Control-Allow-Headers: *');
    header('Access-Control-Max-Age: 86400');
    exit;
}

// Build target URL from path + query string
$target = substr($_SERVER['REQUEST_URI'], 1);          // strip leading /
$query  = $_SERVER['QUERY_STRING'];
if ($query !== '') {
    $target .= '?' . $query;
}
if (!preg_match('#^https?://#i', $target)) {
    $target = 'http://' . $target;
}

$parts = parse_url($target);
if (!$parts || !isset($parts['host'])) {
    http_response_code(400);
    echo 'Invalid target URL provided.';
    exit;
}

// Collect and clean request headers
$requestHeaders = getallheaders();
$forwardHeaders = [];

// Hop-by-hop headers that should NOT be forwarded (both request and response)
$hopByHop = [
    'connection', 'keep-alive', 'proxy-authenticate',
    'proxy-authorization', 'te', 'trailer', 'transfer-encoding',
    'upgrade'
];

foreach ($requestHeaders as $name => $value) {
    $lower = strtolower($name);
    // Drop Cloudflare headers, real IP, and original Host
    if (strpos($lower, 'cf-') === 0 || $lower === 'x-real-ip' || $lower === 'host') {
        continue;
    }
    // Don't forward hop-by-hop request headers
    if (in_array($lower, $hopByHop)) {
        continue;
    }
    // Accept-Encoding is handled by cURL automatically; do not forward it
    if ($lower === 'accept-encoding') {
        continue;
    }
    $forwardHeaders[] = "$name: $value";
}

// Set the target host
$forwardHeaders[] = 'Host: ' . $parts['host'];

// Fallback User-Agent (only if client didn't send one)
$hasUserAgent = false;
foreach ($forwardHeaders as $h) {
    if (stripos($h, 'User-Agent:') === 0) {
        $hasUserAgent = true;
        break;
    }
}
if (!$hasUserAgent) {
    $forwardHeaders[] = 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36';
}

// cURL request
$ch = curl_init($target);
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST  => $_SERVER['REQUEST_METHOD'],
    CURLOPT_HTTPHEADER     => $forwardHeaders,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HEADER         => true,       // include headers in output
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_ENCODING       => '',         // accept any compression, decompress automatically
    CURLOPT_SSL_VERIFYPEER => true,       // keep enabled (HF has CA certs)
]);

// Forward request body (if any)
$body = file_get_contents('php://input');
if ($body !== '' && $body !== false) {
    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}

$response = curl_exec($ch);
$error    = curl_error($ch);
$info     = curl_getinfo($ch);
curl_close($ch);

if ($error) {
    http_response_code(502);
    echo 'Upstream server fetch failed. Please check the URL and network.';
    exit;
}

// Split headers and body
$headerSize      = $info['header_size'];
$responseHeaders = substr($response, 0, $headerSize);
$responseBody    = substr($response, $headerSize);

// Parse incoming response headers
$headersArr = explode("\r\n", $responseHeaders);
$statusCode = 200;
$outHeaders = []; // name -> value pairs (duplicates allowed, but we'll filter some)

foreach ($headersArr as $line) {
    // Status line
    if (stripos($line, 'HTTP/') === 0) {
        preg_match('/\d{3}/', $line, $m);
        $statusCode = (int)($m[0] ?? 200);
        continue;
    }
    if (trim($line) === '') {
        continue;
    }

    $colon = strpos($line, ':');
    if ($colon !== false) {
        $name  = substr($line, 0, $colon);
        $value = trim(substr($line, $colon + 1));
        $lower = strtolower($name);

        // Remove security headers that block iframes
        if ($lower === 'content-security-policy' || $lower === 'x-frame-options') {
            continue;
        }
        // Remove hop-by-hop response headers
        if (in_array($lower, $hopByHop)) {
            continue;
        }
        // cURL already decompressed the body – remove Content-Encoding
        if ($lower === 'content-encoding') {
            continue;
        }

        $outHeaders[] = [$name, $value];
    }
}

// --- Send final response ---
http_response_code($statusCode);

// Always add CORS and no-cache headers
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: *');
header('Access-Control-Allow-Headers: *');
header('Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate');
header('Pragma: no-cache');
header('Expires: 0');

// Forward upstream headers (except those we filtered out)
foreach ($outHeaders as $h) {
    header($h[0] . ': ' . $h[1]);
}

echo $responseBody;