Page MenuHomePhabricator

D21116.diff
No OneTemporary

D21116.diff

diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php
--- a/src/aphront/configuration/AphrontApplicationConfiguration.php
+++ b/src/aphront/configuration/AphrontApplicationConfiguration.php
@@ -771,12 +771,21 @@
);
}
+ $raw_input = @file_get_contents('php://input');
+ if ($raw_input !== false) {
+ $base64_input = base64_encode($raw_input);
+ } else {
+ $base64_input = null;
+ }
+
$result = array(
'path' => $path,
'params' => $params,
'user' => idx($_SERVER, 'PHP_AUTH_USER'),
'pass' => idx($_SERVER, 'PHP_AUTH_PW'),
+ 'raw.base64' => $base64_input,
+
// This just makes sure that the response compresses well, so reasonable
// algorithms should want to gzip or deflate it.
'filler' => str_repeat('Q', 1024 * 16),
diff --git a/src/aphront/requeststream/AphrontRequestStream.php b/src/aphront/requeststream/AphrontRequestStream.php
--- a/src/aphront/requeststream/AphrontRequestStream.php
+++ b/src/aphront/requeststream/AphrontRequestStream.php
@@ -89,4 +89,24 @@
return $stream;
}
+ public static function supportsGzip() {
+ if (!function_exists('gzencode') || !function_exists('gzdecode')) {
+ return false;
+ }
+
+ $has_zlib = false;
+
+ // NOTE: At least locally, this returns "zlib.*", which is not terribly
+ // reassuring. We care about "zlib.inflate".
+
+ $filters = stream_get_filters();
+ foreach ($filters as $filter) {
+ if (preg_match('/^zlib\\./', $filter)) {
+ $has_zlib = true;
+ }
+ }
+
+ return $has_zlib;
+ }
+
}
diff --git a/src/applications/config/check/PhabricatorWebServerSetupCheck.php b/src/applications/config/check/PhabricatorWebServerSetupCheck.php
--- a/src/applications/config/check/PhabricatorWebServerSetupCheck.php
+++ b/src/applications/config/check/PhabricatorWebServerSetupCheck.php
@@ -50,6 +50,20 @@
new PhutilOpaqueEnvelope($expect_pass))
->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,
// 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.
@@ -61,12 +75,16 @@
$self_future,
$ec2_future,
);
+
+ if ($gzip_future) {
+ $futures[] = $gzip_future;
+ }
+
$futures = new FutureIterator($futures);
foreach ($futures as $future) {
// Just resolve the futures here.
}
-
try {
list($body) = $ec2_future->resolvex();
$body = trim($body);
@@ -259,6 +277,107 @@
->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);
}
}

File Metadata

Mime Type
text/plain
Expires
Thu, May 9, 7:58 PM (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6276177
Default Alt Text
D21116.diff (6 KB)

Event Timeline