Issues (2473)

Branch: master

Security Analysis    no vulnerabilities found

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

engine/lib/filestore.php (2 issues)

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
/**
3
 * Elgg filestore.
4
 * This file contains functions for saving and retrieving data from files.
5
 *
6
 * @package Elgg.Core
7
 * @subpackage DataModel.FileStorage
8
 */
9
10
/**
11
 * Get the size of the specified directory.
12
 *
13
 * @param string $dir        The full path of the directory
14
 * @param int    $total_size Add to current dir size
15
 *
16
 * @return int The size of the directory in bytes
17
 */
18
function get_dir_size($dir, $total_size = 0) {
19
	$handle = @opendir($dir);
20
	while ($file = @readdir($handle)) {
21
		if (in_array($file, array('.', '..'))) {
22
			continue;
23
		}
24
		if (is_dir($dir . $file)) {
25
			$total_size = get_dir_size($dir . $file . "/", $total_size);
26
		} else {
27
			$total_size += filesize($dir . $file);
28
		}
29
	}
30
	@closedir($handle);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
31
32
	return($total_size);
33
}
34
35
/**
36
 * Get the contents of an uploaded file.
37
 * (Returns false if there was an issue.)
38
 *
39
 * @param string $input_name The name of the file input field on the submission form
40
 *
41
 * @return mixed|false The contents of the file, or false on failure.
42
 */
43
function get_uploaded_file($input_name) {
44
	$files = _elgg_services()->request->files;
45
	if (!$files->has($input_name)) {
46
		return false;
47
	}
48
49
	$file = $files->get($input_name);
50
	if (empty($file)) {
51
		// a file input was provided but no file uploaded
52
		return false;
53
	}
54
	if ($file->getError() !== 0) {
55
		return false;
56
	}
57
58
	return file_get_contents($file->getPathname());
59
}
60
61
/**
62
 * Gets the jpeg contents of the resized version of an uploaded image
63
 * (Returns false if the uploaded file was not an image)
64
 *
65
 * @param string $input_name The name of the file input field on the submission form
66
 * @param int    $maxwidth   The maximum width of the resized image
67
 * @param int    $maxheight  The maximum height of the resized image
68
 * @param bool   $square     If set to true, will take the smallest
69
 *                           of maxwidth and maxheight and use it to set the
70
 *                           dimensions on all size; the image will be cropped.
71
 * @param bool   $upscale    Resize images smaller than $maxwidth x $maxheight?
72
 *
73
 * @return false|mixed The contents of the resized image, or false on failure
74
 */
75
function get_resized_image_from_uploaded_file($input_name, $maxwidth, $maxheight,
76
$square = false, $upscale = false) {
77
	$files = _elgg_services()->request->files;
78
	if (!$files->has($input_name)) {
79
		return false;
80
	}
81
	
82
	$file = $files->get($input_name);
83
	if (empty($file)) {
84
		// a file input was provided but no file uploaded
85
		return false;
86
	}
87
	if ($file->getError() !== 0) {
88
		return false;
89
	}
90
91
	return get_resized_image_from_existing_file($file->getPathname(), $maxwidth,
92
		$maxheight, $square, 0, 0, 0, 0, $upscale);
93
}
94
95
/**
96
 * Gets the jpeg contents of the resized version of an already uploaded image
97
 * (Returns false if the file was not an image)
98
 *
99
 * @param string $input_name The name of the file on the disk
100
 * @param int    $maxwidth   The desired width of the resized image
101
 * @param int    $maxheight  The desired height of the resized image
102
 * @param bool   $square     If set to true, takes the smallest of maxwidth and
103
 * 			                 maxheight and use it to set the dimensions on the new image.
104
 *                           If no crop parameters are set, the largest square that fits
105
 *                           in the image centered will be used for the resize. If square,
106
 *                           the crop must be a square region.
107
 * @param int    $x1         x coordinate for top, left corner
108
 * @param int    $y1         y coordinate for top, left corner
109
 * @param int    $x2         x coordinate for bottom, right corner
110
 * @param int    $y2         y coordinate for bottom, right corner
111
 * @param bool   $upscale    Resize images smaller than $maxwidth x $maxheight?
112
 *
113
 * @return false|mixed The contents of the resized image, or false on failure
114
 */
