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/output.php (3 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
 * Output functions
4
 * Processing text for output such as pulling out URLs and extracting excerpts
5
 *
6
 * @package Elgg
7
 * @subpackage Core
8
 */
9
10
/**
11
 * Takes a string and turns any URLs into formatted links
12
 *
13
 * @param string $text The input string
14
 *
15
 * @return string The output string with formatted links
16
 */
17
function parse_urls($text) {
18
19
	// URI specification: http://www.ietf.org/rfc/rfc3986.txt
20
	// This varies from the specification in the following ways:
21
	//  * Supports non-ascii characters
22
	//  * Does not allow parentheses and single quotes
23
	//  * Cuts off commas, exclamation points, and periods off as last character
24
25
	// @todo this causes problems with <attr = "val">
26
	// must be in <attr="val"> format (no space).
27
	// By default htmlawed rewrites tags to this format.
28
	// if PHP supported conditional negative lookbehinds we could use this:
29
	// $r = preg_replace_callback('/(?<!=)(?<![ ])?(?<!["\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\'\!\(\),]+)/i',
30
	$r = preg_replace_callback('/(?<![=\/"\'])((ht|f)tps?:\/\/[^\s\r\n\t<>"\']+)/i',
31
	create_function(
32
		'$matches',
33
		'
34
			$url = $matches[1];
35
			$punc = "";
36
			$last = substr($url, -1, 1);
37
			if (in_array($last, array(".", "!", ",", "(", ")"))) {
38
				$punc = $last;
39
				$url = rtrim($url, ".!,()");
40
			}
41
			$urltext = str_replace("/", "/<wbr />", $url);
42
			return "<a href=\"$url\" rel=\"nofollow\">$urltext</a>$punc";
43
		'
44
	), $text);
45
46
	return $r;
47
}
48
49
/**
50
 * Create paragraphs from text with line spacing
51
 *
52
 * @param string $string The string
53
 *
54
 * @return string
55
 **/
56
function elgg_autop($string) {
57
	return _elgg_services()->autoP->process($string);
58
}
59
60
/**
61
 * Returns an excerpt.
62
 * Will return up to n chars stopping at the nearest space.
63
 * If no spaces are found (like in Japanese) will crop off at the
64
 * n char mark. Adds ... if any text was chopped.
65
 *
66
 * @param string $text      The full text to excerpt
67
 * @param int    $num_chars Return a string up to $num_chars long
68
 *
69
 * @return string
70
 * @since 1.7.2
71
 */
72
function elgg_get_excerpt($text, $num_chars = 250) {
73
	$text = trim(elgg_strip_tags($text));
74
	$string_length = elgg_strlen($text);
75
76
	if ($string_length <= $num_chars) {
77
		return $text;
78
	}
79
80
	// handle cases
81
	$excerpt = elgg_substr($text, 0, $num_chars);
82
	$space = elgg_strrpos($excerpt, ' ', 0);
83
84
	// don't crop if can't find a space.
85
	if ($space === false) {
86
		$space = $num_chars;
87
	}
88
	$excerpt = trim(elgg_substr($excerpt, 0, $space));
89
90
	if ($string_length != elgg_strlen($excerpt)) {
91
		$excerpt .= '...';
92
	}
93
94
	return $excerpt;
95
}
96
97
/**
98
 * Handles formatting of ampersands in urls
99
 *
100
 * @param string $url The URL
101
 *
102
 * @return string
103
 * @since 1.7.1
104
 */
105
function elgg_format_url($url) {
106 1
	return preg_replace('/&(?!amp;)/', '&amp;', $url);
107
}
108
109
/**
110
 * Format bytes to a human readable format
111
 *
112
 * @param int $size      File size in bytes to format
113
 *
114
 * @param int $precision Precision to round formatting bytes to
115
 *
116
 * @return string
117
 * @since 1.9.0
118
 */
119
function elgg_format_bytes($size, $precision = 2) {
120
	if (!$size || $size < 0) {
121
		return false;
122
	}
123
124
	$base = log($size) / log(1024);
125
	$suffixes = array('B', 'kB', 'MB', 'GB', 'TB');   
126
127
	return round(pow(1024, $base - floor($base)), $precision) . ' ' . $suffixes[floor($base)];
128
}
129
130
/**
131
 * Converts an associative array into a string of well-formed attributes
132
 *
133
 * @note usually for HTML, but could be useful for XML too...
134
 *
135
 * @param array $attrs An associative array of attr => val pairs
136
 *
137
 * @return string HTML attributes to be inserted into a tag (e.g., <tag $attrs>)
138
 */
139
function elgg_format_attributes(array $attrs = array()) {
140
	if (!is_array($attrs) || !count($attrs)) {
141
		return '';
142
	}
143
144
	$attrs = _elgg_clean_vars($attrs);
145
	$attributes = array();
146
147
	if (isset($attrs['js'])) {
148
		elgg_deprecated_notice('Use associative array of attr => val pairs instead of $vars[\'js\']', 1.8);
149
150
		if (!empty($attrs['js'])) {
151
			$attributes[] = $attrs['js'];
152
		}
153
154
		unset($attrs['js']);
155
	}
156
157
	foreach ($attrs as $attr => $val) {
158
		$attr = strtolower($attr);
159
160
		if ($val === true) {
161
			$val = $attr; //e.g. checked => true ==> checked="checked"
162
		}
163
164
		/**
165
		 * Ignore non-array values and allow attribute values to be an array
166
		 *  <code>
167
		 *  $attrs = array(
168
		 *		'entity' => <\ElggObject>, // will be ignored
169
		 * 		'class' => array('elgg-input', 'elgg-input-text'), // will be imploded with spaces
170
		 * 		'style' => array('margin-left:10px;', 'color: #666;'), // will be imploded with spaces
171
		 *		'alt' => 'Alt text', // will be left as is
172
		 *  );
173
		 *  </code>
174
		 */
175
		if ($val !== NULL && $val !== false && (is_array($val) || !is_object($val))) {
176
			if (is_array($val)) {
177
				$val = implode(' ', $val);
178
			}
179
180
			$val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8', false);
181
			$attributes[] = "$attr=\"$val\"";
182
		}
183
	}
184
185
	return implode(' ', $attributes);
186
}
187
188
/**
189
 * Format an HTML element
190
 *
191
 * @param string $tag_name   The element tagName. e.g. "div". This will not be validated.
192
 *
193
 * @param array  $attributes The element attributes. This is passed to elgg_format_attributes().
194
 *
195
 * @param string $text       The contents of the element. Assumed to be HTML unless encode_text is true.
196
 *
197
 * @param array  $options    Options array with keys:
198
 *
199
 *   encode_text   => (bool, default false) If true, $text will be HTML-escaped. Already-escaped entities
200
 *                    will not be double-escaped.
201
 *
202
 *   double_encode => (bool, default false) If true, the $text HTML escaping will be allowed to double
203
 *                    encode HTML entities: '&times;' will become '&amp;times;'
204
 *
205
 *   is_void       => (bool) If given, this determines whether the function will return just the open tag.
206
 *                    Otherwise this will be determined by the tag name according to this list:
207
 *                    http://www.w3.org/html/wg/drafts/html/master/single-page.html#void-elements
208
 *
209
 *   is_xml        => (bool, default false) If true, void elements will be formatted like "<tag />"
210
 *
211
 * @return string
212
 * @throws InvalidArgumentException
213
 * @since 1.9.0
214
 */
215
function elgg_format_element($tag_name, array $attributes = array(), $text = '', array $options = array()) {
216
	if (!is_string($tag_name)) {
217
		throw new \InvalidArgumentException('$tag_name is required');
218
	}
219
220
	if (isset($options['is_void'])) {
221
		$is_void = $options['is_void'];
222
	} else {
223
		// from http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
224
		$is_void = in_array(strtolower($tag_name), array(
225
			'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem',
226
			'meta', 'param', 'source', 'track', 'wbr'
227
		));
228
	}
229
230
	if (!empty($options['encode_text'])) {
231
		$double_encode = empty($options['double_encode']) ? false : true;
232
		$text = htmlspecialchars($text, ENT_QUOTES, 'UTF-8', $double_encode);
233
	}
234
235
	if ($attributes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attributes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
236
		$attrs = elgg_format_attributes($attributes);
237
		if ($attrs !== '') {
238
			$attrs = " $attrs";
239
		}
240
	} else {
241
		$attrs = '';
242
	}
243
244
	if ($is_void) {
245
		return empty($options['is_xml']) ? "<{$tag_name}{$attrs}>" : "<{$tag_name}{$attrs} />";
246
	} else {
247
		return "<{$tag_name}{$attrs}>$text</$tag_name>";
248
	}
249
}
250
251
/**
252
 * Preps an associative array for use in {@link elgg_format_attributes()}.
253
 *
254
 * Removes all the junk that {@link elgg_view()} puts into $vars.
255
 * Maintains backward compatibility with attributes like 'internalname' and 'internalid'
256
 *
257
 * @note This function is called automatically by elgg_format_attributes(). No need to
258
 *       call it yourself before using elgg_format_attributes().
259
 *
260
 * @param array $vars The raw $vars array with all it's dirtiness (config, url, etc.)
261
 *
262
 * @return array The array, ready to be used in elgg_format_attributes().
263
 * @access private
264
 */
265
function _elgg_clean_vars(array $vars = array()) {
266
	unset($vars['config']);
267
	unset($vars['url']);
268
	unset($vars['user']);
269
270
	// backwards compatibility code
271
	if (isset($vars['internalname'])) {
272
		if (!isset($vars['__ignoreInternalname'])) {
273
			$vars['name'] = $vars['internalname'];
274
		}
275
		unset($vars['internalname']);
276
	}
277
278
	if (isset($vars['internalid'])) {
279
		if (!isset($vars['__ignoreInternalid'])) {
280
			$vars['id'] = $vars['internalid'];
281
		}
282
		unset($vars['internalid']);
283
	}
284
285
	if (isset($vars['__ignoreInternalid'])) {
286
		unset($vars['__ignoreInternalid']);
287
	}
288
289
	if (isset($vars['__ignoreInternalname'])) {
290
		unset($vars['__ignoreInternalname']);
291
	}
292
293
	return $vars;
294
}
295
296
/**
297
 * Converts shorthand urls to absolute urls.
298
 *
299
 * If the url is already absolute or protocol-relative, no change is made.
300
 *
301
 * @example
302
 * elgg_normalize_url('');                   // 'http://my.site.com/'
303
 * elgg_normalize_url('dashboard');          // 'http://my.site.com/dashboard'
304
 * elgg_normalize_url('http://google.com/'); // no change
305
 * elgg_normalize_url('//google.com/');      // no change
306
 *
307
 * @param string $url The URL to normalize
308
 *
309
 * @return string The absolute url
310
 */
311
function elgg_normalize_url($url) {
312
	// see https://bugs.php.net/bug.php?id=51192
313
	// from the bookmarks save action.
314 26
	$php_5_2_13_and_below = version_compare(PHP_VERSION, '5.2.14', '<');
315 26
	$php_5_3_0_to_5_3_2 = version_compare(PHP_VERSION, '5.3.0', '>=') &&
316 26
			version_compare(PHP_VERSION, '5.3.3', '<');
317
318 26 View Code Duplication
	if ($php_5_2_13_and_below || $php_5_3_0_to_5_3_2) {
319
		$tmp_address = str_replace("-", "", $url);
320
		$validated = filter_var($tmp_address, FILTER_VALIDATE_URL);
321
	} else {
322 26
		$validated = filter_var($url, FILTER_VALIDATE_URL);
323
	}
324
325
	// work around for handling absoluate IRIs (RFC 3987) - see #4190
326 26
	if (!$validated && (strpos($url, 'http:') === 0) || (strpos($url, 'https:') === 0)) {
327
		$validated = true;
328
	}
329
330 26
	if ($validated) {
331
		// all normal URLs including mailto:
332 1
		return $url;
333
334 26
	} elseif (preg_match("#^(\#|\?|//)#i", $url)) {
335
		// '//example.com' (Shortcut for protocol.)
336
		// '?query=test', #target
337 1
		return $url;
338
	
339 25
	} elseif (stripos($url, 'javascript:') === 0 || stripos($url, 'mailto:') === 0) {
340
		// 'javascript:' and 'mailto:'
341
		// Not covered in FILTER_VALIDATE_URL
342
		return $url;
343
344 25
	} elseif (preg_match("#^[^/]*\.php(\?.*)?$#i", $url)) {
345
		// 'install.php', 'install.php?step=step'
346
		return elgg_get_site_url() . $url;
347
348 25
	} elseif (preg_match("#^[^/?]*\.#i", $url)) {
349
		// 'example.com', 'example.com/subpage'
350
		return "http://$url";
351
352
	} else {
353
		// 'page/handler', 'mod/plugin/file.php'
354
355
		// trim off any leading / because the site URL is stored
356
		// with a trailing /
357 25
		return elgg_get_site_url() . ltrim($url, '/');
358
	}
359
}
360
361
/**
362
 * When given a title, returns a version suitable for inclusion in a URL
363
 *
364
 * @param string $title The title
365
 *
366
 * @return string The optimized title
367
 * @since 1.7.2
368
 */
369
function elgg_get_friendly_title($title) {
370
371
	// return a URL friendly title to short circuit normal title formatting
372
	$params = array('title' => $title);
373
	$result = elgg_trigger_plugin_hook('format', 'friendly:title', $params, null);
374
	if ($result) {
375
		return $result;
376
	}
377
378
	// titles are often stored HTML encoded
379
	$title = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
380
	
381
	$title = \Elgg\Translit::urlize($title);
382
383
	return $title;
384
}
385
386
/**
387
 * Formats a UNIX timestamp in a friendly way (eg "less than a minute ago")
388
 *
389
 * @see elgg_view_friendly_time()
390
 *
391
 * @param int $time         A UNIX epoch timestamp
392
 * @param int $current_time Current UNIX epoch timestamp (optional)
393
 *
394
 * @return string The friendly time string
395
 * @since 1.7.2
396
 */
397
function elgg_get_friendly_time($time, $current_time = null) {
398
	
399
	if (!$current_time) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $current_time of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
400
		$current_time = time();
401
	}
402
403
	// return a time string to short circuit normal time formatting
404
	$params = array('time' => $time, 'current_time' => $current_time);
405
	$result = elgg_trigger_plugin_hook('format', 'friendly:time', $params, null);
406
	if ($result) {
407
		return $result;
408
	}
409
410
	$diff = abs((int)$current_time - (int)$time);
411
412
	$minute = 60;
413
	$hour = $minute * 60;
414
	$day = $hour * 24;
415
416
	if ($diff < $minute) {
417
		return elgg_echo("friendlytime:justnow");
418
	}
419
	
420
	if ($diff < $hour) {
421
		$granularity = ':minutes';
422
		$diff = round($diff / $minute);
423
	} else if ($diff < $day) {
424
		$granularity = ':hours';
425
		$diff = round($diff / $hour);
426
	} else {
427
		$granularity = ':days';
428
		$diff = round($diff / $day);
429
	}
430
431
	if ($diff == 0) {
432
		$diff = 1;
433
	}
434
	
435
	$future = ((int)$current_time - (int)$time < 0) ? ':future' : '';
436
	$singular = ($diff == 1) ? ':singular' : '';
437
438
	return elgg_echo("friendlytime{$future}{$granularity}{$singular}", array($diff));
439
}
440
441
/**
442
 * Returns a human-readable message for PHP's upload error codes
443
 *
444
 * @param int $error_code The code as stored in $_FILES['name']['error']
445
 * @return string
446
 */
