PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.2.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.2.0
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 year ago Access 1 year ago Application 1 year ago Archive 1 year ago ArchiveProcessor 1 year ago Archiver 2 years ago AssetManager 1 year ago Auth 1 year ago Category 2 years ago Changes 1 year ago CliMulti 1 year ago Columns 1 year ago Concurrency 1 year ago Config 1 year ago Container 1 year ago CronArchive 1 year ago DataAccess 1 year ago DataFiles 2 years ago DataTable 1 year ago Db 1 year ago DeviceDetector 1 year ago Email 2 years ago Exception 1 year ago Http 1 year ago Intl 1 year ago Log 2 years ago Mail 1 year ago Measurable 1 year ago Menu 1 year ago Metrics 1 year ago Notification 1 year ago Period 1 year ago Plugin 1 year ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 1 year ago Scheduler 1 year ago Segment 1 year ago Session 1 year ago Settings 1 year ago Tracker 1 year ago Translation 1 year ago Twig 1 year ago UpdateCheck 1 year ago Updater 1 year ago Updates 1 year ago Validators 1 year ago View 1 year ago ViewDataTable 1 year ago Visualization 1 year ago Widget 1 year ago .htaccess 2 years ago Access.php 1 year ago Archive.php 1 year ago ArchiveProcessor.php 1 year ago AssetManager.php 1 year ago Auth.php 2 years ago AuthResult.php 2 years ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 1 year ago CliMulti.php 1 year ago Common.php 1 year ago Config.php 1 year ago Console.php 1 year ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 1 year ago DI.php 1 year ago DataArray.php 1 year ago DataTable.php 1 year ago Date.php 1 year ago Db.php 1 year ago DbHelper.php 1 year ago Development.php 1 year ago ErrorHandler.php 1 year ago EventDispatcher.php 1 year ago ExceptionHandler.php 1 year ago FileIntegrity.php 1 year ago Filechecks.php 1 year ago Filesystem.php 1 year ago FrontController.php 1 year ago Http.php 1 year ago IP.php 1 year ago Log.php 2 years ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 1 year ago NoAccessException.php 2 years ago Nonce.php 1 year ago Notification.php 1 year ago NumberFormatter.php 1 year ago Option.php 1 year ago Period.php 1 year ago Piwik.php 1 year ago Plugin.php 1 year ago Process.php 1 year ago Profiler.php 1 year ago ProxyHeaders.php 2 years ago ProxyHttp.php 1 year ago QuickForm2.php 1 year ago RankingQuery.php 1 year ago ReportRenderer.php 1 year ago Request.php 1 year ago Segment.php 1 year ago Sequence.php 2 years ago Session.php 1 year ago SettingsPiwik.php 1 year ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 1 year ago SiteContentDetector.php 1 year ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 1 year ago Twig.php 1 year ago Unzip.php 1 year ago UpdateCheck.php 1 year ago Updater.php 1 year ago UpdaterErrorException.php 2 years ago Updates.php 1 year ago Url.php 1 year ago UrlHelper.php 1 year ago Version.php 1 year ago View.php 1 year ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 2 years 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 bool $expireFarFuture 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 http://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