115
function get_resized_image_from_existing_file($input_name, $maxwidth, $maxheight, $square = false,
116
$x1 = 0, $y1 = 0, $x2 = 0, $y2 = 0, $upscale = false) {
117
118
	// Get the size information from the image
119
	$imgsizearray = getimagesize($input_name);
120
	if ($imgsizearray == false) {
121
		return false;
122
	}
123
124
	$width = $imgsizearray[0];
125
	$height = $imgsizearray[1];
126
127
	$accepted_formats = array(
128
		'image/jpeg' => 'jpeg',
129
		'image/pjpeg' => 'jpeg',
130
		'image/png' => 'png',
131
		'image/x-png' => 'png',
132
		'image/gif' => 'gif'
133
	);
134
135
	// make sure the function is available
136
	$load_function = "imagecreatefrom" . $accepted_formats[$imgsizearray['mime']];
137
	if (!is_callable($load_function)) {
138
		return false;
139
	}
140
141
	// get the parameters for resizing the image
142
	$options = array(
143
		'maxwidth' => $maxwidth,
144
		'maxheight' => $maxheight,
145
		'square' => $square,
146
		'upscale' => $upscale,
147
		'x1' => $x1,
148
		'y1' => $y1,
149
		'x2' => $x2,
150
		'y2' => $y2,
151
	);
152
	$params = get_image_resize_parameters($width, $height, $options);
153
	if ($params == false) {
154
		return false;
155
	}
156
157
	// load original image
158
	$original_image = call_user_func($load_function, $input_name);
159
	if (!$original_image) {
160
		return false;
161
	}
162
163
	// allocate the new image
164
	$new_image = imagecreatetruecolor($params['newwidth'], $params['newheight']);
165
	if (!$new_image) {
166
		return false;
167
	}
168
169
	// color transparencies white (default is black)
170
	imagefilledrectangle(
171
		$new_image, 0, 0, $params['newwidth'], $params['newheight'],
172
		imagecolorallocate($new_image, 255, 255, 255)
173
	);
174
175
	$rtn_code = imagecopyresampled(	$new_image,
176
									$original_image,
177
									0,
178
									0,
179
									$params['xoffset'],
180
									$params['yoffset'],
181
									$params['newwidth'],
182
									$params['newheight'],
183
									$params['selectionwidth'],
184
									$params['selectionheight']);
185
	if (!$rtn_code) {
186
		return false;
187
	}
188
189
	// grab a compressed jpeg version of the image
190
	ob_start();
191
	imagejpeg($new_image, null, 90);
192
	$jpeg = ob_get_clean();
193
194
	imagedestroy($new_image);
195
	imagedestroy($original_image);
196
197
	return $jpeg;
198
}
199
200
/**
201
 * Calculate the parameters for resizing an image
202
 *
203
 * @param int   $width   Width of the original image
204
 * @param int   $height  Height of the original image
205
 * @param array $options See $defaults for the options
206
 *
207
 * @return array or false
208
 * @since 1.7.2
209
 */
210
function get_image_resize_parameters($width, $height, $options) {
211
212
	$defaults = array(
213
		'maxwidth' => 100,
214
		'maxheight' => 100,
215
216
		'square' => false,
217
		'upscale' => false,
218
219
		'x1' => 0,
220
		'y1' => 0,
221
		'x2' => 0,
222
		'y2' => 0,
223
	);
224
225
	$options = array_merge($defaults, $options);
226
227
	// Avoiding extract() because it hurts static analysis
228
	$maxwidth = $options['maxwidth'];
229
	$maxheight = $options['maxheight'];
230
	$square = $options['square'];
231
	$upscale = $options['upscale'];
232
	$x1 = $options['x1'];
233
	$y1 = $options['y1'];
234
	$x2 = $options['x2'];
235
	$y2 = $options['y2'];
236
237
	// crop image first?
238
	$crop = true;
239 View Code Duplication
	if ($x1 == 0 && $y1 == 0 && $x2 == 0 && $y2 == 0) {
240
		$crop = false;
241
	}
242
243
	// how large a section of the image has been selected
244
	if ($crop) {
245
		$selection_width = $x2 - $x1;
246
		$selection_height = $y2 - $y1;
247
	} else {
248
		// everything selected if no crop parameters
249
		$selection_width = $width;
250
		$selection_height = $height;
251
	}
252
253
	// determine cropping offsets
254
	if ($square) {
255
		// asking for a square image back
256
257
		// detect case where someone is passing crop parameters that are not for a square
258
		if ($crop == true && $selection_width != $selection_height) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
259
			return false;
260
		}
261
262
		// size of the new square image
263
		$new_width = $new_height = min($maxwidth, $maxheight);
264
265
		// find largest square that fits within the selected region
266
		$selection_width = $selection_height = min($selection_width, $selection_height);
267
268
		// set offsets for crop
269 View Code Duplication
		if ($crop) {
270
			$widthoffset = $x1;
271
			$heightoffset = $y1;
272
			$width = $x2 - $x1;
273
			$height = $width;
274
		} else {
275
			// place square region in the center
276
			$widthoffset = floor(($width - $selection_width) / 2);
277
			$heightoffset = floor(($height - $selection_height) / 2);
278
		}
279
	} else {
280
		// non-square new image
281
		$new_width = $maxwidth;
282
		$new_height = $maxheight;
283
284
		// maintain aspect ratio of original image/crop
285 View Code Duplication
		if (($selection_height / (float)$new_height) > ($selection_width / (float)$new_width)) {
286
			$new_width = floor($new_height * $selection_width / (float)$selection_height);
287
		} else {
288
			$new_height = floor($new_width * $selection_height / (float)$selection_width);
289
		}
290
291
		// by default, use entire image
292
		$widthoffset = 0;
293
		$heightoffset = 0;
294
295
		if ($crop) {
296
			$widthoffset = $x1;
297
			$heightoffset = $y1;
298
		}
299
	}
