Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/config/check/PhabricatorWebServerSetupCheck.php
12262 views
1
<?php
2
3
final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
4
5
public function getDefaultGroup() {
6
return self::GROUP_OTHER;
7
}
8
9
protected function executeChecks() {
10
// The documentation says these headers exist, but it's not clear if they
11
// are entirely reliable in practice.
12
if (isset($_SERVER['HTTP_X_MOD_PAGESPEED']) ||
13
isset($_SERVER['HTTP_X_PAGE_SPEED'])) {
14
$this->newIssue('webserver.pagespeed')
15
->setName(pht('Disable Pagespeed'))
16
->setSummary(pht('Pagespeed is enabled, but should be disabled.'))
17
->setMessage(
18
pht(
19
'This server received an "X-Mod-Pagespeed" or "X-Page-Speed" '.
20
'HTTP header on this request, which indicates that you have '.
21
'enabled "mod_pagespeed" on this server. This module is not '.
22
'compatible with this software. You should disable the module.'));
23
}
24
25
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
26
if ($base_uri === null || !strlen($base_uri)) {
27
// If `phabricator.base-uri` is not set then we can't really do
28
// anything.
29
return;
30
}
31
32
$expect_user = 'alincoln';
33
$expect_pass = 'hunter2';
34
35
$send_path = '/test-%252A/';
36
$expect_path = '/test-%2A/';
37
38
$expect_key = 'duck-sound';
39
$expect_value = 'quack';
40
41
$base_uri = id(new PhutilURI($base_uri))
42
->setPath($send_path)
43
->replaceQueryParam($expect_key, $expect_value);
44
45
$self_future = id(new HTTPSFuture($base_uri))
46
->addHeader('X-Setup-SelfCheck', 1)
47
->addHeader('Accept-Encoding', 'gzip')
48
->setDisableContentDecoding(true)
49
->setHTTPBasicAuthCredentials(
50
$expect_user,
51
new PhutilOpaqueEnvelope($expect_pass))
52
->setTimeout(5);
53
54
if (AphrontRequestStream::supportsGzip()) {
55
$gzip_uncompressed = str_repeat('Quack! ', 128);
56
$gzip_compressed = gzencode($gzip_uncompressed);
57
58
$gzip_future = id(new HTTPSFuture($base_uri))
59
->addHeader('X-Setup-SelfCheck', 1)
60
->addHeader('Content-Encoding', 'gzip')
61
->setTimeout(5)
62
->setData($gzip_compressed);
63
64
} else {
65
$gzip_future = null;
66
}
67
68
// Make a request to the metadata service available on EC2 instances,
69
// to test if we're running on a T2 instance in AWS so we can warn that
70
// this is a bad idea. Outside of AWS, this request will just fail.
71
$ec2_uri = 'http://169.254.169.254/latest/meta-data/instance-type';
72
$ec2_future = id(new HTTPSFuture($ec2_uri))
73
->setTimeout(1);
74
75
$futures = array(
76
$self_future,
77
$ec2_future,
78
);
79
80
if ($gzip_future) {
81
$futures[] = $gzip_future;
82
}
83
84
$futures = new FutureIterator($futures);
85
foreach ($futures as $future) {
86
// Just resolve the futures here.
87
}
88
89
try {
90
list($body) = $ec2_future->resolvex();
91
$body = trim($body);
92
if (preg_match('/^t2/', $body)) {
93
$message = pht(
94
'This software appears to be installed on a very small EC2 instance '.
95
'(of class "%s") with burstable CPU. This is strongly discouraged. '.
96
'This software regularly needs CPU, and these instances are often '.
97
'choked to death by CPU throttling. Use an instance with a normal '.
98
'CPU instead.',
99
$body);
100
101
$this->newIssue('ec2.burstable')
102
->setName(pht('Installed on Burstable CPU Instance'))
103
->setSummary(
104
pht(
105
'Do not install this software on an instance class with '.
106
'burstable CPU.'))
107
->setMessage($message);
108
}
109
} catch (Exception $ex) {
110
// If this fails, just continue. We're probably not running in EC2.
111
}
112
113
try {
114
list($body, $headers) = $self_future->resolvex();
115
} catch (Exception $ex) {
116
// If this fails for whatever reason, just ignore it. Hopefully, the
117
// error is obvious and the user can correct it on their own, but we
118
// can't do much to offer diagnostic advice.
119
return;
120
}
121
122
if (BaseHTTPFuture::getHeader($headers, 'Content-Encoding') != 'gzip') {
123
$message = pht(
124
'This software sent itself a request with "Accept-Encoding: gzip", '.
125
'but received an uncompressed response.'.
126
"\n\n".
127
'This may indicate that your webserver is not configured to '.
128
'compress responses. If so, you should enable compression. '.
129
'Compression can dramatically improve performance, especially '.
130
'for clients with less bandwidth.');
131
132
$this->newIssue('webserver.gzip')
133
->setName(pht('GZip Compression May Not Be Enabled'))
134
->setSummary(pht('Your webserver may have compression disabled.'))
135
->setMessage($message);
136
} else {
137
if (function_exists('gzdecode')) {
138
$body = @gzdecode($body);
139
} else {
140
$body = null;
141
}
142
if (!$body) {
143
// For now, just bail if we can't decode the response.
144
// This might need to use the stronger magic in "AphrontRequestStream"
145
// to decode more reliably.
146
return;
147
}
148
}
149
150
$structure = null;
151
$extra_whitespace = ($body !== trim($body));
152
153
try {
154
$structure = phutil_json_decode(trim($body));
155
} catch (Exception $ex) {
156
// Ignore the exception, we only care if the decode worked or not.
157
}
158
159
if (!$structure || $extra_whitespace) {
160
if (!$structure) {
161
$short = id(new PhutilUTF8StringTruncator())
162
->setMaximumGlyphs(1024)
163
->truncateString($body);
164
165
$message = pht(
166
'This software sent itself a test request with the '.
167
'"X-Setup-SelfCheck" header and expected to get a valid JSON '.
168
'response back. Instead, the response begins:'.
169
"\n\n".
170
'%s'.
171
"\n\n".
172
'Something is misconfigured or otherwise mangling responses.',
173
phutil_tag('pre', array(), $short));
174
} else {
175
$message = pht(
176
'This software sent itself a test request and expected to get a '.
177
'bare JSON response back. It received a JSON response, but the '.
178
'response had extra whitespace at the beginning or end.'.
179
"\n\n".
180
'This usually means you have edited a file and left whitespace '.
181
'characters before the opening %s tag, or after a closing %s tag. '.
182
'Remove any leading whitespace, and prefer to omit closing tags.',
183
phutil_tag('tt', array(), '<?php'),
184
phutil_tag('tt', array(), '?>'));
185
}
186
187
$this->newIssue('webserver.mangle')
188
->setName(pht('Mangled Webserver Response'))
189
->setSummary(pht('Your webserver produced an unexpected response.'))
190
->setMessage($message);
191
192
// We can't run the other checks if we could not decode the response.
193
if (!$structure) {
194
return;
195
}
196
}
197
198
$actual_user = idx($structure, 'user');
199
$actual_pass = idx($structure, 'pass');
200
if (($expect_user != $actual_user) || ($actual_pass != $expect_pass)) {
201
$message = pht(
202
'This software sent itself a test request with an "Authorization" '.
203
'HTTP header, and expected those credentials to be transmitted. '.
204
'However, they were absent or incorrect when received. This '.
205
'software sent username "%s" with password "%s"; received '.
206
'username "%s" and password "%s".'.
207
"\n\n".
208
'Your webserver may not be configured to forward HTTP basic '.
209
'authentication. If you plan to use basic authentication (for '.
210
'example, to access repositories) you should reconfigure it.',
211
$expect_user,
212
$expect_pass,
213
$actual_user,
214
$actual_pass);
215
216
$this->newIssue('webserver.basic-auth')
217
->setName(pht('HTTP Basic Auth Not Configured'))
218
->setSummary(pht('Your webserver is not forwarding credentials.'))
219
->setMessage($message);
220
}
221
222
$actual_path = idx($structure, 'path');
223
if ($expect_path != $actual_path) {
224
$message = pht(
225
'This software sent itself a test request with an unusual path, to '.
226
'test if your webserver is rewriting paths correctly. The path was '.
227
'not transmitted correctly.'.
228
"\n\n".
229
'This software sent a request to path "%s", and expected the '.
230
'webserver to decode and rewrite that path so that it received a '.
231
'request for "%s". However, it received a request for "%s" instead.'.
232
"\n\n".
233
'Verify that your rewrite rules are configured correctly, following '.
234
'the instructions in the documentation. If path encoding is not '.
235
'working properly you will be unable to access files with unusual '.
236
'names in repositories, among other issues.'.
237
"\n\n".
238
'(This problem can be caused by a missing "B" in your RewriteRule.)',
239
$send_path,
240
$expect_path,
241
$actual_path);
242
243
$this->newIssue('webserver.rewrites')
244
->setName(pht('HTTP Path Rewriting Incorrect'))
245
->setSummary(pht('Your webserver is rewriting paths improperly.'))
246
->setMessage($message);
247
}
248
249
$actual_key = pht('<none>');
250
$actual_value = pht('<none>');
251
foreach (idx($structure, 'params', array()) as $pair) {
252
if (idx($pair, 'name') == $expect_key) {
253
$actual_key = idx($pair, 'name');
254
$actual_value = idx($pair, 'value');
255
break;
256
}
257
}
258
259
if (($expect_key !== $actual_key) || ($expect_value !== $actual_value)) {
260
$message = pht(
261
'This software sent itself a test request with an HTTP GET parameter, '.
262
'but the parameter was not transmitted. Sent "%s" with value "%s", '.
263
'got "%s" with value "%s".'.
264
"\n\n".
265
'Your webserver is configured incorrectly and large parts of '.
266
'this software will not work until this issue is corrected.'.
267
"\n\n".
268
'(This problem can be caused by a missing "QSA" in your RewriteRule.)',
269
$expect_key,
270
$expect_value,
271
$actual_key,
272
$actual_value);
273
274
$this->newIssue('webserver.parameters')
275
->setName(pht('HTTP Parameters Not Transmitting'))
276
->setSummary(
277
pht('Your webserver is not handling GET parameters properly.'))
278
->setMessage($message);
279
}
280
281
if ($gzip_future) {
282
$this->checkGzipResponse(
283
$gzip_future,
284
$gzip_uncompressed,
285
$gzip_compressed);
286
}
287
}
288
289
private function checkGzipResponse(
290
Future $future,
291
$uncompressed,
292
$compressed) {
293
294
try {
295
list($body, $headers) = $future->resolvex();
296
} catch (Exception $ex) {
297
return;
298
}
299
300
try {
301
$structure = phutil_json_decode(trim($body));
302
} catch (Exception $ex) {
303
return;
304
}
305
306
$raw_body = idx($structure, 'raw.base64');
307
$raw_body = @base64_decode($raw_body);
308
309
// The server received the exact compressed bytes we expected it to, so
310
// everything is working great.
311
if ($raw_body === $compressed) {
312
return;
313
}
314
315
// If the server received a prefix of the raw uncompressed string, it
316
// is almost certainly configured to decompress responses inline. Guide
317
// users to this problem narrowly.
318
319
// Otherwise, something is wrong but we don't have much of a clue what.
320
321
$message = array();
322
$message[] = pht(
323
'This software sent itself a test request that was compressed with '.
324
'"Content-Encoding: gzip", but received different bytes than it '.
325
'sent.');
326
327
$prefix_len = min(strlen($raw_body), strlen($uncompressed));
328
if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) {
329
$message[] = pht(
330
'The request body that the server received had already been '.
331
'decompressed. This strongly suggests your webserver is configured '.
332
'to decompress requests inline, before they reach PHP.');
333
$message[] = pht(
334
'If you are using Apache, your server may be configured with '.
335
'"SetInputFilter DEFLATE". This directive destructively mangles '.
336
'requests and emits them with "Content-Length" and '.
337
'"Content-Encoding" headers that no longer match the data in the '.
338
'request body.');
339
} else {
340
$message[] = pht(
341
'This suggests your webserver is configured to decompress or mangle '.
342
'compressed requests.');
343
344
$message[] = pht(
345
'The request body that was sent began:');
346
$message[] = $this->snipBytes($compressed);
347
348
$message[] = pht(
349
'The request body that was received began:');
350
$message[] = $this->snipBytes($raw_body);
351
}
352
353
$message[] = pht(
354
'Identify the component in your webserver configuration which is '.
355
'decompressing or mangling requests and disable it. This software '.
356
'will not work properly until you do.');
357
358
$message = phutil_implode_html("\n\n", $message);
359
360
$this->newIssue('webserver.accept-gzip')
361
->setName(pht('Compressed Requests Not Received Properly'))
362
->setSummary(
363
pht(
364
'Your webserver is not handling compressed request bodies '.
365
'properly.'))
366
->setMessage($message);
367
}
368
369
private function snipBytes($raw) {
370
if (!strlen($raw)) {
371
$display = pht('<empty>');
372
} else {
373
$snip = substr($raw, 0, 24);
374
$display = phutil_loggable_string($snip);
375
376
if (strlen($snip) < strlen($raw)) {
377
$display .= '...';
378
}
379
}
380
381
return phutil_tag('tt', array(), $display);
382
}
383
384
}
385
386