From: Tim Düsterhus Date: Tue, 10 Aug 2021 15:21:31 +0000 (+0200) Subject: Add `cache-control: private` to PSR-7 responses X-Git-Tag: 5.5.0_Alpha_1~493^2~2^2~1 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=1d809b765e30929e68a5a98404b0ef1e1ac9b94c;p=GitHub%2FWoltLab%2FWCF.git Add `cache-control: private` to PSR-7 responses see #4273 --- diff --git a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php index 74210e7966..2b97fac1f6 100644 --- a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php @@ -96,8 +96,7 @@ class RequestHandler extends SingletonFactory $result = $this->getActiveRequest()->execute(); if ($result instanceof ResponseInterface) { - $emitter = new SapiEmitter(); - $emitter->emit($result); + $this->sendPsr7Response($result); } } catch (NamedUserException $e) { $e->show(); @@ -106,6 +105,88 @@ class RequestHandler extends SingletonFactory } } + /** + * Splits the given array of cache-control values at commas, while properly + * taking into account that each value might itself contain commas within a + * quoted string. + */ + private function splitCacheControl(array $values): \Iterator + { + foreach ($values as $value) { + $isQuoted = false; + $result = ''; + + for ($i = 0, $len = \strlen($value); $i < $len; $i++) { + $char = $value[$i]; + if (!$isQuoted && $char === ',') { + yield \trim($result); + + $isQuoted = false; + $result = ''; + + continue; + } + + if ($isQuoted && $char === '\\') { + $result .= $char; + $i++; + + if ($i < $len) { + $result .= $value[$i]; + + continue; + } + } + + if ($char === '"') { + $isQuoted = !$isQuoted; + } + + $result .= $char; + } + + if ($result !== '') { + yield \trim($result); + } + } + } + + /** + * @since 5.5 + */ + private function sendPsr7Response(ResponseInterface $response) + { + // Storing responses in a shared cache is unsafe, because they all contain session specific information. + // Add the 'private' value to the cache-control header and remove any 'public' value. + $cacheControl = []; + foreach ($this->splitCacheControl($response->getHeader('cache-control')) as $value) { + [$field] = \explode('=', $value, 2); + + // Prevent duplication of the 'private' field. + if ($field === 'private') { + continue; + } + + // Drop the 'public' field. + if ($field === 'public') { + continue; + } + + $cacheControl[] = $value; + } + $cacheControl[] = 'private'; + + $response = $response->withHeader( + 'cache-control', + // Manually imploding the fields is not required as per strict reading of the HTTP standard, + // but having duplicate 'cache-control' headers in the response certainly looks odd. + \implode(', ', $cacheControl) + ); + + $emitter = new SapiEmitter(); + $emitter->emit($response); + } + /** * Builds a new request. *