1179fae31SGreg Roach<?php 2179fae31SGreg Roach 3179fae31SGreg Roach/** 4179fae31SGreg Roach * webtrees: online genealogy 5d11be702SGreg Roach * Copyright (C) 2023 webtrees development team 6179fae31SGreg Roach * This program is free software: you can redistribute it and/or modify 7179fae31SGreg Roach * it under the terms of the GNU General Public License as published by 8179fae31SGreg Roach * the Free Software Foundation, either version 3 of the License, or 9179fae31SGreg Roach * (at your option) any later version. 10179fae31SGreg Roach * This program is distributed in the hope that it will be useful, 11179fae31SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of 12179fae31SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13179fae31SGreg Roach * GNU General Public License for more details. 14179fae31SGreg Roach * You should have received a copy of the GNU General Public License 15179fae31SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>. 16179fae31SGreg Roach */ 17179fae31SGreg Roach 18179fae31SGreg Roachdeclare(strict_types=1); 19179fae31SGreg Roach 20179fae31SGreg Roachnamespace Fisharebest\Webtrees\Http\Middleware; 21179fae31SGreg Roach 22179fae31SGreg Roachuse Psr\Http\Message\RequestInterface; 23179fae31SGreg Roachuse Psr\Http\Message\ResponseInterface; 24179fae31SGreg Roachuse Psr\Http\Message\ServerRequestInterface; 25179fae31SGreg Roachuse Psr\Http\Message\StreamFactoryInterface; 26179fae31SGreg Roachuse Psr\Http\Server\MiddlewareInterface; 27179fae31SGreg Roachuse Psr\Http\Server\RequestHandlerInterface; 28179fae31SGreg Roach 29179fae31SGreg Roachuse function extension_loaded; 30179fae31SGreg Roachuse function gzdeflate; 31179fae31SGreg Roachuse function gzencode; 32179fae31SGreg Roachuse function in_array; 33179fae31SGreg Roachuse function str_contains; 34179fae31SGreg Roachuse function strstr; 35179fae31SGreg Roachuse function strtolower; 36179fae31SGreg Roachuse function strtr; 37179fae31SGreg Roach 38179fae31SGreg Roach/** 39179fae31SGreg Roach * Middleware to compress (gzip or deflate) a response. 40179fae31SGreg Roach */ 41179fae31SGreg Roachclass CompressResponse implements MiddlewareInterface 42179fae31SGreg Roach{ 43179fae31SGreg Roach // Non-text responses that will benefit from compression. 44179fae31SGreg Roach protected const MIME_TYPES = [ 45179fae31SGreg Roach 'application/javascript', 46179fae31SGreg Roach 'application/json', 47179fae31SGreg Roach 'application/pdf', 48179fae31SGreg Roach 'application/vnd.geo+json', 49179fae31SGreg Roach 'application/xml', 50179fae31SGreg Roach 'image/svg+xml', 51179fae31SGreg Roach ]; 52179fae31SGreg Roach 53c4943cffSGreg Roach protected StreamFactoryInterface $stream_factory; 54179fae31SGreg Roach 55179fae31SGreg Roach /** 56179fae31SGreg Roach * @param StreamFactoryInterface $stream_factory 57179fae31SGreg Roach */ 58179fae31SGreg Roach public function __construct(StreamFactoryInterface $stream_factory) 59179fae31SGreg Roach { 60179fae31SGreg Roach $this->stream_factory = $stream_factory; 61179fae31SGreg Roach } 62179fae31SGreg Roach 63179fae31SGreg Roach /** 64179fae31SGreg Roach * @param ServerRequestInterface $request 65179fae31SGreg Roach * @param RequestHandlerInterface $handler 66179fae31SGreg Roach * 67179fae31SGreg Roach * @return ResponseInterface 68179fae31SGreg Roach */ 69179fae31SGreg Roach public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface 70179fae31SGreg Roach { 71179fae31SGreg Roach $response = $handler->handle($request); 72179fae31SGreg Roach 73179fae31SGreg Roach $method = $this->compressionMethod($request); 74179fae31SGreg Roach 75179fae31SGreg Roach if ($method !== null && $this->isCompressible($response)) { 76179fae31SGreg Roach $content = (string) $response->getBody(); 77179fae31SGreg Roach 78179fae31SGreg Roach switch ($method) { 79179fae31SGreg Roach case 'deflate': 80179fae31SGreg Roach $content = gzdeflate($content); 81179fae31SGreg Roach break; 82179fae31SGreg Roach 83179fae31SGreg Roach case 'gzip': 84179fae31SGreg Roach $content = gzencode($content); 85179fae31SGreg Roach break; 86179fae31SGreg Roach } 87179fae31SGreg Roach 88179fae31SGreg Roach if ($content === false) { 89179fae31SGreg Roach return $response; 90179fae31SGreg Roach } 91179fae31SGreg Roach 92179fae31SGreg Roach $stream = $this->stream_factory->createStream($content); 93179fae31SGreg Roach 94179fae31SGreg Roach return $response 95179fae31SGreg Roach ->withBody($stream) 96179fae31SGreg Roach ->withHeader('content-encoding', $method) 97179fae31SGreg Roach ->withHeader('vary', 'accept-encoding'); 98179fae31SGreg Roach } 99179fae31SGreg Roach 100179fae31SGreg Roach return $response; 101179fae31SGreg Roach } 102179fae31SGreg Roach 103*1ff45046SGreg Roach protected function compressionMethod(RequestInterface $request): string|null 104179fae31SGreg Roach { 105179fae31SGreg Roach $accept_encoding = strtolower($request->getHeaderLine('accept-encoding')); 106179fae31SGreg Roach $zlib_available = extension_loaded('zlib'); 107179fae31SGreg Roach 108179fae31SGreg Roach if ($zlib_available) { 109179fae31SGreg Roach if (str_contains($accept_encoding, 'gzip')) { 110179fae31SGreg Roach return 'gzip'; 111179fae31SGreg Roach } 112179fae31SGreg Roach 113179fae31SGreg Roach if (str_contains($accept_encoding, 'deflate')) { 114179fae31SGreg Roach return 'deflate'; 115179fae31SGreg Roach } 116179fae31SGreg Roach } 117179fae31SGreg Roach 118179fae31SGreg Roach return null; 119179fae31SGreg Roach } 120179fae31SGreg Roach 121179fae31SGreg Roach /** 122179fae31SGreg Roach * @param ResponseInterface $response 123179fae31SGreg Roach * 124179fae31SGreg Roach * @return bool 125179fae31SGreg Roach */ 126179fae31SGreg Roach protected function isCompressible(ResponseInterface $response): bool 127179fae31SGreg Roach { 128179fae31SGreg Roach // Already encoded? 129179fae31SGreg Roach if ($response->hasHeader('content-encoding')) { 130179fae31SGreg Roach return false; 131179fae31SGreg Roach } 132179fae31SGreg Roach 133179fae31SGreg Roach $content_type = $response->getHeaderLine('content-type'); 134179fae31SGreg Roach $content_type = strtr($content_type, [' ' => '']); 135179fae31SGreg Roach $content_type = strstr($content_type, ';', true) ?: $content_type; 136179fae31SGreg Roach $content_type = strtolower($content_type); 137179fae31SGreg Roach 138179fae31SGreg Roach if (str_starts_with($content_type, 'text/')) { 139179fae31SGreg Roach return true; 140179fae31SGreg Roach } 141179fae31SGreg Roach 142179fae31SGreg Roach return in_array($content_type, static::MIME_TYPES, true); 143179fae31SGreg Roach } 144179fae31SGreg Roach} 145