forked from softius/php-cross-domain-proxy
-
Notifications
You must be signed in to change notification settings - Fork 12
/
proxy.php
185 lines (140 loc) · 3.75 KB
/
proxy.php
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
<?php
/**
* @param whitelist
* @param curl_opts
* @param zlib
*/
// Get normalized headers and such
$headers = array_change_key_case(getallheaders());
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$url = $headers['x-proxy-url'] ?? null;
$cookie = $headers['x-proxy-cookie'] ?? null;
// Check that we have a URL
if( ! $url)
failure(400, "X-Proxy-Url header missing");
// Check that the URL looks like an absolute URL
if( ! parse_url($url, PHP_URL_SCHEME))
failure(400, "Not an absolute URL: $url");
// Check referer hostname
if( ! parse_url($headers['referer'] ?? null, PHP_URL_HOST) == $_SERVER['HTTP_HOST'])
failure(403, "Invalid referer");
// Check whitelist, if not empty
if( ! array_reduce($whitelist ?? [], 'is_bad', [$url, false]))
failure(403, "Not whitelisted: $url");
// Remove ignored headers
$ignore = [
'cookie',
'content-length',
'host',
'x-proxy-url',
'x-proxy-cookie',
];
$headers = array_diff_key($headers, array_flip($ignore));
// Set proxied cookie if we got one
if($cookie)
$headers['Cookie'] = $cookie;
// Format headers for curl
foreach($headers as $key => &$value)
$value = ucwords($key, '-').": $value";
// Init curl
$curl = curl_init();
$maxredirs = $opts[CURLOPT_MAXREDIRS] ?? 20;
do
{
// Set options
curl_setopt_array($curl,
[
CURLOPT_URL => $url,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_HEADER => true,
]
+ ($curl_opts ?? []) +
[
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => $maxredirs,
]);
// Method specific options
switch($method)
{
case 'HEAD':
curl_setopt($curl, CURLOPT_NOBODY, true);
break;
case 'GET':
break;
case 'PUT':
case 'POST':
case 'DELETE':
default:
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($curl, CURLOPT_POSTFIELDS, file_get_contents('php://input'));
break;
}
// Perform request
ob_start();
curl_exec($curl);
$out = ob_get_clean();
// Light error handling
if(curl_errno($curl))
switch(curl_errno($curl))
{
// Connect timeout => Service Unavailable
case 7:
failure(503, $curl);
// Operation timeout => Gateway Timeout
case 28:
failure(504, $curl);
// Other errors => Service Unavailable
default:
failure(503, $curl);
}
// HACK: Workaround if not following, which happened once...
$url = curl_getinfo($curl, CURLINFO_REDIRECT_URL);
}
while($url and --$maxredirs > 0);
// Get curl info and close handler
$info = curl_getinfo($curl);
curl_close($curl);
// Remove any existing headers
header_remove();
// Use zlib, if acceptable
ini_set('zlib.output_compression', $zlib ?? 'On');
// Get content and headers
$content = substr($out, $info['header_size']);
$headers = substr($out, 0, $info['header_size']);
// Rename Set-Cookie header
$headers = preg_replace('/^Set-Cookie:/im', 'X-Proxy-Set-Cookie:', $headers);
// Output headers
foreach(explode("\r\n", $headers) as $h)
// HACK: Prevent chunked encoding issues (Issue #1)
if( ! preg_match('/^Transfer-Encoding:/i', $h))
header($h, false);
// HACK: Prevent gzip issue (Issue #1)
header('Content-Length: '.strlen($content), true);
// Output content
echo $content;
function failure(int $status, $text)
{
if(is_resource($text))
$text = sprintf('[%s] %s', curl_errno($text), curl_error($text));
http_response_code($status);
header("X-Proxy-Curl-Error: $text");
exit($text);
}
function is_bad($carry, array $rule): bool
{
static $url;
if(is_array($carry))
{
$url = parse_url($carry[0]);
$url['raw'] = $carry[0];
$carry = $carry[1];
}
// Equals full URL
if(isset($rule[0]))
return $carry or $url['raw'] == $rule[0];
// Regex matches URL
if(isset($rule['regex']))
return $carry or preg_match($rule['regex'], $url['raw']);
// Components in rule matches same components in URL
return $carry or $rule == array_intersect_key($url, $rule);
}