@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a setup warning to detect "SetInputFilter DEFLATE" and other "Content-Encoding" request mangling

Summary: Ref T13507. See that task for discussion.

Test Plan: Faked different response behaviors and hit both variations of this error.

Maniphest Tasks: T13507

Differential Revision: https://secure.phabricator.com/D21116

+149 -1
+9
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 771 771 ); 772 772 } 773 773 774 + $raw_input = @file_get_contents('php://input'); 775 + if ($raw_input !== false) { 776 + $base64_input = base64_encode($raw_input); 777 + } else { 778 + $base64_input = null; 779 + } 780 + 774 781 $result = array( 775 782 'path' => $path, 776 783 'params' => $params, 777 784 'user' => idx($_SERVER, 'PHP_AUTH_USER'), 778 785 'pass' => idx($_SERVER, 'PHP_AUTH_PW'), 786 + 787 + 'raw.base64' => $base64_input, 779 788 780 789 // This just makes sure that the response compresses well, so reasonable 781 790 // algorithms should want to gzip or deflate it.
+20
src/aphront/requeststream/AphrontRequestStream.php
··· 89 89 return $stream; 90 90 } 91 91 92 + public static function supportsGzip() { 93 + if (!function_exists('gzencode') || !function_exists('gzdecode')) { 94 + return false; 95 + } 96 + 97 + $has_zlib = false; 98 + 99 + // NOTE: At least locally, this returns "zlib.*", which is not terribly 100 + // reassuring. We care about "zlib.inflate". 101 + 102 + $filters = stream_get_filters(); 103 + foreach ($filters as $filter) { 104 + if (preg_match('/^zlib\\./', $filter)) { 105 + $has_zlib = true; 106 + } 107 + } 108 + 109 + return $has_zlib; 110 + } 111 + 92 112 }
+120 -1
src/applications/config/check/PhabricatorWebServerSetupCheck.php
··· 50 50 new PhutilOpaqueEnvelope($expect_pass)) 51 51 ->setTimeout(5); 52 52 53 + if (AphrontRequestStream::supportsGzip()) { 54 + $gzip_uncompressed = str_repeat('Quack! ', 128); 55 + $gzip_compressed = gzencode($gzip_uncompressed); 56 + 57 + $gzip_future = id(new HTTPSFuture($base_uri)) 58 + ->addHeader('X-Phabricator-SelfCheck', 1) 59 + ->addHeader('Content-Encoding', 'gzip') 60 + ->setTimeout(5) 61 + ->setData($gzip_compressed); 62 + 63 + } else { 64 + $gzip_future = null; 65 + } 66 + 53 67 // Make a request to the metadata service available on EC2 instances, 54 68 // to test if we're running on a T2 instance in AWS so we can warn that 55 69 // this is a bad idea. Outside of AWS, this request will just fail. ··· 61 75 $self_future, 62 76 $ec2_future, 63 77 ); 78 + 79 + if ($gzip_future) { 80 + $futures[] = $gzip_future; 81 + } 82 + 64 83 $futures = new FutureIterator($futures); 65 84 foreach ($futures as $future) { 66 85 // Just resolve the futures here. 67 86 } 68 - 69 87 70 88 try { 71 89 list($body) = $ec2_future->resolvex(); ··· 259 277 ->setMessage($message); 260 278 } 261 279 280 + if ($gzip_future) { 281 + $this->checkGzipResponse( 282 + $gzip_future, 283 + $gzip_uncompressed, 284 + $gzip_compressed); 285 + } 286 + } 287 + 288 + private function checkGzipResponse( 289 + Future $future, 290 + $uncompressed, 291 + $compressed) { 292 + 293 + try { 294 + list($body, $headers) = $future->resolvex(); 295 + } catch (Exception $ex) { 296 + return; 297 + } 298 + 299 + try { 300 + $structure = phutil_json_decode(trim($body)); 301 + } catch (Exception $ex) { 302 + return; 303 + } 304 + 305 + $raw_body = idx($structure, 'raw.base64'); 306 + $raw_body = base64_decode($raw_body); 307 + 308 + // The server received the exact compressed bytes we expected it to, so 309 + // everything is working great. 310 + if ($raw_body === $compressed) { 311 + return; 312 + } 313 + 314 + // If the server received a prefix of the raw uncompressed string, it 315 + // is almost certainly configured to decompress responses inline. Guide 316 + // users to this problem narrowly. 317 + 318 + // Otherwise, something is wrong but we don't have much of a clue what. 319 + 320 + $message = array(); 321 + $message[] = pht( 322 + 'Phabricator sent itself a test request that was compressed with '. 323 + '"Content-Encoding: gzip", but received different bytes than it '. 324 + 'sent.'); 325 + 326 + $prefix_len = min(strlen($raw_body), strlen($uncompressed)); 327 + if ($prefix_len > 16 && !strncmp($raw_body, $uncompressed, $prefix_len)) { 328 + $message[] = pht( 329 + 'The request body that the server received had already been '. 330 + 'decompressed. This strongly suggests your webserver is configured '. 331 + 'to decompress requests inline, before they reach PHP.'); 332 + $message[] = pht( 333 + 'If you are using Apache, your server may be configured with '. 334 + '"SetInputFilter DEFLATE". This directive destructively mangles '. 335 + 'requests and emits them with "Content-Length" and '. 336 + '"Content-Encoding" headers that no longer match the data in the '. 337 + 'request body.'); 338 + } else { 339 + $message[] = pht( 340 + 'This suggests your webserver is configured to decompress or mangle '. 341 + 'compressed requests.'); 342 + 343 + $message[] = pht( 344 + 'The request body Phabricator sent began:'); 345 + $message[] = $this->snipBytes($compressed); 346 + 347 + $message[] = pht( 348 + 'The request body Phabricator received began:'); 349 + $message[] = $this->snipBytes($raw_body); 350 + } 351 + 352 + $message[] = pht( 353 + 'Identify the component in your webserver configuration which is '. 354 + 'decompressing or mangling requests and disable it. Phabricator '. 355 + 'will not work properly until you do.'); 356 + 357 + $message = phutil_implode_html("\n\n", $message); 358 + 359 + $this->newIssue('webserver.accept-gzip') 360 + ->setName(pht('Compressed Requests Not Received Properly')) 361 + ->setSummary( 362 + pht( 363 + 'Your webserver is not handling compressed request bodies '. 364 + 'properly.')) 365 + ->setMessage($message); 366 + } 367 + 368 + private function snipBytes($raw) { 369 + if (!strlen($raw)) { 370 + $display = pht('<empty>'); 371 + } else { 372 + $snip = substr($raw, 0, 24); 373 + $display = phutil_loggable_string($snip); 374 + 375 + if (strlen($snip) < strlen($raw)) { 376 + $display .= '...'; 377 + } 378 + } 379 + 380 + return phutil_tag('tt', array(), $display); 262 381 } 263 382 264 383 }