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