300
301
	if (!$upscale && ($selection_height < $new_height || $selection_width < $new_width)) {
302
		// we cannot upscale and selected area is too small so we decrease size of returned image
303
		if ($square) {
304
			$new_height = $selection_height;
305
			$new_width = $selection_width;
306
		} else {
307
			if ($selection_height < $new_height && $selection_width < $new_width) {
308
				$new_height = $selection_height;
309
				$new_width = $selection_width;
310
			}
311
		}
312
	}
313
314
	$params = array(
315
		'newwidth' => $new_width,
316
		'newheight' => $new_height,
317
		'selectionwidth' => $selection_width,
318
		'selectionheight' => $selection_height,
319
		'xoffset' => $widthoffset,
320
		'yoffset' => $heightoffset,
321
	);
322
323
	return $params;
324
}
325
326
/**
327
 * Delete an \ElggFile file
328
 *
329
 * @param int $guid \ElggFile GUID
330
 *
331
 * @return bool
332
 */
333
function file_delete($guid) {
334
	$file = get_entity($guid);
335
	if (!$file || !$file->canEdit()) {
336
		return false;
337
	}
338
339
	$thumbnail = $file->thumbnail;
340
	$smallthumb = $file->smallthumb;
341
	$largethumb = $file->largethumb;
342
	if ($thumbnail) {
343
		$delfile = new \ElggFile();
344
		$delfile->owner_guid = $file->owner_guid;
345
		$delfile->setFilename($thumbnail);
346
		$delfile->delete();
347
	}
348 View Code Duplication
	if ($smallthumb) {
349
		$delfile = new \ElggFile();
350
		$delfile->owner_guid = $file->owner_guid;
351
		$delfile->setFilename($smallthumb);
352
		$delfile->delete();
353
	}
354
	if ($largethumb) {
355
		$delfile = new \ElggFile();
356
		$delfile->owner_guid = $file->owner_guid;
357
		$delfile->setFilename($largethumb);
358
		$delfile->delete();
359
	}
360
361
	return $file->delete();
362
}
363
364
/**
365
 * Delete a directory and all its contents
366
 *
367
 * @param string $directory Directory to delete
368
 *
369
 * @return bool
370
 */
371
function delete_directory($directory) {
372
	// sanity check: must be a directory
373
	if (!$handle = opendir($directory)) {
374
		return false;
375
	}
376
377
	// loop through all files
378
	while (($file = readdir($handle)) !== false) {
379
		if (in_array($file, array('.', '..'))) {
380
			continue;
381
		}
382
383
		$path = "$directory/$file";
384
		if (is_dir($path)) {
385
			// recurse down through directory
386
			if (!delete_directory($path)) {
387
				return false;
388
			}
389
		} else {
390
			// delete file
391
			unlink($path);
392
		}
393
	}
394
395
	// remove empty directory
396
	closedir($handle);
397
	return rmdir($directory);
398
}
399
400
/**
401
 * Removes all entity files
402
 *
403
 * @warning This only deletes the physical files and not their entities.
404
 * This will result in FileExceptions being thrown.  Don't use this function.
405
 *
406
 * @warning This must be kept in sync with \ElggDiskFilestore.
407
 *
408
 * @todo Remove this when all files are entities.
409
 *
410
 * @param \ElggEntity $entity An \ElggEntity
411
 *
412
 * @return void
413
 * @access private
414
 */
415
function _elgg_clear_entity_files($entity) {
416
	$dir = new \Elgg\EntityDirLocator($entity->guid);
417
	$file_path = elgg_get_config('dataroot') . $dir;
418
	if (file_exists($file_path)) {
419
		delete_directory($file_path);
420
	}
421
}
422
423
424
/// Variable holding the default datastore
425
$DEFAULT_FILE_STORE = null;
426
427
/**
428
 * Return the default filestore.
429
 *
430
 * @return \ElggFilestore
431
 */
432
function get_default_filestore() {
433
	global $DEFAULT_FILE_STORE;
434
435
	return $DEFAULT_FILE_STORE;
436
}
437
438
/**
439
 * Set the default filestore for the system.
440
 *
441
 * @param \ElggFilestore $filestore An \ElggFilestore object.
442
 *
443
 * @return true
444
 */
