Changeset View
Changeset View
Standalone View
Standalone View
src/applications/config/check/PhabricatorWebServerSetupCheck.php
Show All 10 Lines | protected function executeChecks() { | ||||
// are entirely reliable in practice. | // are entirely reliable in practice. | ||||
if (isset($_SERVER['HTTP_X_MOD_PAGESPEED']) || | if (isset($_SERVER['HTTP_X_MOD_PAGESPEED']) || | ||||
isset($_SERVER['HTTP_X_PAGE_SPEED'])) { | isset($_SERVER['HTTP_X_PAGE_SPEED'])) { | ||||
$this->newIssue('webserver.pagespeed') | $this->newIssue('webserver.pagespeed') | ||||
->setName(pht('Disable Pagespeed')) | ->setName(pht('Disable Pagespeed')) | ||||
->setSummary(pht('Pagespeed is enabled, but should be disabled.')) | ->setSummary(pht('Pagespeed is enabled, but should be disabled.')) | ||||
->setMessage( | ->setMessage( | ||||
pht( | pht( | ||||
'Phabricator received an "X-Mod-Pagespeed" or "X-Page-Speed" '. | 'This server received an "X-Mod-Pagespeed" or "X-Page-Speed" '. | ||||
'HTTP header on this request, which indicates that you have '. | 'HTTP header on this request, which indicates that you have '. | ||||
'enabled "mod_pagespeed" on this server. This module is not '. | 'enabled "mod_pagespeed" on this server. This module is not '. | ||||
'compatible with Phabricator. You should disable it.')); | 'compatible with this software. You should disable the module.')); | ||||
} | } | ||||
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); | $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri'); | ||||
if (!strlen($base_uri)) { | if (!strlen($base_uri)) { | ||||
// If `phabricator.base-uri` is not set then we can't really do | // If `phabricator.base-uri` is not set then we can't really do | ||||
// anything. | // anything. | ||||
return; | return; | ||||
} | } | ||||
$expect_user = 'alincoln'; | $expect_user = 'alincoln'; | ||||
$expect_pass = 'hunter2'; | $expect_pass = 'hunter2'; | ||||
$send_path = '/test-%252A/'; | $send_path = '/test-%252A/'; | ||||
$expect_path = '/test-%2A/'; | $expect_path = '/test-%2A/'; | ||||
$expect_key = 'duck-sound'; | $expect_key = 'duck-sound'; | ||||
$expect_value = 'quack'; | $expect_value = 'quack'; | ||||
$base_uri = id(new PhutilURI($base_uri)) | $base_uri = id(new PhutilURI($base_uri)) | ||||
->setPath($send_path) | ->setPath($send_path) | ||||
->replaceQueryParam($expect_key, $expect_value); | ->replaceQueryParam($expect_key, $expect_value); | ||||
$self_future = id(new HTTPSFuture($base_uri)) | $self_future = id(new HTTPSFuture($base_uri)) | ||||
->addHeader('X-Phabricator-SelfCheck', 1) | ->addHeader('X-Setup-SelfCheck', 1) | ||||
->addHeader('Accept-Encoding', 'gzip') | ->addHeader('Accept-Encoding', 'gzip') | ||||
->setDisableContentDecoding(true) | ->setDisableContentDecoding(true) | ||||
->setHTTPBasicAuthCredentials( | ->setHTTPBasicAuthCredentials( | ||||
$expect_user, | $expect_user, | ||||
new PhutilOpaqueEnvelope($expect_pass)) | new PhutilOpaqueEnvelope($expect_pass)) | ||||
->setTimeout(5); | ->setTimeout(5); | ||||
if (AphrontRequestStream::supportsGzip()) { | if (AphrontRequestStream::supportsGzip()) { | ||||
$gzip_uncompressed = str_repeat('Quack! ', 128); | $gzip_uncompressed = str_repeat('Quack! ', 128); | ||||
$gzip_compressed = gzencode($gzip_uncompressed); | $gzip_compressed = gzencode($gzip_uncompressed); | ||||
$gzip_future = id(new HTTPSFuture($base_uri)) | $gzip_future = id(new HTTPSFuture($base_uri)) | ||||
->addHeader('X-Phabricator-SelfCheck', 1) | ->addHeader('X-Setup-SelfCheck', 1) | ||||
->addHeader('Content-Encoding', 'gzip') | ->addHeader('Content-Encoding', 'gzip') | ||||
->setTimeout(5) | ->setTimeout(5) | ||||
->setData($gzip_compressed); | ->setData($gzip_compressed); | ||||
} else { | } else { | ||||
$gzip_future = null; | $gzip_future = null; | ||||
} | } | ||||
Show All 18 Lines | foreach ($futures as $future) { | ||||
// Just resolve the futures here. | // Just resolve the futures here. | ||||
} | } | ||||
try { | try { | ||||
list($body) = $ec2_future->resolvex(); | list($body) = $ec2_future->resolvex(); | ||||
$body = trim($body); | $body = trim($body); | ||||
if (preg_match('/^t2/', $body)) { | if (preg_match('/^t2/', $body)) { | ||||
$message = pht( | $message = pht( | ||||
'Phabricator appears to be installed on a very small EC2 instance '. | 'This software appears to be installed on a very small EC2 instance '. | ||||
'(of class "%s") with burstable CPU. This is strongly discouraged. '. | '(of class "%s") with burstable CPU. This is strongly discouraged. '. | ||||
'Phabricator regularly needs CPU, and these instances are often '. | 'This software regularly needs CPU, and these instances are often '. | ||||
'choked to death by CPU throttling. Use an instance with a normal '. | 'choked to death by CPU throttling. Use an instance with a normal '. | ||||
'CPU instead.', | 'CPU instead.', | ||||
$body); | $body); | ||||
$this->newIssue('ec2.burstable') | $this->newIssue('ec2.burstable') | ||||
->setName(pht('Installed on Burstable CPU Instance')) | ->setName(pht('Installed on Burstable CPU Instance')) | ||||
->setSummary( | ->setSummary( | ||||
pht( | pht( | ||||
'Do not install Phabricator on an instance class with '. | 'Do not install this software on an instance class with '. | ||||
'burstable CPU.')) | 'burstable CPU.')) | ||||
->setMessage($message); | ->setMessage($message); | ||||
} | } | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
// If this fails, just continue. We're probably not running in EC2. | // If this fails, just continue. We're probably not running in EC2. | ||||
} | } | ||||
try { | try { | ||||
list($body, $headers) = $self_future->resolvex(); | list($body, $headers) = $self_future->resolvex(); | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
// If this fails for whatever reason, just ignore it. Hopefully, the | // If this fails for whatever reason, just ignore it. Hopefully, the | ||||
// error is obvious and the user can correct it on their own, but we | // error is obvious and the user can correct it on their own, but we | ||||
// can't do much to offer diagnostic advice. | // can't do much to offer diagnostic advice. | ||||
return; | return; | ||||
} | } | ||||
if (BaseHTTPFuture::getHeader($headers, 'Content-Encoding') != 'gzip') { | if (BaseHTTPFuture::getHeader($headers, 'Content-Encoding') != 'gzip') { | ||||
$message = pht( | $message = pht( | ||||
'Phabricator sent itself a request with "Accept-Encoding: gzip", '. | 'This software sent itself a request with "Accept-Encoding: gzip", '. | ||||
'but received an uncompressed response.'. | 'but received an uncompressed response.'. | ||||
"\n\n". | "\n\n". | ||||
'This may indicate that your webserver is not configured to '. | 'This may indicate that your webserver is not configured to '. | ||||
'compress responses. If so, you should enable compression. '. | 'compress responses. If so, you should enable compression. '. | ||||
'Compression can dramatically improve performance, especially '. | 'Compression can dramatically improve performance, especially '. | ||||
'for clients with less bandwidth.'); | 'for clients with less bandwidth.'); | ||||
$this->newIssue('webserver.gzip') | $this->newIssue('webserver.gzip') | ||||
Show All 25 Lines | protected function executeChecks() { | ||||
if (!$structure || $extra_whitespace) { | if (!$structure || $extra_whitespace) { | ||||
if (!$structure) { | if (!$structure) { | ||||
$short = id(new PhutilUTF8StringTruncator()) | $short = id(new PhutilUTF8StringTruncator()) | ||||
->setMaximumGlyphs(1024) | ->setMaximumGlyphs(1024) | ||||
->truncateString($body); | ->truncateString($body); | ||||
$message = pht( | $message = pht( | ||||
'Phabricator sent itself a test request with the '. | 'This software sent itself a test request with the '. | ||||
'"X-Phabricator-SelfCheck" header and expected to get a valid JSON '. | '"X-Setup-SelfCheck" header and expected to get a valid JSON '. | ||||
'response back. Instead, the response begins:'. | 'response back. Instead, the response begins:'. | ||||
"\n\n". | "\n\n". | ||||
'%s'. | '%s'. | ||||
"\n\n". | "\n\n". | ||||
'Something is misconfigured or otherwise mangling responses.', | 'Something is misconfigured or otherwise mangling responses.', | ||||
phutil_tag('pre', array(), $short)); | phutil_tag('pre', array(), $short)); | ||||
} else { | } else { | ||||
$message = pht( | $message = pht( | ||||
'Phabricator sent itself a test request and expected to get a bare '. | 'This software sent itself a test request and expected to get a '. | ||||
'JSON response back. It received a JSON response, but the response '. | 'bare JSON response back. It received a JSON response, but the '. | ||||
'had extra whitespace at the beginning or end.'. | 'response had extra whitespace at the beginning or end.'. | ||||
"\n\n". | "\n\n". | ||||
'This usually means you have edited a file and left whitespace '. | 'This usually means you have edited a file and left whitespace '. | ||||
'characters before the opening %s tag, or after a closing %s tag. '. | 'characters before the opening %s tag, or after a closing %s tag. '. | ||||
'Remove any leading whitespace, and prefer to omit closing tags.', | 'Remove any leading whitespace, and prefer to omit closing tags.', | ||||
phutil_tag('tt', array(), '<?php'), | phutil_tag('tt', array(), '<?php'), | ||||
phutil_tag('tt', array(), '?>')); | phutil_tag('tt', array(), '?>')); | ||||
} | } | ||||
$this->newIssue('webserver.mangle') | $this->newIssue('webserver.mangle') | ||||
->setName(pht('Mangled Webserver Response')) | ->setName(pht('Mangled Webserver Response')) | ||||
->setSummary(pht('Your webserver produced an unexpected response.')) | ->setSummary(pht('Your webserver produced an unexpected response.')) | ||||
->setMessage($message); | ->setMessage($message); | ||||
// We can't run the other checks if we could not decode the response. | // We can't run the other checks if we could not decode the response. | ||||
if (!$structure) { | if (!$structure) { | ||||
return; | return; | ||||
} | } | ||||
} | } | ||||
$actual_user = idx($structure, 'user'); | $actual_user = idx($structure, 'user'); | ||||
$actual_pass = idx($structure, 'pass'); | $actual_pass = idx($structure, 'pass'); | ||||
if (($expect_user != $actual_user) || ($actual_pass != $expect_pass)) { | if (($expect_user != $actual_user) || ($actual_pass != $expect_pass)) { | ||||
$message = pht( | $message = pht( | ||||
'Phabricator sent itself a test request with an "Authorization" HTTP '. | 'This software sent itself a test request with an "Authorization" '. | ||||
'header, and expected those credentials to be transmitted. However, '. | 'HTTP header, and expected those credentials to be transmitted. '. | ||||
'they were absent or incorrect when received. Phabricator sent '. | 'However, they were absent or incorrect when received. This '. | ||||
'username "%s" with password "%s"; received username "%s" and '. | 'software sent username "%s" with password "%s"; received '. | ||||
'password "%s".'. | 'username "%s" and password "%s".'. | ||||
"\n\n". | "\n\n". | ||||
'Your webserver may not be configured to forward HTTP basic '. | 'Your webserver may not be configured to forward HTTP basic '. | ||||
'authentication. If you plan to use basic authentication (for '. | 'authentication. If you plan to use basic authentication (for '. | ||||
'example, to access repositories) you should reconfigure it.', | 'example, to access repositories) you should reconfigure it.', | ||||
$expect_user, | $expect_user, | ||||
$expect_pass, | $expect_pass, | ||||
$actual_user, | $actual_user, | ||||
$actual_pass); | $actual_pass); | ||||
$this->newIssue('webserver.basic-auth') | $this->newIssue('webserver.basic-auth') | ||||
->setName(pht('HTTP Basic Auth Not Configured')) | ->setName(pht('HTTP Basic Auth Not Configured')) | ||||
->setSummary(pht('Your webserver is not forwarding credentials.')) | ->setSummary(pht('Your webserver is not forwarding credentials.')) | ||||
->setMessage($message); | ->setMessage($message); | ||||
} | } | ||||
$actual_path = idx($structure, 'path'); | $actual_path = idx($structure, 'path'); | ||||
if ($expect_path != $actual_path) { | if ($expect_path != $actual_path) { | ||||
$message = pht( | $message = pht( | ||||
'Phabricator sent itself a test request with an unusual path, to '. | 'This software sent itself a test request with an unusual path, to '. | ||||
'test if your webserver is rewriting paths correctly. The path was '. | 'test if your webserver is rewriting paths correctly. The path was '. | ||||
'not transmitted correctly.'. | 'not transmitted correctly.'. | ||||
"\n\n". | "\n\n". | ||||
'Phabricator sent a request to path "%s", and expected the webserver '. | 'This software sent a request to path "%s", and expected the '. | ||||
'to decode and rewrite that path so that it received a request for '. | 'webserver to decode and rewrite that path so that it received a '. | ||||
'"%s". However, it received a request for "%s" instead.'. | 'request for "%s". However, it received a request for "%s" instead.'. | ||||
"\n\n". | "\n\n". | ||||
'Verify that your rewrite rules are configured correctly, following '. | 'Verify that your rewrite rules are configured correctly, following '. | ||||
'the instructions in the documentation. If path encoding is not '. | 'the instructions in the documentation. If path encoding is not '. | ||||
'working properly you will be unable to access files with unusual '. | 'working properly you will be unable to access files with unusual '. | ||||
'names in repositories, among other issues.'. | 'names in repositories, among other issues.'. | ||||
"\n\n". | "\n\n". | ||||
'(This problem can be caused by a missing "B" in your RewriteRule.)', | '(This problem can be caused by a missing "B" in your RewriteRule.)', | ||||
$send_path, | $send_path, | ||||
Show All 13 Lines | foreach (idx($structure, 'params', array()) as $pair) { | ||||
$actual_key = idx($pair, 'name'); | $actual_key = idx($pair, 'name'); | ||||
$actual_value = idx($pair, 'value'); | $actual_value = idx($pair, 'value'); | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
if (($expect_key !== $actual_key) || ($expect_value !== $actual_value)) { | if (($expect_key !== $actual_key) || ($expect_value !== $actual_value)) { | ||||
$message = pht( | $message = pht( | ||||
'Phabricator sent itself a test request with an HTTP GET parameter, '. | 'This software sent itself a test request with an HTTP GET parameter, '. | ||||
'but the parameter was not transmitted. Sent "%s" with value "%s", '. | 'but the parameter was not transmitted. Sent "%s" with value "%s", '. | ||||
'got "%s" with value "%s".'. | 'got "%s" with value "%s".'. | ||||
"\n\n". | "\n\n". | ||||
'Your webserver is configured incorrectly and large parts of '. | 'Your webserver is configured incorrectly and large parts of '. | ||||
'Phabricator will not work until this issue is corrected.'. | 'this software will not work until this issue is corrected.'. | ||||
"\n\n". | "\n\n". | ||||
'(This problem can be caused by a missing "QSA" in your RewriteRule.)', | '(This problem can be caused by a missing "QSA" in your RewriteRule.)', | ||||
$expect_key, | $expect_key, | ||||
$expect_value, | $expect_value, | ||||
$actual_key, | $actual_key, | ||||
$actual_value); | $actual_value); | ||||
$this->newIssue('webserver.parameters') | $this->newIssue('webserver.parameters') | ||||
Show All 40 Lines | private function checkGzipResponse( | ||||
// If the server received a prefix of the raw uncompressed string, it | // If the server received a prefix of the raw uncompressed string, it | ||||
// is almost certainly configured to decompress responses inline. Guide | // is almost certainly configured to decompress responses inline. Guide | ||||
// users to this problem narrowly. | // users to this problem narrowly. | ||||
// Otherwise, something is wrong but we don't have much of a clue what. | // Otherwise, something is wrong but we don't have much of a clue what. | ||||
$message = array(); | $message = array(); | ||||
$message[] = pht( | $message[] = pht( | ||||
'Phabricator sent itself a test request that was compressed with '. | 'This software sent itself a test request that was compressed with '. | ||||
'"Content-Encoding: gzip", but received different bytes than it '. | '"Content-Encoding: gzip", but received different bytes than it '. | ||||
'sent.'); | 'sent.'); | ||||
$prefix_len = min(strlen($raw_body), strlen($uncompressed)); | $prefix_len = min(strlen($raw_body), strlen($uncompressed)); | ||||
if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) { | if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) { | ||||
$message[] = pht( | $message[] = pht( | ||||
'The request body that the server received had already been '. | 'The request body that the server received had already been '. | ||||
'decompressed. This strongly suggests your webserver is configured '. | 'decompressed. This strongly suggests your webserver is configured '. | ||||
'to decompress requests inline, before they reach PHP.'); | 'to decompress requests inline, before they reach PHP.'); | ||||
$message[] = pht( | $message[] = pht( | ||||
'If you are using Apache, your server may be configured with '. | 'If you are using Apache, your server may be configured with '. | ||||
'"SetInputFilter DEFLATE". This directive destructively mangles '. | '"SetInputFilter DEFLATE". This directive destructively mangles '. | ||||
'requests and emits them with "Content-Length" and '. | 'requests and emits them with "Content-Length" and '. | ||||
'"Content-Encoding" headers that no longer match the data in the '. | '"Content-Encoding" headers that no longer match the data in the '. | ||||
'request body.'); | 'request body.'); | ||||
} else { | } else { | ||||
$message[] = pht( | $message[] = pht( | ||||
'This suggests your webserver is configured to decompress or mangle '. | 'This suggests your webserver is configured to decompress or mangle '. | ||||
'compressed requests.'); | 'compressed requests.'); | ||||
$message[] = pht( | $message[] = pht( | ||||
'The request body Phabricator sent began:'); | 'The request body that was sent began:'); | ||||
$message[] = $this->snipBytes($compressed); | $message[] = $this->snipBytes($compressed); | ||||
$message[] = pht( | $message[] = pht( | ||||
'The request body Phabricator received began:'); | 'The request body that was received began:'); | ||||
$message[] = $this->snipBytes($raw_body); | $message[] = $this->snipBytes($raw_body); | ||||
} | } | ||||
$message[] = pht( | $message[] = pht( | ||||
'Identify the component in your webserver configuration which is '. | 'Identify the component in your webserver configuration which is '. | ||||
'decompressing or mangling requests and disable it. Phabricator '. | 'decompressing or mangling requests and disable it. This software '. | ||||
'will not work properly until you do.'); | 'will not work properly until you do.'); | ||||
$message = phutil_implode_html("\n\n", $message); | $message = phutil_implode_html("\n\n", $message); | ||||
$this->newIssue('webserver.accept-gzip') | $this->newIssue('webserver.accept-gzip') | ||||
->setName(pht('Compressed Requests Not Received Properly')) | ->setName(pht('Compressed Requests Not Received Properly')) | ||||
->setSummary( | ->setSummary( | ||||
pht( | pht( | ||||
Show All 21 Lines |