447
function elgg_get_friendly_upload_error($error_code) {
448
	switch ($error_code) {
449
		case UPLOAD_ERR_OK:
450
			return '';
451
			
452
		case UPLOAD_ERR_INI_SIZE:
453
			$key = 'ini_size';
454
			break;
455
		
456
		case UPLOAD_ERR_FORM_SIZE:
457
			$key = 'form_size';
458
			break;
459
460
		case UPLOAD_ERR_PARTIAL:
461
			$key = 'partial';
462
			break;
463
464
		case UPLOAD_ERR_NO_FILE:
465
			$key = 'no_file';
466
			break;
467
468
		case UPLOAD_ERR_NO_TMP_DIR:
469
			$key = 'no_tmp_dir';
470
			break;
471
472
		case UPLOAD_ERR_CANT_WRITE:
473
			$key = 'cant_write';
474
			break;
475
476
		case UPLOAD_ERR_EXTENSION:
477
			$key = 'extension';
478
			break;
479
		
480
		default:
481
			$key = 'unknown';
482
			break;
483
	}
484
485
	return elgg_echo("upload:error:$key");
486
}
487
488
489
/**
490
 * Strip tags and offer plugins the chance.
491
 * Plugins register for output:strip_tags plugin hook.
492
 * Original string included in $params['original_string']
493
 *
494
 * @param string $string         Formatted string
495
 * @param string $allowable_tags Optional parameter to specify tags which should not be stripped
496
 *
497
 * @return string String run through strip_tags() and any plugin hooks.
498
 */
