From 34777c57c9b1988656e98c54783cf449c4c137d6 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Mon, 10 Aug 2026 14:32:03 +0200 Subject: [PATCH] feat: add option to send legacy style x-amx-user-agent header to s3 server Signed-off-by: Robin Appelman --- .../Files/ObjectStore/S3ConnectionTrait.php | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lib/private/Files/ObjectStore/S3ConnectionTrait.php b/lib/private/Files/ObjectStore/S3ConnectionTrait.php index 3837f0b869f8b..371c0bc6e6f8c 100644 --- a/lib/private/Files/ObjectStore/S3ConnectionTrait.php +++ b/lib/private/Files/ObjectStore/S3ConnectionTrait.php @@ -8,12 +8,15 @@ namespace OC\Files\ObjectStore; use Aws\ClientResolver; +use Aws\Command; +use Aws\CommandInterface; use Aws\Credentials\CredentialProvider; use Aws\Credentials\Credentials; use Aws\Exception\CredentialsException; use Aws\Middleware; use Aws\S3\Exception\S3Exception; use Aws\S3\S3Client; +use Aws\UserAgentMiddleware; use GuzzleHttp\Promise\Create; use GuzzleHttp\Promise\RejectedPromise; use GuzzleHttp\Psr7\Utils; @@ -69,6 +72,7 @@ protected function parseParams($params) { $params['port'] = (isset($params['use_ssl']) && $params['use_ssl'] === false) ? 80 : 443; } $params['verify_bucket_exists'] = $params['verify_bucket_exists'] ?? true; + $params['legacyAmzUserAgent'] = $params['legacyAmzUserAgent'] ?? false; if ($params['s3-accelerate']) { $params['verify_bucket_exists'] = false; @@ -164,6 +168,10 @@ public function getConnection() { $this->addDeleteObjectsContentMd5Middleware(); + if ($this->params['legacyAmzUserAgent']) { + $this->addLegacyAmzUserAgentMiddleware(); + } + try { $logger = Server::get(LoggerInterface::class); if (!$this->connection::isBucketDnsCompatible($this->bucket)) { @@ -260,6 +268,36 @@ private function addDeleteObjectsContentMd5Middleware(): void { ); } + /** + * Starting with aws-sdk 3.336.0, the user agent headers sent by the sdk have changed. + * + * Previously, the `X-Amz-User-Agent` and `User-Agent` header would both contain the same + * user agent. + * Since 3.336.0, the `X-Amz-User-Agent` no longer contains the user agent but is sent empty instead. + * + * This seems to break some s3 implementations, so we can add a middleware to re-add the value + */ + private function addLegacyAmzUserAgentMiddleware(): void { + if ($this->connection === null) { + return; + } + + $handlerList = $this->connection->getHandlerList(); + $handlerList->appendBuild( + Middleware::mapRequest(static function ( + RequestInterface $request, + ): RequestInterface { + // Get the upstream `UserAgentMiddleware` to generate a user id in the same format as the old `X-Amz-User-Agent` + // This won't be exactly what the final `User-Agent` will be, but it should be close enough for our goals. + $upstreamUAMiddleware = new UserAgentMiddleware(function (CommandInterface $_command, RequestInterface $request) { + return $request->getHeader('User-Agent'); + }); + $generatedUA = $upstreamUAMiddleware->__invoke(new Command('dummy'), $request); + return $request->withHeader('X-Amz-User-Agent', $generatedUA); + }), + ); + } + public static function legacySignatureProvider($version, $service, $region) { switch ($version) { case 'v2':