Changeset View
Changeset View
Standalone View
Standalone View
src/applications/config/check/PhabricatorWebServerSetupCheck.php
| Show First 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | protected function executeChecks() { | ||||
| $self_future = id(new HTTPSFuture($base_uri)) | $self_future = id(new HTTPSFuture($base_uri)) | ||||
| ->addHeader('X-Phabricator-SelfCheck', 1) | ->addHeader('X-Phabricator-SelfCheck', 1) | ||||
| ->addHeader('Accept-Encoding', 'gzip') | ->addHeader('Accept-Encoding', 'gzip') | ||||
| ->setHTTPBasicAuthCredentials( | ->setHTTPBasicAuthCredentials( | ||||
| $expect_user, | $expect_user, | ||||
| new PhutilOpaqueEnvelope($expect_pass)) | new PhutilOpaqueEnvelope($expect_pass)) | ||||
| ->setTimeout(5); | ->setTimeout(5); | ||||
| if (AphrontRequestStream::supportsGzip()) { | |||||
| $gzip_uncompressed = str_repeat('Quack! ', 128); | |||||
| $gzip_compressed = gzencode($gzip_uncompressed); | |||||
| $gzip_future = id(new HTTPSFuture($base_uri)) | |||||
| ->addHeader('X-Phabricator-SelfCheck', 1) | |||||
| ->addHeader('Content-Encoding', 'gzip') | |||||
| ->setTimeout(5) | |||||
| ->setData($gzip_compressed); | |||||
| } else { | |||||
| $gzip_future = null; | |||||
| } | |||||
| // Make a request to the metadata service available on EC2 instances, | // Make a request to the metadata service available on EC2 instances, | ||||
| // to test if we're running on a T2 instance in AWS so we can warn that | // to test if we're running on a T2 instance in AWS so we can warn that | ||||
| // this is a bad idea. Outside of AWS, this request will just fail. | // this is a bad idea. Outside of AWS, this request will just fail. | ||||
| $ec2_uri = 'http://169.254.169.254/latest/meta-data/instance-type'; | $ec2_uri = 'http://169.254.169.254/latest/meta-data/instance-type'; | ||||
| $ec2_future = id(new HTTPSFuture($ec2_uri)) | $ec2_future = id(new HTTPSFuture($ec2_uri)) | ||||
| ->setTimeout(1); | ->setTimeout(1); | ||||
| $futures = array( | $futures = array( | ||||
| $self_future, | $self_future, | ||||
| $ec2_future, | $ec2_future, | ||||
| ); | ); | ||||
| if ($gzip_future) { | |||||
| $futures[] = $gzip_future; | |||||
| } | |||||
| $futures = new FutureIterator($futures); | $futures = new FutureIterator($futures); | ||||
| foreach ($futures as $future) { | 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 '. | 'Phabricator 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 '. | 'Phabricator regularly needs CPU, and these instances are often '. | ||||
| ▲ Show 20 Lines • Show All 176 Lines • ▼ Show 20 Lines | if (($expect_key !== $actual_key) || ($expect_value !== $actual_value)) { | ||||
| $this->newIssue('webserver.parameters') | $this->newIssue('webserver.parameters') | ||||
| ->setName(pht('HTTP Parameters Not Transmitting')) | ->setName(pht('HTTP Parameters Not Transmitting')) | ||||
| ->setSummary( | ->setSummary( | ||||
| pht('Your webserver is not handling GET parameters properly.')) | pht('Your webserver is not handling GET parameters properly.')) | ||||
| ->setMessage($message); | ->setMessage($message); | ||||
| } | } | ||||
| if ($gzip_future) { | |||||
| $this->checkGzipResponse( | |||||
| $gzip_future, | |||||
| $gzip_uncompressed, | |||||
| $gzip_compressed); | |||||
| } | |||||
| } | |||||
| private function checkGzipResponse( | |||||
| Future $future, | |||||
| $uncompressed, | |||||
| $compressed) { | |||||
| try { | |||||
| list($body, $headers) = $future->resolvex(); | |||||
| } catch (Exception $ex) { | |||||
| return; | |||||
| } | |||||
| try { | |||||
| $structure = phutil_json_decode(trim($body)); | |||||
| } catch (Exception $ex) { | |||||
| return; | |||||
| } | |||||
| $raw_body = idx($structure, 'raw.base64'); | |||||
| $raw_body = base64_decode($raw_body); | |||||
| // The server received the exact compressed bytes we expected it to, so | |||||
| // everything is working great. | |||||
| if ($raw_body === $compressed) { | |||||
| return; | |||||
| } | |||||
| // If the server received a prefix of the raw uncompressed string, it | |||||
| // is almost certainly configured to decompress responses inline. Guide | |||||
| // users to this problem narrowly. | |||||
| // Otherwise, something is wrong but we don't have much of a clue what. | |||||
| $message = array(); | |||||
| $message[] = pht( | |||||
| 'Phabricator sent itself a test request that was compressed with '. | |||||
| '"Content-Encoding: gzip", but received different bytes than it '. | |||||
| 'sent.'); | |||||
| $prefix_len = min(strlen($raw_body), strlen($uncompressed)); | |||||
| if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) { | |||||
| $message[] = pht( | |||||
| 'The request body that the server received had already been '. | |||||
| 'decompressed. This strongly suggests your webserver is configured '. | |||||
| 'to decompress requests inline, before they reach PHP.'); | |||||
| $message[] = pht( | |||||
| 'If you are using Apache, your server may be configured with '. | |||||
| '"SetInputFilter DEFLATE". This directive destructively mangles '. | |||||
| 'requests and emits them with "Content-Length" and '. | |||||
| '"Content-Encoding" headers that no longer match the data in the '. | |||||
| 'request body.'); | |||||
| } else { | |||||
| $message[] = pht( | |||||
| 'This suggests your webserver is configured to decompress or mangle '. | |||||
| 'compressed requests.'); | |||||
| $message[] = pht( | |||||
| 'The request body Phabricator sent began:'); | |||||
| $message[] = $this->snipBytes($compressed); | |||||
| $message[] = pht( | |||||
| 'The request body Phabricator received began:'); | |||||
| $message[] = $this->snipBytes($raw_body); | |||||
| } | |||||
| $message[] = pht( | |||||
| 'Identify the component in your webserver configuration which is '. | |||||
| 'decompressing or mangling requests and disable it. Phabricator '. | |||||
| 'will not work properly until you do.'); | |||||
| $message = phutil_implode_html("\n\n", $message); | |||||
| $this->newIssue('webserver.accept-gzip') | |||||
| ->setName(pht('Compressed Requests Not Received Properly')) | |||||
| ->setSummary( | |||||
| pht( | |||||
| 'Your webserver is not handling compressed request bodies '. | |||||
| 'properly.')) | |||||
| ->setMessage($message); | |||||
| } | |||||
| private function snipBytes($raw) { | |||||
| if (!strlen($raw)) { | |||||
| $display = pht('<empty>'); | |||||
| } else { | |||||
| $snip = substr($raw, 0, 24); | |||||
| $display = phutil_loggable_string($snip); | |||||
| if (strlen($snip) < strlen($raw)) { | |||||
| $display .= '...'; | |||||
| } | |||||
| } | |||||
| return phutil_tag('tt', array(), $display); | |||||
| } | } | ||||
| } | } | ||||