PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / ProxyHttp.php
matomo / app / core Last commit date
API 3 months ago Access 3 months ago Application 3 months ago Archive 3 months ago ArchiveProcessor 3 months ago Archiver 2 years ago AssetManager 3 months ago Auth 6 months ago Category 6 months ago Changes 3 months ago CliMulti 1 year ago Columns 3 months ago Concurrency 3 months ago Config 3 months ago Container 3 months ago CronArchive 3 months ago DataAccess 3 months ago DataFiles 2 years ago DataTable 3 months ago Db 3 months ago DeviceDetector 1 year ago Email 2 years ago Exception 4 months ago Http 4 months ago Intl 3 months ago Log 2 years ago Mail 1 year ago Measurable 6 months ago Menu 3 months ago Metrics 3 months ago Notification 6 months ago Period 3 months ago Plugin 3 months ago Policy 3 months ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 3 months ago Request 3 months ago Scheduler 3 months ago Segment 3 months ago Session 3 months ago Settings 3 months ago Tracker 3 months ago Translation 3 months ago Twig 1 year ago UpdateCheck 3 months ago Updater 4 months ago Updates 3 months ago Validators 1 year ago View 6 months ago ViewDataTable 3 months ago Visualization 1 year ago Widget 3 months ago .htaccess 2 years ago Access.php 3 months ago Archive.php 3 months ago ArchiveProcessor.php 4 months ago AssetManager.php 3 months ago Auth.php 6 months ago AuthResult.php 6 months ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 4 months ago CliMulti.php 3 months ago Common.php 3 months ago Config.php 3 months ago Console.php 3 months ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 3 months ago DI.php 3 months ago DataArray.php 5 months ago DataTable.php 3 months ago Date.php 3 months ago Db.php 3 months ago DbHelper.php 3 months ago Development.php 1 year ago ErrorHandler.php 6 months ago EventDispatcher.php 1 year ago ExceptionHandler.php 4 months ago FileIntegrity.php 3 months ago Filechecks.php 1 year ago Filesystem.php 3 months ago FrontController.php 4 months ago Http.php 4 months ago IP.php 1 year ago Log.php 3 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 3 months ago NoAccessException.php 2 years ago Nonce.php 6 months ago Notification.php 6 months ago NumberFormatter.php 5 months ago Option.php 5 months ago Period.php 3 months ago Piwik.php 3 months ago Plugin.php 3 months ago Process.php 1 year ago Profiler.php 6 months ago ProxyHeaders.php 4 months ago ProxyHttp.php 5 months ago QuickForm2.php 3 months ago RankingQuery.php 5 months ago ReportRenderer.php 3 months ago Request.php 3 months ago Segment.php 3 months ago Sequence.php 6 months ago Session.php 3 months ago SettingsPiwik.php 3 months ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 4 months ago SiteContentDetector.php 3 months ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 3 months ago Twig.php 3 months ago Unzip.php 1 year ago UpdateCheck.php 3 months ago Updater.php 3 months ago UpdaterErrorException.php 2 years ago Updates.php 3 months ago Url.php 3 months ago UrlHelper.php 3 months ago Version.php 3 months ago View.php 3 months ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 6 months ago
ProxyHttp.php
256 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik;
10
11 /**
12 * Http helper: static file server proxy, with compression, caching, isHttps() helper...
13 *
14 * Used to server piwik.js and the merged+minified CSS and JS files
15 *
16 */
17 class ProxyHttp
18 {
19 public const DEFLATE_ENCODING_REGEX = '/(?:^|, ?)(deflate)(?:,|$)/';
20 public const GZIP_ENCODING_REGEX = '/(?:^|, ?)((x-)?gzip)(?:,|$)/';
21 /**
22 * Returns true if the current request appears to be a secure HTTPS connection
23 *
24 * @return bool
25 */
26 public static function isHttps()
27 {
28 return \Piwik\Url::getCurrentScheme() === 'https';
29 }
30 /**
31 * Serve static files through php proxy.
32 *
33 * It performs the following actions:
34 * - Checks the file is readable or returns "HTTP/1.0 404 Not Found"
35 * - Returns "HTTP/1.1 304 Not Modified" after comparing the HTTP_IF_MODIFIED_SINCE
36 * with the modification date of the static file
37 * - Will try to compress the static file according to HTTP_ACCEPT_ENCODING. Compressed files are store in
38 * the /tmp directory. If compressing extensions are not available, a manually gzip compressed file
39 * can be provided in the /tmp directory. It has to bear the same name with an added .gz extension.
40 * Using manually compressed static files requires you to manually update the compressed file when
41 * the static file is updated.
42 * - Overrides server cache control config to allow caching
43 * - Sends Very Accept-Encoding to tell proxies to store different version of the static file according
44 * to users encoding capacities.
45 *
46 * Warning:
47 * Compressed filed are stored in the /tmp directory.
48 * If this method is used with two files bearing the same name but located in different locations,
49 * there is a risk of conflict. One file could be served with the content of the other.
50 * A future upgrade of this method would be to recreate the directory structure of the static file
51 * within a /tmp/compressed-static-files directory.
52 *
53 * @param string $file The location of the static file to serve
54 * @param string $contentType The content type of the static file.
55 * @param int $expireFarFutureDays Day in the far future to set the Expires header to.
56 * Should be set to false for files that should not be cached.
57 * @param int|false $byteStart The starting byte in the file to serve. If false, the data from the beginning
58 * of the file will be served.
59 * @param int|false $byteEnd The ending byte in the file to serve. If false, the data from $byteStart to the
60 * end of the file will be served.
61 * @param string|false $filename By default the filename of $file is reused as Content-Disposition. If the
62 * file should be sent as a different filename to the client you can specify
63 * a custom filename here.
64 */
65 public static function serverStaticFile($file, $contentType, $expireFarFutureDays = 100, $byteStart = \false, $byteEnd = \false, $filename = \false)
66 {
67 // if the file cannot be found return HTTP status code '404'
68 if (empty($file) || !file_exists($file)) {
69 \Piwik\Common::sendResponseCode(404);
70 return;
71 }
72 if (!is_readable($file)) {
73 \Piwik\Common::sendResponseCode(500);
74 return;
75 }
76 $modifiedSince = \Piwik\Http::getModifiedSinceHeader();
77 $fileModifiedTime = @filemtime($file);
78 $lastModified = gmdate('D, d M Y H:i:s', $fileModifiedTime) . ' GMT';
79 // set some HTTP response headers
80 self::overrideCacheControlHeaders('public');
81 \Piwik\Common::sendHeader('Vary: Accept-Encoding');
82 if (\false === $filename) {
83 $filename = basename($file);
84 }
85 \Piwik\Common::sendHeader('Content-Disposition: inline; filename=' . $filename);
86 if ($expireFarFutureDays) {
87 // Required by proxy caches potentially in between the browser and server to cache the request indeed
88 \Piwik\Common::sendHeader(self::getExpiresHeaderForFutureDay($expireFarFutureDays));
89 }
90 // Return 304 if the file has not modified since
91 if ($modifiedSince === $lastModified) {
92 \Piwik\Common::sendResponseCode(304);
93 return;
94 }
95 // if we have to serve the file, serve it now, either in the clear or compressed
96 if ($byteStart === \false) {
97 $byteStart = 0;
98 }
99 if ($byteEnd === \false) {
100 $byteEnd = filesize($file);
101 }
102 $compressed = \false;
103 $encoding = '';
104 $compressedFileLocation = \Piwik\AssetManager::getInstance()->getAssetDirectory() . '/' . basename($file);
105 if (!($byteStart == 0 && $byteEnd == filesize($file))) {
106 $compressedFileLocation .= ".{$byteStart}.{$byteEnd}";
107 }
108 $phpOutputCompressionEnabled = self::isPhpOutputCompressed();
109 if (isset($_SERVER['HTTP_ACCEPT_ENCODING']) && !$phpOutputCompressionEnabled) {
110 [$encoding, $extension] = self::getCompressionEncodingAcceptedByClient();
111 $filegz = $compressedFileLocation . $extension;
112 if (self::canCompressInPhp()) {
113 if (!empty($encoding)) {
114 // compress the file if it doesn't exist or is newer than the existing cached file, and cache
115 // the compressed result
116 if (self::shouldCompressFile($file, $filegz)) {
117 self::compressFile($file, $filegz, $encoding, $byteStart, $byteEnd);
118 }
119 $compressed = \true;
120 $file = $filegz;
121 $byteStart = 0;
122 $byteEnd = filesize($file);
123 }
124 } else {
125 // if a compressed file exists, the file was manually compressed so we just serve that
126 if ($extension == '.gz' && !self::shouldCompressFile($file, $filegz)) {
127 $compressed = \true;
128 $file = $filegz;
129 $byteStart = 0;
130 $byteEnd = filesize($file);
131 }
132 }
133 }
134 \Piwik\Common::sendHeader('Last-Modified: ' . $lastModified);
135 if (!$phpOutputCompressionEnabled) {
136 \Piwik\Common::sendHeader('Content-Length: ' . ($byteEnd - $byteStart));
137 }
138 if (!empty($contentType)) {
139 \Piwik\Common::sendHeader('Content-Type: ' . $contentType);
140 }
141 if ($compressed) {
142 \Piwik\Common::sendHeader('Content-Encoding: ' . $encoding);
143 }
144 // in case any notices were triggered before this point (eg in WordPress) etc.
145 // it would break the gzipped response since it would have mixed regular notice/string plus gzipped content
146 // and would not be able to decode the response
147 $levels = ob_get_level();
148 for ($i = 0; $i < $levels; $i++) {
149 ob_end_clean();
150 }
151 // clearing all output buffers combined with output compressions had bugs on certain PHP versions
152 // manually removing the Content-Encoding header fixes this
153 // See https://github.com/php/php-src/issues/8218
154 if ($phpOutputCompressionEnabled && (version_compare(\PHP_VERSION, '8.0.17', '=') || version_compare(\PHP_VERSION, '8.0.18', '=') || version_compare(\PHP_VERSION, '8.1.4', '=') || version_compare(\PHP_VERSION, '8.1.5', '='))) {
155 header_remove("Content-Encoding");
156 }
157 if (!_readfile($file, $byteStart, $byteEnd)) {
158 \Piwik\Common::sendResponseCode(500);
159 }
160 }
161 /**
162 * Test if php output is compressed
163 *
164 * @return bool True if php output is (or suspected/likely) to be compressed
165 */
166 public static function isPhpOutputCompressed()
167 {
168 // Off = ''; On = '1'; otherwise, it's a buffer size
169 $zlibOutputCompression = ini_get('zlib.output_compression');
170 // could be ob_gzhandler, ob_deflatehandler, etc
171 $outputHandler = ini_get('output_handler');
172 // output handlers can be stacked
173 $obHandlers = array_filter(ob_list_handlers(), function ($var) {
174 return $var !== "default output handler";
175 });
176 // user defined handler via wrapper
177 if (!defined('PIWIK_TEST_MODE')) {
178 $autoPrependFile = ini_get('auto_prepend_file');
179 $autoAppendFile = ini_get('auto_append_file');
180 }
181 return !empty($zlibOutputCompression) || !empty($outputHandler) || !empty($obHandlers) || !empty($autoPrependFile) || !empty($autoAppendFile);
182 }
183 /**
184 * Workaround IE bug when downloading certain document types over SSL and
185 * cache control headers are present, e.g.,
186 *
187 * Cache-Control: no-cache
188 * Cache-Control: no-store,max-age=0,must-revalidate
189 * Pragma: no-cache
190 *
191 * @see https://support.microsoft.com/kb/316431/
192 * @see RFC2616
193 *
194 * @param string $override One of "public", "private", "no-cache", or "no-store". (optional)
195 */
196 public static function overrideCacheControlHeaders($override = null)
197 {
198 if ($override || self::isHttps()) {
199 \Piwik\Common::stripHeader('Pragma');
200 \Piwik\Common::stripHeader('Expires');
201 if (in_array($override, array('public', 'private', 'no-cache', 'no-store'))) {
202 \Piwik\Common::sendHeader("Cache-Control: {$override}, must-revalidate");
203 } else {
204 \Piwik\Common::sendHeader('Cache-Control: must-revalidate');
205 }
206 }
207 }
208 /**
209 * Returns a formatted Expires HTTP header for a certain number of days in the future. The result
210 * can be used in a call to `header()`.
211 */
212 private static function getExpiresHeaderForFutureDay($expireFarFutureDays)
213 {
214 return "Expires: " . gmdate('D, d M Y H:i:s', time() + 86400 * (int) $expireFarFutureDays) . ' GMT';
215 }
216 private static function getCompressionEncodingAcceptedByClient()
217 {
218 $acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
219 if (preg_match(self::GZIP_ENCODING_REGEX, $acceptEncoding, $matches)) {
220 return array('gzip', '.gz');
221 } elseif (preg_match(self::DEFLATE_ENCODING_REGEX, $acceptEncoding, $matches)) {
222 return array('deflate', '.deflate');
223 } else {
224 return array(\false, \false);
225 }
226 }
227 private static function canCompressInPhp()
228 {
229 return extension_loaded('zlib') && function_exists('file_get_contents') && function_exists('file_put_contents');
230 }
231 private static function shouldCompressFile($fileToCompress, $compressedFilePath)
232 {
233 $toCompressLastModified = @filemtime($fileToCompress);
234 $compressedLastModified = @filemtime($compressedFilePath);
235 return !file_exists($compressedFilePath) || $toCompressLastModified > $compressedLastModified;
236 }
237 private static function compressFile($fileToCompress, $compressedFilePath, $compressionEncoding, $byteStart, $byteEnd)
238 {
239 $data = file_get_contents($fileToCompress);
240 $data = substr($data, $byteStart, $byteEnd - $byteStart);
241 if ($compressionEncoding == 'deflate') {
242 $data = gzdeflate($data, 9);
243 } elseif ($compressionEncoding == 'gzip' || $compressionEncoding == 'x-gzip') {
244 $data = self::gzencode($data);
245 }
246 if (\false === $data) {
247 throw new \Exception('compressing file ' . $fileToCompress . ' failed');
248 }
249 file_put_contents($compressedFilePath, $data);
250 }
251 public static function gzencode($data)
252 {
253 return gzencode($data, 9);
254 }
255 }
256