499
function elgg_strip_tags($string, $allowable_tags = null) {
500
	$params['original_string'] = $string;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
501
	$params['allowable_tags'] = $allowable_tags;
502
503
	$string = strip_tags($string, $allowable_tags);
504
	$string = elgg_trigger_plugin_hook('format', 'strip_tags', $params, $string);
505
506
	return $string;
507
}
508
509
/**
510
 * Apply html_entity_decode() to a string while re-entitising HTML
511
 * special char entities to prevent them from being decoded back to their
512
 * unsafe original forms.
513
 *
514
 * This relies on html_entity_decode() not translating entities when
515
 * doing so leaves behind another entity, e.g. &amp;gt; if decoded would
516
 * create &gt; which is another entity itself. This seems to escape the
517
 * usual behaviour where any two paired entities creating a HTML tag are
518
 * usually decoded, i.e. a lone &gt; is not decoded, but &lt;foo&gt; would
519
 * be decoded to <foo> since it creates a full tag.
520
 *
521
 * Note: This function is poorly explained in the manual - which is really
522
 * bad given its potential for misuse on user input already escaped elsewhere.
523
 * Stackoverflow is littered with advice to use this function in the precise
524
 * way that would lead to user input being capable of injecting arbitrary HTML.
525
 *
526
 * @param string $string
527
 *
528
 * @return string
529
 *
530
 * @author Pádraic Brady
531
 * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
532
 * @license Released under dual-license GPL2/MIT by explicit permission of Pádraic Brady
533
 *
534
 * @access private
535
 */