445
function set_default_filestore(\ElggFilestore $filestore) {
446
	global $DEFAULT_FILE_STORE;
447
448
	$DEFAULT_FILE_STORE = $filestore;
449
450
	return true;
451
}
452
453
/**
454
 * Returns the category of a file from its MIME type
455
 *
456
 * @param string $mime_type The MIME type
457
 *
458
 * @return string 'document', 'audio', 'video', or 'general' if the MIME type was unrecognized
459
 * @since 1.10
460
 */
461
function elgg_get_file_simple_type($mime_type) {
462
	$params = array('mime_type' => $mime_type);
463
	return elgg_trigger_plugin_hook('simple_type', 'file', $params, 'general');
464
}
465
466
/**
467
 * Initialize the file library.
468
 * Listens to system init and configures the default filestore
469
 *
470
 * @return void
471
 * @access private
472
 */
473
function _elgg_filestore_init() {
474
	global $CONFIG;
475
476
	// Now register a default filestore
477
	if (isset($CONFIG->dataroot)) {
478
		set_default_filestore(new \ElggDiskFilestore($CONFIG->dataroot));
479
	}
480
481
	// Fix MIME type detection for Microsoft zipped formats
482
	elgg_register_plugin_hook_handler('mime_type', 'file', '_elgg_filestore_detect_mimetype');
483
	
484
	// Parse category of file from MIME type
485
	elgg_register_plugin_hook_handler('simple_type', 'file', '_elgg_filestore_parse_simpletype');
486
487
	// Unit testing
488
	elgg_register_plugin_hook_handler('unit_test', 'system', '_elgg_filestore_test');
489
}
490
491
/**
492
 * Fix MIME type detection for Microsoft zipped formats
493
 *
494
 * @param string $hook      "mime_type"
495
 * @param string $type      "file"
496
 * @param string $mime_type Detected MIME type
497
 * @param array  $params    Hook parameters
498
 *
499
 * @return string The MIME type
500
 * @access private
501
 */
502
function _elgg_filestore_detect_mimetype($hook, $type, $mime_type, $params) {
503
504
	$original_filename = elgg_extract('original_filename', $params);
505
506
	$info = pathinfo($original_filename);
507
508
	// hack for Microsoft zipped formats
509
	$office_formats = array('docx', 'xlsx', 'pptx');
510 View Code Duplication
	if ($mime_type == "application/zip" && in_array($info['extension'], $office_formats)) {
511
		switch ($info['extension']) {
512
			case 'docx':
513
				$mime_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
514
				break;
515
			case 'xlsx':
516
				$mime_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
517
				break;
518
			case 'pptx':
519
				$mime_type = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
520
				break;
521
		}
522
	}
523
524
	// check for bad ppt detection
525
	if ($mime_type == "application/vnd.ms-office" && $info['extension'] == "ppt") {
526
		$mime_type = "application/vnd.ms-powerpoint";
527
	}
528
529
	return $mime_type;
530
}
531
532
/**
533
 * Parse a file category of file from a MIME type
534
 *
535
 * @param string $hook        "simple_type"
536
 * @param string $type        "file"
537
 * @param string $simple_type The category of file
538
 * @param array  $params      Hook parameters
539
 *
540
 * @return string 'document', 'audio', 'video', or 'general' if the MIME type is unrecognized
541
 * @access private
542
 */
543
function _elgg_filestore_parse_simpletype($hook, $type, $simple_type, $params) {
544
545
	$mime_type = elgg_extract('mime_type', $params);
546
547
	switch ($mime_type) {
548
		case "application/msword":
549
		case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
550
		case "application/pdf":
551
			return "document";
552
553
		case "application/ogg":
554
			return "audio";
555
	}
556
557
	if (preg_match('~^(audio|image|video)/~', $mime_type, $m)) {
558
		return $m[1];
559
	}
560
	if (0 === strpos($mime_type, 'text/') || false !== strpos($mime_type, 'opendocument')) {
561
		return "document";
562
	}
563
564
	// unrecognized MIME
565
	return $simple_type;
566
}
567
568
/**
569
 * Unit tests for files
570
 *
571
 * @param string $hook   unit_test
572
 * @param string $type   system
573
 * @param mixed  $value  Array of tests
574
 *
575
 * @return array
576
 * @access private
577
 */
578
function _elgg_filestore_test($hook, $type, $value) {
579
	global $CONFIG;
580
	$value[] = "{$CONFIG->path}engine/tests/ElggCoreFilestoreTest.php";
581
	return $value;
582
}
583
584
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
585
	$events->registerHandler('init', 'system', '_elgg_filestore_init', 100);
586
};
587