Issues (2564)

app/Http/Middleware/CompressResponse.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2025 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Http\Middleware;
21
22
use Fisharebest\Webtrees\Services\PhpService;
23
use Psr\Http\Message\RequestInterface;
24
use Psr\Http\Message\ResponseInterface;
25
use Psr\Http\Message\ServerRequestInterface;
26
use Psr\Http\Message\StreamFactoryInterface;
27
use Psr\Http\Server\MiddlewareInterface;
28
use Psr\Http\Server\RequestHandlerInterface;
29
30
use function gzdeflate;
31
use function gzencode;
32
use function in_array;
33
use function str_contains;
34
use function strstr;
35
use function strtolower;
36
use function strtr;
37
38
/**
39
 * Middleware to compress (gzip or deflate) a response.
40
 */
41
class CompressResponse implements MiddlewareInterface
42
{
43
    // Non-text responses that will benefit from compression.
44
    protected const array MIME_TYPES = [
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 44 at column 26
Loading history...
45
        'application/javascript',
46
        'application/json',
47
        'application/pdf',
48
        'application/vnd.geo+json',
49
        'application/xml',
50
        'image/svg+xml',
51
    ];
52
53
    public function __construct(private PhpService $php_service, private StreamFactoryInterface $stream_factory)
54
    {
55
    }
56
57
    /**
58
     * @param ServerRequestInterface  $request
59
     * @param RequestHandlerInterface $handler
60
     *
61
     * @return ResponseInterface
62
     */
63
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
64
    {
65
        $response = $handler->handle($request);
66
67
        $method = $this->compressionMethod($request);
68
69
        if ($method !== null && $this->isCompressible($response)) {
70
            $content = (string) $response->getBody();
71
72
            switch ($method) {
73
                case 'deflate':
74
                    $content = gzdeflate($content);
75
                    break;
76
77
                case 'gzip':
78
                    $content = gzencode($content);
79
                    break;
80
            }
81
82
            if ($content === false) {
83
                return $response;
84
            }
85
86
            $stream = $this->stream_factory->createStream($content);
87
88
            return $response
89
                ->withBody($stream)
90
                ->withHeader('content-encoding', $method)
91
                ->withHeader('vary', 'accept-encoding');
92
        }
93
94
        return $response;
95
    }
96
97
    protected function compressionMethod(RequestInterface $request): string|null
98
    {
99
        $accept_encoding = strtolower($request->getHeaderLine('accept-encoding'));
100
        $zlib_available  = $this->php_service->extensionLoaded(extension: 'zlib');
101
102
        if ($zlib_available) {
103
            if (str_contains($accept_encoding, 'gzip')) {
104
                return 'gzip';
105
            }
106
107
            if (str_contains($accept_encoding, 'deflate')) {
108
                return 'deflate';
109
            }
110
        }
111
112
        return null;
113
    }
114
115
    /**
116
     * @param ResponseInterface $response
117
     *
118
     * @return bool
119
     */
120
    protected function isCompressible(ResponseInterface $response): bool
121
    {
122
        // Already encoded?
123
        if ($response->hasHeader('content-encoding')) {
124
            return false;
125
        }
126
127
        $content_type = $response->getHeaderLine('content-type');
128
        $content_type = strtr($content_type, [' ' => '']);
129
        $content_type = strstr($content_type, ';', true) ?: $content_type;
130
        $content_type = strtolower($content_type);
131
132
        if (str_starts_with($content_type, 'text/')) {
133
            return true;
134
        }
135
136
        return in_array($content_type, static::MIME_TYPES, true);
137
    }
138
}
139