536
function _elgg_html_decode($string) {
537
	$string = str_replace(
538
		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'),
539
		array('&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'),
540
		$string
541
	);
542
	$string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
543
	$string = str_replace(
544
		array('&amp;gt;', '&amp;lt;', '&amp;amp;', '&amp;quot;', '&amp;#039;'),
545
		array('&gt;', '&lt;', '&amp;', '&quot;', '&#039;'),
546
		$string
547
	);
548
	return $string;
549
}
550
551
/**
552
 * Prepares query string for output to prevent CSRF attacks.
553
 * 
554
 * @param string $string
555
 * @return string
556
 *
557
 * @access private
558
 */
559
function _elgg_get_display_query($string) {
560
	//encode <,>,&, quotes and characters above 127
561 View Code Duplication
	if (function_exists('mb_convert_encoding')) {
562
		$display_query = mb_convert_encoding($string, 'HTML-ENTITIES', 'UTF-8');
563
	} else {
564
		// if no mbstring extension, we just strip characters
565
		$display_query = preg_replace("/[^\x01-\x7F]/", "", $string);
566
	}
567
	return htmlspecialchars($display_query, ENT_QUOTES, 'UTF-8', false);
568
}
569
570
return function(\Elgg\EventsService $events, \Elgg\HooksRegistrationService $hooks) {
571
572
};
573