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