PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / trunk
Matomo Analytics – Powerful, Privacy-First Insights for WordPress vtrunk
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 1 month ago Access 3 months ago Application 1 month ago Archive 1 month ago ArchiveProcessor 1 month ago Archiver 2 years ago AssetManager 1 month ago Auth 6 months ago Category 6 months ago Changes 1 month ago CliMulti 1 year ago Columns 1 month ago Concurrency 1 month ago Config 1 month ago Container 1 month ago CronArchive 3 months ago DataAccess 1 month ago DataFiles 2 years ago DataTable 2 weeks ago Db 2 weeks 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 1 month ago Metrics 3 months ago Notification 6 months ago Period 1 month ago Plugin 2 weeks ago Policy 1 month ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 3 months ago Request 3 months ago Scheduler 1 month ago Segment 1 month ago Session 2 weeks ago Settings 1 month ago Tracker 2 weeks ago Translation 1 month ago Twig 1 year ago UpdateCheck 3 months ago Updater 1 month ago Updates 2 days ago Validators 1 year ago View 1 month ago ViewDataTable 2 weeks ago Visualization 1 year ago Widget 1 month ago .htaccess 2 years ago Access.php 1 month ago Archive.php 1 month ago ArchiveProcessor.php 1 month ago AssetManager.php 1 month 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 1 month ago Common.php 2 weeks ago Config.php 1 month ago Console.php 3 months ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 1 month ago DI.php 3 months ago DataArray.php 1 month ago DataTable.php 1 month ago Date.php 1 month ago Db.php 1 month ago DbHelper.php 1 month ago Development.php 1 year ago ErrorHandler.php 6 months ago EventDispatcher.php 1 month ago ExceptionHandler.php 4 months ago FileIntegrity.php 1 month ago Filechecks.php 1 year ago Filesystem.php 1 month ago FrontController.php 4 months ago Http.php 1 month ago IP.php 1 year ago Log.php 3 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 1 month ago NoAccessException.php 2 years ago Nonce.php 6 months ago Notification.php 1 month ago NumberFormatter.php 5 months ago Option.php 5 months ago Period.php 1 month ago Piwik.php 1 month ago Plugin.php 1 month ago Process.php 1 month ago Profiler.php 6 months ago ProxyHeaders.php 4 months ago ProxyHttp.php 5 months ago QuickForm2.php 3 months ago RankingQuery.php 1 month ago ReportRenderer.php 1 month ago Request.php 1 month ago Segment.php 1 month ago Sequence.php 6 months ago Session.php 2 weeks ago SettingsPiwik.php 1 month ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 1 month ago SiteContentDetector.php 1 month ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 1 month ago Tracker.php 1 month ago Twig.php 1 month ago Unzip.php 1 year ago UpdateCheck.php 1 month ago Updater.php 1 month ago UpdaterErrorException.php 2 years ago Updates.php 3 months ago Url.php 3 months ago UrlHelper.php 1 month ago Version.php 2 days ago View.php 1 month 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