From 6b84508733e086477caa5417010b6d3c9146cae7 Mon Sep 17 00:00:00 2001 From: Josh Date: Wed, 8 Jan 2025 13:31:03 -0500 Subject: [PATCH] fix(AppFramework): Log malformed protocol values and unify fallback behavior Signed-off-by: Josh --- lib/private/AppFramework/Http/Request.php | 49 +++++++++++++-------- tests/lib/AppFramework/Http/RequestTest.php | 26 +++++++++-- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index d177221556c66..27b15e1b72df7 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -14,6 +14,7 @@ use OCP\IConfig; use OCP\IRequest; use OCP\IRequestId; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\IpUtils; /** @@ -613,36 +614,46 @@ private function isOverwriteCondition(): bool { /** * Returns the server protocol. It respects one or more reverse proxies servers - * and load balancers + * and load balancers. Precedence: + * 1. `overwriteprotocol` config value + * 2. `X-Forwarded-Proto` header value + * 3. $_SERVER['HTTPS'] value + * If an invalid protocol is provided, defaults to http, continues, but logs as an error. + * * @return string Server protocol (http or https) */ public function getServerProtocol(): string { + $proto = 'http'; + if ($this->config->getSystemValueString('overwriteprotocol') !== '' - && $this->isOverwriteCondition()) { - return $this->config->getSystemValueString('overwriteprotocol'); - } - - if ($this->fromTrustedProxy() && isset($this->server['HTTP_X_FORWARDED_PROTO'])) { + && $this->isOverwriteCondition() + ) { + $proto = strtolower($this->config->getSystemValueString('overwriteprotocol')); + } elseif ($this->fromTrustedProxy() + && isset($this->server['HTTP_X_FORWARDED_PROTO']) + ) { if (str_contains($this->server['HTTP_X_FORWARDED_PROTO'], ',')) { $parts = explode(',', $this->server['HTTP_X_FORWARDED_PROTO']); $proto = strtolower(trim($parts[0])); } else { $proto = strtolower($this->server['HTTP_X_FORWARDED_PROTO']); } - - // Verify that the protocol is always HTTP or HTTPS - // default to http if an invalid value is provided - return $proto === 'https' ? 'https' : 'http'; - } - - if (isset($this->server['HTTPS']) - && $this->server['HTTPS'] !== null + } elseif (!empty($this->server['HTTPS']) && $this->server['HTTPS'] !== 'off' - && $this->server['HTTPS'] !== '') { - return 'https'; + ) { + $proto = 'https'; } - return 'http'; + if ($proto !== 'https' && $proto !== 'http') { + // log unrecognized value so admin has a chance to fix it + \OC::$server->get(LoggerInterface::class)->critical( + 'Server protocol is malformed [falling back to http] (check overwriteprotocol and/or X-Forwarded-Proto to remedy): ' . $proto, + ['app' => 'core'] + ); + } + + // default to http if provided an invalid value + return $proto === 'https' ? 'https' : 'http'; } /** @@ -729,11 +740,11 @@ public function getRawPathInfo(): string { } /** - * Get PathInfo from request + * Get PathInfo from request (rawurldecoded) * @throws \Exception * @return string|false Path info or false when not found */ - public function getPathInfo() { + public function getPathInfo(): string|false { $pathInfo = $this->getRawPathInfo(); return \Sabre\HTTP\decodePath($pathInfo); } diff --git a/tests/lib/AppFramework/Http/RequestTest.php b/tests/lib/AppFramework/Http/RequestTest.php index 1c7f07580cc66..4bf0ac84bed6a 100644 --- a/tests/lib/AppFramework/Http/RequestTest.php +++ b/tests/lib/AppFramework/Http/RequestTest.php @@ -787,12 +787,12 @@ public function testGetHttpProtocol($input, $expected): void { $this->assertSame($expected, $request->getHttpProtocol()); } - public function testGetServerProtocolWithOverride(): void { + public function testGetServerProtocolWithOverrideValid(): void { $this->config ->expects($this->exactly(3)) ->method('getSystemValueString') ->willReturnMap([ - ['overwriteprotocol', '', 'customProtocol'], + ['overwriteprotocol', '', 'HTTPS'], // should be automatically lowercased ['overwritecondaddr', '', ''], ]); @@ -804,9 +804,29 @@ public function testGetServerProtocolWithOverride(): void { $this->stream ); - $this->assertSame('customProtocol', $request->getServerProtocol()); + $this->assertSame('https', $request->getServerProtocol()); } + public function testGetServerProtocolWithOverrideInValid(): void { + $this->config + ->expects($this->exactly(3)) + ->method('getSystemValueString') + ->willReturnMap([ + ['overwriteprotocol', '', 'bogusProtocol'], // should trigger fallback to http + ['overwritecondaddr', '', ''], + ]); + + $request = new Request( + [], + $this->requestId, + $this->config, + $this->csrfTokenManager, + $this->stream + ); + + $this->assertSame('http', $request->getServerProtocol()); + } + public function testGetServerProtocolWithProtoValid(): void { $this->config ->method('getSystemValue')