Test Failed
Push — master ( 8c47c2...3acf9f )
by Steve
12:37
created

engine/classes/Elgg/Application/CacheHandler.php (1 issue)

mismatching argument types.

Documentation Minor

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Elgg\Application;
3
4
use Elgg\Application;
5
use Elgg\Config;
6
7
8
/**
9
 * Simplecache handler
10
 *
11
 * @access private
12
 *
13
 * @package Elgg.Core
14
 */
15
class CacheHandler {
16
	
17
	public static $extensions = [
18
		'bmp' => "image/bmp",
19
		'css' => "text/css",
20
		'gif' => "image/gif",
21
		'html' => "text/html",
22
		'ico' => "image/x-icon",
23
		'jpeg' => "image/jpeg",
24
		'jpg' => "image/jpeg",
25
		'js' => "application/javascript",
26
		'json' => "application/json",
27
		'png' => "image/png",
28
		'svg' => "image/svg+xml",
29
		'swf' => "application/x-shockwave-flash",
30
		'tiff' => "image/tiff",
31
		'webp' => "image/webp",
32
		'xml' => "text/xml",
33
		'eot' => "application/vnd.ms-fontobject",
34
		'ttf' => "application/font-ttf",
35
		'woff' => "application/font-woff",
36
		'woff2' => "application/font-woff2",
37
		'otf' => "application/font-otf",
38
	];
39
40
	public static $utf8_content_types = [
41
		"text/css",
42
		"text/html",
43
		"application/javascript",
44
		"application/json",
45
		"image/svg+xml",
46
		"text/xml",
47
	];
48
49
	/** @var Application */
50
	private $application;
51
52
	/** @var Config */
53
	private $config;
54
55
	/** @var array */
56
	private $server_vars;
57
58
	/**
59
	 * Constructor
60
	 *
61
	 * @param Application $app         Elgg Application
62
	 * @param Config      $config      Elgg configuration
63
	 * @param array       $server_vars Server vars
64
	 */
65 6
	public function __construct(Application $app, Config $config, $server_vars) {
66 6
		$this->application = $app;
67 6
		$this->config = $config;
68 6
		$this->server_vars = $server_vars;
69 6
	}
70
71
	/**
72
	 * Handle a request for a cached view
73
	 *
74
	 * @param array $path URL path
75
	 * @return void
76
	 */
77
	public function handleRequest($path) {
78
		$config = $this->config;
79
		
80
		$request = $this->parsePath($path);
0 ignored issues
show
$path is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
81
		if (!$request) {
82
			$this->send403();
83
		}
84
		
85
		$ts = $request['ts'];
86
		$view = $request['view'];
87
		$viewtype = $request['viewtype'];
88
89
		$content_type = $this->getContentType($view);
90
		if (empty($content_type)) {
91
			$this->send403("Asset must have a valid file extension");
92
		}
93
94
		if (in_array($content_type, self::$utf8_content_types)) {
95
			header("Content-Type: $content_type;charset=utf-8");
96
		} else {
97
			header("Content-Type: $content_type");
98
		}
99
100
		// we can't use $config->get yet. It fails before the core is booted
101
		if (!$config->get('simplecache_enabled')) {
102
			$this->application->bootCore();
103
104
			if (!$this->isCacheableView($view)) {
105
				$this->send403("Requested view is not an asset");
106
			} else {
107
				$content = $this->getProcessedView($view, $viewtype);
108
				$etag = '"' . md5($content) . '"';
109
				$this->sendRevalidateHeaders($etag);
110
				$this->handle304($etag);
111
112
				echo $content;
113
			}
114
			exit;
115
		}
116
117
		$etag = "\"$ts\"";
118
		$this->handle304($etag);
119
120
		// trust the client but check for an existing cache file
121
		$filename = $config->getCachePath() . "views_simplecache/$ts/$viewtype/$view";
122
		if (file_exists($filename)) {
123
			$this->sendCacheHeaders($etag);
124
			readfile($filename);
125
			exit;
126
		}
127
128
		// the hard way
129
		$this->application->bootCore();
130
131
		elgg_set_viewtype($viewtype);
132
		if (!$this->isCacheableView($view)) {
133
			$this->send403("Requested view is not an asset");
134
		}
135
136
		$lastcache = (int) $config->get('lastcache');
137
138
		$filename = $config->getCachePath() . "views_simplecache/$lastcache/$viewtype/$view";
139
140
		if ($lastcache == $ts) {
141
			$this->sendCacheHeaders($etag);
142
143
			$content = $this->getProcessedView($view, $viewtype);
144
145
			$dir_name = dirname($filename);
146
			if (!is_dir($dir_name)) {
147
				// PHP and the server accessing the cache symlink may be a different user. And here
148
				// it's safe to make everything readable anyway.
149
				mkdir($dir_name, 0775, true);
150
			}
151
152
			file_put_contents($filename, $content);
153
			chmod($filename, 0664);
154
		} else {
155
			// if wrong timestamp, don't send HTTP cache
156
			$content = $this->getProcessedView($view, $viewtype);
157
		}
158
159
		echo $content;
160
		exit;
161
	}
162
163
	/**
164
	 * Parse a request
165
	 *
166
	 * @param string $path Request URL path
167
	 * @return array Cache parameters (empty array if failure)
168
	 */
169 6
	public function parsePath($path) {
170
		// no '..'
171 6
		if (false !== strpos($path, '..')) {
172 1
			return [];
173
		}
174
		// only alphanumeric characters plus /, ., -, and _
175 5
		if (preg_match('#[^a-zA-Z0-9/\.\-_]#', $path)) {
176 1
			return [];
177
		}
178
179
		// testing showed regex to be marginally faster than array / string functions over 100000 reps
180
		// it won't make a difference in real life and regex is easier to read.
181
		// <ts>/<viewtype>/<name/of/view.and.dots>.<type>
182 4
		if (!preg_match('#^/cache/([0-9]+)/([^/]+)/(.+)$#', $path, $matches)) {
183 3
			return [];
184
		}
185
186
		return [
187 1
			'ts' => $matches[1],
188 1
			'viewtype' => $matches[2],
189 1
			'view' => $matches[3],
190
		];
191
	}
192
193
	/**
194
	 * Is the view cacheable. Language views are handled specially.
195
	 *
196
	 * @param string $view View name
197
	 *
198
	 * @return bool
199
	 */
200
	protected function isCacheableView($view) {
201
		if (preg_match('~^languages/(.*)\.js$~', $view, $m)) {
202
			return in_array($m[1],  _elgg_services()->translator->getAllLanguageCodes());
203
		}
204
		return _elgg_services()->views->isCacheableView($view);
205
	}
206
207
	/**
208
	 * Send cache headers
209
	 *
210
	 * @param string $etag ETag value
211
	 * @return void
212
	 */
213
	protected function sendCacheHeaders($etag) {
214
		header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', strtotime("+6 months")), true);
215
		header("Pragma: public", true);
216
		header("Cache-Control: public", true);
217
		header("ETag: $etag");
218
	}
219
220
	/**
221
	 * Send revalidate cache headers
222
	 *
223
	 * @param string $etag ETag value
224
	 * @return void
225
	 */
226
	protected function sendRevalidateHeaders($etag) {
227
		header_remove('Expires');
228
		header("Pragma: public", true);
229
		header("Cache-Control: public, max-age=0, must-revalidate", true);
230
		header("ETag: $etag");
231
	}
232
233
	/**
234
	 * Send a 304 and exit() if the ETag matches the request
235
	 *
236
	 * @param string $etag ETag value
237
	 * @return void
238
	 */
239
	protected function handle304($etag) {
240
		if (!isset($this->server_vars['HTTP_IF_NONE_MATCH'])) {
241
			return;
242
		}
243
244
		// strip -gzip for #9427
245
		$if_none_match = str_replace('-gzip', '', trim($this->server_vars['HTTP_IF_NONE_MATCH']));
246
		if ($if_none_match === $etag) {
247
			header("HTTP/1.1 304 Not Modified");
248
			exit;
249
		}
250
	}
251
252
	/**
253
	 * Get the content type
254
	 *
255
	 * @param string $view The view name
256
	 *
257
	 * @return string|null
258
	 */
259
	protected function getContentType($view) {
260
		$extension = $this->getViewFileType($view);
261
		
262
		if (isset(self::$extensions[$extension])) {
263
			return self::$extensions[$extension];
264
		} else {
265
			return null;
266
		}
267
	}
268
	
269
	/**
270
	 * Returns the type of output expected from the view.
271
	 *
272
	 *  - view/name.extension returns "extension" if "extension" is valid
273
	 *  - css/view return "css"
274
	 *  - js/view return "js"
275
	 *  - Otherwise, returns "unknown"
276
	 *
277
	 * @param string $view The view name
278
	 * @return string
279
	 */
280
	private function getViewFileType($view) {
281
		$extension = (new \SplFileInfo($view))->getExtension();
282
		$hasValidExtension = isset(self::$extensions[$extension]);
283
284
		if ($hasValidExtension) {
285
			return $extension;
286
		}
287
		
288
		if (preg_match('~(?:^|/)(css|js)(?:$|/)~', $view, $m)) {
289
			return $m[1];
290
		}
291
		
292
		return 'unknown';
293
	}
294
295
	/**
296
	 * Get the contents of a view for caching
297
	 *
298
	 * @param string $view     The view name
299
	 * @param string $viewtype The viewtype
300
	 * @return string
301
	 * @see CacheHandler::renderView()
302
	 */
303
	protected function getProcessedView($view, $viewtype) {
304
		$content = $this->renderView($view, $viewtype);
305
306
		if ($this->config->getVolatile('simplecache_enabled')) {
307
			$hook_name = 'simplecache:generate';
308
		} else {
309
			$hook_name = 'cache:generate';
310
		}
311
		$hook_type = $this->getViewFileType($view);
312
		$hook_params = [
313
			'view' => $view,
314
			'viewtype' => $viewtype,
315
			'view_content' => $content,
316
		];
317
		return \_elgg_services()->hooks->trigger($hook_name, $hook_type, $hook_params, $content);
318
	}
319
320
	/**
321
	 * Render a view for caching. Language views are handled specially.
322
	 *
323
	 * @param string $view     The view name
324
	 * @param string $viewtype The viewtype
325
	 * @return string
326
	 */
327
	protected function renderView($view, $viewtype) {
328
		elgg_set_viewtype($viewtype);
329
330
		if ($viewtype === 'default' && preg_match("#^languages/(.*?)\\.js$#", $view, $matches)) {
331
			$view = "languages.js";
332
			$vars = ['language' => $matches[1]];
333
		} else {
334
			$vars = [];
335
		}
336
337
		if (!elgg_view_exists($view)) {
338
			$this->send403();
339
		}
340
341
		// disable error reporting so we don't cache problems
342
		$this->config->set('debug', null);
343
344
		return elgg_view($view, $vars);
345
	}
346
347
	/**
348
	 * Send an error message to requestor
349
	 *
350
	 * @param string $msg Optional message text
351
	 * @return void
352
	 */
353
	protected function send403($msg = 'Cache error: bad request') {
354
		header('HTTP/1.1 403 Forbidden');
355
		echo $msg;
356
		exit;
357
	}
358
}
359
360