mirror of
https://github.com/nextcloud/server.git
synced 2026-06-12 18:21:40 -04:00
feat: add setup check for request buffering on FPM
Context: Using `Transfer-Encoding: chunked` of HTTP 1.1 PHP-FPM has a bug[1] where the request body is not passed to PHP if the `Content-Length` header is missing, while FastCGI in general allows this (I could reproduce that FastCGI passed the request stream from NGinx) PHP-FPM does not forward this to the PHP application. This means when using PHP-FPM we get an empty request body and thus every `PUT` will be an empty file. I tested that `mod_php` is not affected, while it also has no `Content-Length` header, it correctly passed the stream and thus also works without buffering the request. Only PHP-FPM needs buffering of the request so that a `Content-Length` header can be generated. To enable this on Apache set: `SetEnvIfNoCase Transfer-Encoding "chunked" proxy-sendcl=1` On NGinx: `fastcgi_request_buffering on;`. [1]: https://github.com/php/php-src/issues/9441 ref: https://github.com/nextcloud/server/issues/7995 Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
15fa1094ac
commit
ccc4d2eb9f
6 changed files with 121 additions and 4 deletions
|
|
@ -44,10 +44,13 @@ return [
|
|||
['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET', 'requirements' => ['group' => '.+'] , 'root' => ''],
|
||||
['name' => 'Users#setPreference', 'url' => '/settings/users/preferences/{key}', 'verb' => 'POST' , 'root' => ''],
|
||||
['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET' , 'root' => ''],
|
||||
|
||||
['name' => 'CheckSetup#setupCheckManager', 'url' => '/settings/setupcheck', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'CheckSetup#checkContentLengthHeader', 'url' => '/settings/setupcheck/test-content-length', 'verb' => 'PUT' , 'root' => ''],
|
||||
['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET' , 'root' => ''],
|
||||
|
||||
['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info'] , 'root' => ''],
|
||||
['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server'] , 'root' => ''],
|
||||
['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET' , 'root' => ''],
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ return array(
|
|||
'OCA\\Settings\\SetupChecks\\PushService' => $baseDir . '/../lib/SetupChecks/PushService.php',
|
||||
'OCA\\Settings\\SetupChecks\\RandomnessSecure' => $baseDir . '/../lib/SetupChecks/RandomnessSecure.php',
|
||||
'OCA\\Settings\\SetupChecks\\ReadOnlyConfig' => $baseDir . '/../lib/SetupChecks/ReadOnlyConfig.php',
|
||||
'OCA\\Settings\\SetupChecks\\RequestBuffering' => $baseDir . '/../lib/SetupChecks/RequestBuffering.php',
|
||||
'OCA\\Settings\\SetupChecks\\SchedulingTableSize' => $baseDir . '/../lib/SetupChecks/SchedulingTableSize.php',
|
||||
'OCA\\Settings\\SetupChecks\\SecurityHeaders' => $baseDir . '/../lib/SetupChecks/SecurityHeaders.php',
|
||||
'OCA\\Settings\\SetupChecks\\SupportedDatabase' => $baseDir . '/../lib/SetupChecks/SupportedDatabase.php',
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ class ComposerStaticInitSettings
|
|||
'OCA\\Settings\\SetupChecks\\PushService' => __DIR__ . '/..' . '/../lib/SetupChecks/PushService.php',
|
||||
'OCA\\Settings\\SetupChecks\\RandomnessSecure' => __DIR__ . '/..' . '/../lib/SetupChecks/RandomnessSecure.php',
|
||||
'OCA\\Settings\\SetupChecks\\ReadOnlyConfig' => __DIR__ . '/..' . '/../lib/SetupChecks/ReadOnlyConfig.php',
|
||||
'OCA\\Settings\\SetupChecks\\RequestBuffering' => __DIR__ . '/..' . '/../lib/SetupChecks/RequestBuffering.php',
|
||||
'OCA\\Settings\\SetupChecks\\SchedulingTableSize' => __DIR__ . '/..' . '/../lib/SetupChecks/SchedulingTableSize.php',
|
||||
'OCA\\Settings\\SetupChecks\\SecurityHeaders' => __DIR__ . '/..' . '/../lib/SetupChecks/SecurityHeaders.php',
|
||||
'OCA\\Settings\\SetupChecks\\SupportedDatabase' => __DIR__ . '/..' . '/../lib/SetupChecks/SupportedDatabase.php',
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ use OCA\Settings\SetupChecks\PhpOutputBuffering;
|
|||
use OCA\Settings\SetupChecks\PushService;
|
||||
use OCA\Settings\SetupChecks\RandomnessSecure;
|
||||
use OCA\Settings\SetupChecks\ReadOnlyConfig;
|
||||
use OCA\Settings\SetupChecks\RequestBuffering;
|
||||
use OCA\Settings\SetupChecks\SchedulingTableSize;
|
||||
use OCA\Settings\SetupChecks\SecurityHeaders;
|
||||
use OCA\Settings\SetupChecks\SupportedDatabase;
|
||||
|
|
@ -203,6 +204,7 @@ class Application extends App implements IBootstrap {
|
|||
$context->registerSetupCheck(PhpOutputBuffering::class);
|
||||
$context->registerSetupCheck(RandomnessSecure::class);
|
||||
$context->registerSetupCheck(ReadOnlyConfig::class);
|
||||
$context->registerSetupCheck(RequestBuffering::class);
|
||||
$context->registerSetupCheck(SecurityHeaders::class);
|
||||
$context->registerSetupCheck(SchedulingTableSize::class);
|
||||
$context->registerSetupCheck(SupportedDatabase::class);
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ use OC\AppFramework\Http;
|
|||
use OC\IntegrityCheck\Checker;
|
||||
use OCA\Settings\Settings\Admin\Overview;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
|
||||
use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\Attribute\PublicPage;
|
||||
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
|
|
@ -53,6 +55,14 @@ class CheckSetupController extends Controller {
|
|||
return new DataResponse($this->setupCheckManager->runAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataResponse
|
||||
*/
|
||||
#[AuthorizedAdminSetting(settings: Overview::class)]
|
||||
public function check() {
|
||||
return new DataResponse($this->setupCheckManager->runAll());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
|
|
@ -125,10 +135,14 @@ Raw output
|
|||
}
|
||||
|
||||
/**
|
||||
* @return DataResponse
|
||||
* Used for setup checks to verify the server has request caching.
|
||||
* @NoSubAdminRequired
|
||||
*/
|
||||
#[AuthorizedAdminSetting(settings: Overview::class)]
|
||||
public function check() {
|
||||
return new DataResponse($this->setupCheckManager->runAll());
|
||||
#[PublicPage()]
|
||||
#[NoCSRFRequired()]
|
||||
#[AnonRateLimit(limit: 10, period: 600)]
|
||||
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
|
||||
public function checkContentLengthHeader(): DataResponse {
|
||||
return new DataResponse(($this->request->getHeader('Content-Length')));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
96
apps/settings/lib/SetupChecks/RequestBuffering.php
Normal file
96
apps/settings/lib/SetupChecks/RequestBuffering.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Settings\SetupChecks;
|
||||
|
||||
use GuzzleHttp\Psr7;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\SetupCheck\CheckServerResponseTrait;
|
||||
use OCP\SetupCheck\ISetupCheck;
|
||||
use OCP\SetupCheck\SetupResult;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class RequestBuffering implements ISetupCheck {
|
||||
|
||||
use CheckServerResponseTrait;
|
||||
|
||||
public function __construct(
|
||||
protected IL10N $l10n,
|
||||
protected IConfig $config,
|
||||
protected IURLGenerator $urlGenerator,
|
||||
protected IClientService $clientService,
|
||||
protected LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getCategory(): string {
|
||||
return 'network';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->l10n->t('Request buffering');
|
||||
}
|
||||
|
||||
public function run(): SetupResult {
|
||||
$usingFPM = function_exists('fastcgi_finish_request');
|
||||
if (!$usingFPM && !\OC::$CLI) {
|
||||
return SetupResult::success(
|
||||
$this->l10n->t('Not using PHP-FPM.')
|
||||
);
|
||||
}
|
||||
|
||||
$works = null;
|
||||
$stream = Psr7\Utils::streamFor(str_repeat('x', 1337));
|
||||
$options = [
|
||||
'body' => $stream,
|
||||
'headers' => [
|
||||
'Transfer-Encoding' => 'chunked',
|
||||
],
|
||||
];
|
||||
$url = $this->urlGenerator->linkToRoute('settings.CheckSetup.checkContentLengthHeader');
|
||||
foreach ($this->runRequest('PUT', $url, ['ignoreSSL' => true, 'options' => $options]) as $response) {
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
if (!str_contains(strtolower($contentType), 'application/json')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
$body = is_resource($body) ? stream_get_contents($body) : $body;
|
||||
$works = $works || $body === '"1337"';
|
||||
}
|
||||
|
||||
if ($works === null) {
|
||||
return SetupResult::info(
|
||||
$this->l10n->t('Could not check that your web server has configured request buffering. Please check manually.'),
|
||||
);
|
||||
} elseif ($works === true) {
|
||||
return SetupResult::success(
|
||||
$this->l10n->t('Your web server seems to have request buffering configured correctly.'),
|
||||
);
|
||||
} else {
|
||||
if ($usingFPM) {
|
||||
return SetupResult::error(
|
||||
$this->l10n->t('Your web server is not configured for request buffering, this will cause broken uploads with some clients.')
|
||||
. ' '
|
||||
. $this->l10n->t('Due to a limitation of PHP-FPM chunked requests will not be passed to Nextcloud if the server does not buffer such requests.'),
|
||||
);
|
||||
} else {
|
||||
// Not using FPM but we are on CLI so we do not know if FPM is used
|
||||
return SetupResult::warning(
|
||||
$this->l10n->t('Your web server is not configured for request buffering, if you are running PHP-FPM this will cause broken uploads with some clients.')
|
||||
. ' '
|
||||
. $this->l10n->t('Due to a limitation of PHP-FPM chunked requests will not be passed to Nextcloud if the server does not buffer such requests.'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue