Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

browserDependingHTTPHeaderEncode()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
	/**
3
	 * Utility functions
4
	 *
5
	 * @package core
6
	 */
7
8
	 require_once(BASE_PATH . 'server/includes/exceptions/class.JSONException.php');
9
10
	/**
11
	 * Function which reads the data stream. This data is send by the WebClient.
12
	 * @return string data
13
	 */
14
	function readData() {
15
		$data = "";
16
		$putData = fopen("php://input", "r");
17
18
		while($block = fread($putData, 1024))
19
		{
20
			$data .= $block;
21
		}
22
23
		fclose($putData);
24
		return $data;
25
	}
26
27
	/*
28
     * Add in config specified default domain to email if no domain is set in form.
29
     * If no default domain is set in config, the input string will be return without changes.
30
     *
31
     * @param string user the user to append domain to
0 ignored issues
show
Bug introduced by
The type user was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
32
     * @return string email
33
     */
34
    function appendDefaultDomain($user)
35
    {
36
			if(empty($user)) return '';
37
			if ( !defined('DEFAULT_DOMAIN') || strpos($user, '@') !== false) return $user;
38
			$email = $user . "@" . DEFAULT_DOMAIN;
0 ignored issues
show
Bug introduced by
The constant DEFAULT_DOMAIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
39
40
			return $email;
41
    }
42
43
	/**
44
	 * Function which is called every time the "session_start" method is called.
45
	 * It unserializes the objects in the session. This function called by PHP.
46
	 * @param string @className the className of the object in the session
0 ignored issues
show
Documentation Bug introduced by
The doc comment @className at position 0 could not be parsed: Unknown type name '@className' at position 0 in @className.
Loading history...
47
	 */
48
	function sessionNotifierLoader($className)
49
	{
50
		$className = strtolower($className); // for PHP5 set className to lower case to find the file (see ticket #839 for more information)
51
52
		switch($className)
53
		{
54
			case "bus":
55
				require_once(BASE_PATH . 'server/includes/core/class.bus.php');
56
				break;
57
58
			default:
59
				$path = BASE_PATH . 'server/includes/notifiers/class.' . $className . '.php';
60
				if (is_file($path)) {
61
					require_once($path);
62
				} else {
63
					$path = $GLOBALS['PluginManager']->getNotifierFilePath($className);
64
					if (is_file($path)) {
65
						require_once($path);
66
					}
67
				}
68
				break;
69
		}
70
		if (!class_exists($className)){
71
			trigger_error("Can't load ".$className." while unserializing the session.", E_USER_WARNING);
72
		}
73
	}
74
75
	/**
76
	 * Function which checks if an array is an associative array.
77
	 * @param array $data array which should be verified
78
	 * @return boolean true if the given array is an associative array, false if not
79
	 */
80
	function is_assoc_array($data) {
81
		return is_array($data) && !empty($data) && !preg_match('/^\d+$/', implode('', array_keys($data)));
82
	}
83
84
	/**
85
	 * gets maximum upload size of attachment from php ini settings
86
	 * important settings are upload_max_filesize and post_max_size
87
	 * upload_max_filesize specifies maximum upload size for attachments
88
	 * post_max_size must be larger then upload_max_filesize.
89
	 * these values are overwritten in .htaccess file of WA
90
	 *
91
	 * @return string return max value either upload max filesize or post max size.
92
	 */
93
	function getMaxUploadSize($as_string = false)
94
	{
95
		$upload_max_value = strtoupper(ini_get('upload_max_filesize'));
96
		$post_max_value = getMaxPostRequestSize();
97
98
		/**
99
		 * if POST_MAX_SIZE is lower then UPLOAD_MAX_FILESIZE, then we have to check based on that value
100
		 * as we will not be able to upload attachment larger then POST_MAX_SIZE (file size + header data)
101
		 * so set POST_MAX_SIZE value to higher then UPLOAD_MAX_FILESIZE
102
		 */
103
104
		// calculate upload_max_value value to bytes
105
		if (strpos($upload_max_value, "K")!== false){
106
			$upload_max_value = ((int) $upload_max_value) * 1024;
107
		} else if (strpos($upload_max_value, "M")!== false){
108
			$upload_max_value = ((int) $upload_max_value) * 1024 * 1024;
109
		} else if (strpos($upload_max_value, "G")!== false){
110
			$upload_max_value = ((int) $upload_max_value) * 1024 * 1024 * 1024;
111
		}
112
113
		// check which one is larger
114
		$value = $upload_max_value;
115
		if($upload_max_value > $post_max_value) {
116
			$value = $post_max_value;
117
		}
118
119
		if ($as_string){
120
			// make user readable string
121
			if ($value > (1024 * 1024 * 1024)){
122
				$value = round($value / (1024 * 1024 * 1024), 1) ." ". _("GB");
123
			} else if ($value > (1024 * 1024)){
124
				$value = round($value / (1024 * 1024), 1) ." ". _("MB");
125
			} else if ($value > 1024){
126
				$value = round($value / 1024, 1) ." ". _("KB");
127
			} else {
128
				$value = $value ." ". _("B");
129
			}
130
		}
131
132
		return $value;
133
	}
134
135
	/**
136
	 * Gets maximum post request size of attachment from php ini settings.
137
	 * post_max_size specifies maximum size of a post request,
138
	 * we are uploading attachment using post method
139
	 *
140
	 * @return string returns the post request size with proper unit(MB, GB, KB etc.).
141
	 */
142
	function getMaxPostRequestSize()
143
	{
144
		$post_max_value = strtoupper(ini_get('post_max_size'));
145
146
		// calculate post_max_value value to bytes
147
		if (strpos($post_max_value, "K")!== false){
148
			$post_max_value = ((int) $post_max_value) * 1024;
149
		} else if (strpos($post_max_value, "M")!== false){
150
			$post_max_value = ((int) $post_max_value) * 1024 * 1024;
151
		} else if (strpos($post_max_value, "G")!== false){
152
			$post_max_value = ((int) $post_max_value) * 1024 * 1024 * 1024;
153
		}
154
155
		return $post_max_value;
156
	}
157
158
	/**
159
	 * Get maximum number of files that can be uploaded in single request from php ini settings.
160
	 * max_file_uploads specifies maximum number of files allowed in post request.
161
	 *
162
	 * @return number maximum number of files can uploaded in single request.
163
	 */
164
	function getMaxFileUploads()
165
	{
166
		return (int)ini_get('max_file_uploads');
167
	}
168
169
	/**
170
	 * cleanTemp
171
	 *
172
	 * Cleans up the temp directory.
173
	 * @param String $directory The path to the temp dir or sessions dir.
174
	 * @param Integer $maxLifeTime The maximum allowed age of files in seconds.
175
	 * @param Boolean $recursive False to prevent the folder to be cleaned up recursively
176
	 * @param Boolean $removeSubs False to prevent empty subfolders from being deleted
177
	 * @return Boolean True if the folder is empty
178
	 */
179
	function cleanTemp($directory = TMP_PATH, $maxLifeTime = STATE_FILE_MAX_LIFETIME, $recursive = true, $removeSubs = true)
0 ignored issues
show
Bug introduced by
The constant TMP_PATH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
180
	{
181
		if (!is_dir($directory)) {
182
			return;
183
		}
184
185
		// PHP doesn't do this by itself, so before running through
186
		// the folder, we should flush the statcache, so the 'atime'
187
		// is current.
188
		clearstatcache();
189
190
		$dir = opendir($directory);
191
		$is_empty = true;
192
193
		while ($file = readdir($dir)) {
194
			// Skip special folders
195
			if ($file === '.' || $file === '..') {
196
				continue;
197
			}
198
199
			$path = $directory . DIRECTORY_SEPARATOR . $file;
200
201
			if (is_dir($path)) {
202
				// If it is a directory, check if we need to
203
				// recursively clean this subfolder.
204
				if ($recursive) {
205
					// If cleanTemp indicates the subfolder is empty,
206
					// and $removeSubs is true, we must delete the subfolder
207
					// otherwise the currently folder is not empty.
208
					if (cleanTemp($path, $maxLifeTime, $recursive) && $removeSubs) {
209
						rmdir($path);
210
					} else {
211
						$is_empty = false;
212
					}
213
				} else {
214
					// We are not cleaning recursively, the current
215
					// folder is not empty.
216
					$is_empty = false;
217
				}
218
			} else {
219
				$fileinfo = stat($path);
220
221
				if ($fileinfo && $fileinfo["atime"] < time() - $maxLifeTime) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fileinfo 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...
222
					unlink($path);
223
				} else {
224
					$is_empty = false;
225
				}
226
			}
227
		}
228
229
		return $is_empty;
230
	}
231
232
	function cleanSearchFolders()
233
	{
234
		$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
235
236
		$storeProps = mapi_getprops($store, array(PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID));
237
		if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) !== STORE_SEARCH_OK) {
238
			return;
239
		}
240
241
		$finderfolder = mapi_msgstore_openentry($store, $storeProps[PR_FINDER_ENTRYID]);
242
243
		$hierarchytable = mapi_folder_gethierarchytable($finderfolder, MAPI_DEFERRED_ERRORS);
244
		mapi_table_restrict($hierarchytable, array(RES_AND,
245
				array(
246
					array(RES_CONTENT,
247
						array(
248
							FUZZYLEVEL	=> FL_PREFIX,
249
							ULPROPTAG	=> PR_DISPLAY_NAME,
250
							VALUE		=> array(PR_DISPLAY_NAME=>"grommunio Web Search Folder")
251
						)
252
					),
253
					array(RES_PROPERTY,
254
						array(
255
							RELOP		=> RELOP_LT,
256
							ULPROPTAG	=> PR_LAST_MODIFICATION_TIME,
257
							VALUE		=> array(PR_LAST_MODIFICATION_TIME=>(time()-ini_get("session.gc_maxlifetime")))
258
						)
259
					)
260
				)
261
		), TBL_BATCH);
262
263
		$folders = mapi_table_queryallrows($hierarchytable, array(PR_ENTRYID));
264
		foreach($folders as $folder){
265
			mapi_folder_deletefolder($finderfolder, $folder[PR_ENTRYID]);
266
		}
267
	}
268
269
	function dechex_32($dec){
270
		// Because on 64bit systems PHP handles integers as 64bit,
271
		// we need to convert these 64bit integers to 32bit when we
272
		// want the hex value
273
		$result = unpack("H*",pack("N", $dec));
274
		return $result[1];
275
	}
276
277
	/**
278
	 * This function will encode the input string for the header based on the browser that makes the
279
	 * HTTP request. MSIE and Edge has an issue with unicode filenames. All browsers do not seem to follow
280
	 * the RFC specification. Firefox requires an unencoded string in the HTTP header. MSIE and Edge will
281
	 * break on this and requires encoding.
282
	 * @param String $input Unencoded string
283
	 * @return String Encoded string
284
	 */
285
	function browserDependingHTTPHeaderEncode($input)
286
	{
287
		$input = preg_replace("/\r|\n/", "", $input);
288
		if(!isIE11() && !isEdge()) {
289
			return $input;
290
		} else {
291
			return rawurlencode($input);
292
		}
293
	}
294
295
	/**
296
	 * Helps to detect if the request came from IE11 or not.
297
	 * @return Boolean True if IE11 is the requester, position of the word otherwise.
298
	 */
299
	function isIE11()
300
	{
301
		return strpos($_SERVER['HTTP_USER_AGENT'], 'Trident') !== false;
302
	}
303
304
	/**
305
	 * Helps to detect if the request came from Edge or not.
306
	 * @return Boolean True if Edge is the requester, position of the word otherwise.
307
	 */
308
	function isEdge()
309
	{
310
		return strpos($_SERVER['HTTP_USER_AGENT'], 'Edge') !== false;
311
	}
312
313
	/**
314
	 * This function will return base name of the file from the full path of the file.
315
	 * PHP's basename() does not properly support streams or filenames beginning with a non-US-ASCII character.
316
	 * The default implementation in php for basename is locale aware. So it will truncate umlauts which can not be
317
	 * parsed by the current set locale.
318
	 * This problem only occurs with PHP < 5.2
319
	 * @see http://bugs.php.net/bug.php?id=37738, https://bugs.php.net/bug.php?id=37268
320
	 * @param String $filepath full path of the file
321
	 * @param String $suffix suffix that will be trimmed from file name
322
	 * @return String base name of the file
323
	 */
324
	function mb_basename($filepath, $suffix = '')
325
	{
326
		// Remove right-most slashes when $uri points to directory.
327
		$filepath = rtrim($filepath, DIRECTORY_SEPARATOR . ' ');
328
329
		// Returns the trailing part of the $uri starting after one of the directory
330
		// separators.
331
		$filename = preg_match('@[^' . preg_quote(DIRECTORY_SEPARATOR, '@') . ']+$@', $filepath, $matches) ? $matches[0] : '';
332
333
		// Cuts off a suffix from the filename.
334
		if ($suffix) {
335
			$filename = preg_replace('@' . preg_quote($suffix, '@') . '$@', '', $filename);
336
		}
337
338
		return $filename;
339
	}
340
341
	/**
342
	 * Function is used to get data from query string and store it in session
343
	 * for use when webapp is completely loaded.
344
	 */
345
	function storeURLDataToSession()
346
	{
347
		$data = array();
348
349
		$urlData = urldecode($_SERVER['QUERY_STRING']);
350
		if(!empty($_GET['action']) && $_GET['action'] === 'mailto') {
351
			$data['mailto'] = $_GET['to'];
352
353
			// There may be some data after to field, like cc, subject, body
354
			// So add them in the urlData string as well
355
			$pos = stripos($urlData, $_GET['to']) + strlen($_GET['to']);
356
			$subString = substr($urlData, $pos);
357
			$data['mailto'] .= $subString;
358
		}
359
360
		if(!empty($data)) {
361
			// finally store all data to session
362
			$_SESSION['url_action'] = $data;
363
		}
364
	}
365
366
	/**
367
	* Checks if the given url is allowed as redirect url.
368
	* @param String $url The url that will be checked
369
	*
370
	* @return Boolean True is the url is allowed as redirect url,
371
	* false otherwise
372
	*/
373
	function isContinueRedirectAllowed($url) {
374
		// First check the protocol
375
		$selfProtocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] ? 'https' : 'http';
376
		$parsed = parse_url($url);
377
		if ( $parsed === false || !isset($parsed['scheme']) || strtolower($parsed['scheme'])!==$selfProtocol ){
378
			return false;
379
		}
380
381
		// The same domain as grommunio Web is always allowed
382
		if ( $parsed['host'] === $_SERVER['HTTP_HOST'] ){
383
			return true;
384
		}
385
386
		// Check if the domain is white listed
387
		$allowedDomains = explode(' ', preg_replace('/\s+/', ' ', REDIRECT_ALLOWED_DOMAINS));
388
		if ( count($allowedDomains) && !empty($allowedDomains[0]) ){
389
			foreach ( $allowedDomains as $domain ){
390
				$parsedDomain = parse_url($domain);
391
392
				// Handle invalid configuration options
393
				if (!isset($parsedDomain['scheme']) || !isset($parsedDomain['host'])) {
394
					error_log("Invalid 'REDIRECT_ALLOWED_DOMAINS' " . $domain);
395
					continue;
396
				}
397
398
				if ( $parsedDomain['scheme'].'://'.$parsedDomain['host'] === $parsed['scheme'].'://'.$parsed['host'] ){
399
					// This domain was allowed to redirect to by the administrator
400
					return true;
401
				}
402
			}
403
		}
404
405
		return false;
406
	}
407
408
	// Constants for regular expressions which are used in get method to verify the input string
409
	define("ID_REGEX", "/^[a-z0-9_]+$/im");
410
	define("STRING_REGEX", "/^[a-z0-9_\s()@]+$/im");
411
	define("USERNAME_REGEX", "/^[a-z0-9\-\.\'_@]+$/im");
412
	define("ALLOWED_EMAIL_CHARS_REGEX", "/^[-a-z0-9_\.@!#\$%&'\*\+\/\=\?\^_`\{\|\}~]+$/im");
413
	define("NUMERIC_REGEX", "/^[0-9]+$/im");
414
	// Don't allow "\/:*?"<>|" characters in filename.
415
	define("FILENAME_REGEX", "/^[^\/\:\*\?\"\<\>\|]+$/im");
416
417
	/**
418
	 * Function to sanitize user input values to prevent XSS attacks
419
	 *
420
	 * @param Mixed $value value that should be sanitized
421
	 * @param Mixed $default default value to return when value is not safe
422
	 * @param String $regex regex to validate values based on type of value passed
423
	 */
424
	function sanitizeValue($value, $default = '', $regex = false)
425
	{
426
		$result = addslashes($value);
427
		if($regex) {
428
			$match = preg_match_all($regex, $result);
429
			if(!$match) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $match of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. 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...
430
				$result = $default;
431
			}
432
		}
433
434
		return $result;
435
	}
436
437
	/**
438
	 * Function to sanitize user input values to prevent XSS attacks
439
	 *
440
	 * @param String $key key that should be used to get value from $_GET to sanitize value
441
	 * @param Mixed $default default value to return when value is not safe
442
	 * @param String $regex regex to validate values based on type of value passed
443
	 */
444
	function sanitizeGetValue($key, $default = '', $regex = false)
445
	{
446
		// check if value really exists
447
		if(isset($_GET[$key])) {
448
			return sanitizeValue($_GET[$key], $default, $regex);
0 ignored issues
show
Bug introduced by
It seems like $regex can also be of type false; however, parameter $regex of sanitizeValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

448
			return sanitizeValue($_GET[$key], $default, /** @scrutinizer ignore-type */ $regex);
Loading history...
449
		}
450
451
		return $default;
452
	}
453
454
	/**
455
	 * Function to sanitize user input values to prevent XSS attacks
456
	 *
457
	 * @param String $key key that should be used to get value from $_POST to sanitize value
458
	 * @param Mixed $default default value to return when value is not safe
459
	 * @param String $regex regex to validate values based on type of value passed
460
	 */
461
	function sanitizePostValue($key, $default = '', $regex = false)
462
	{
463
		// check if value really exists
464
		if(isset($_POST[$key])) {
465
			return sanitizeValue($_POST[$key], $default, $regex);
0 ignored issues
show
Bug introduced by
It seems like $regex can also be of type false; however, parameter $regex of sanitizeValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

465
			return sanitizeValue($_POST[$key], $default, /** @scrutinizer ignore-type */ $regex);
Loading history...
466
		}
467
468
		return $default;
469
	}
470
471
	/**
472
	 * Function will be used to decode smime messages and convert it to normal messages
473
	 * @param MAPIStore $store user's store
0 ignored issues
show
Bug introduced by
The type MAPIStore was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
474
	 * @param MAPIMessage $message smime message
0 ignored issues
show
Bug introduced by
The type MAPIMessage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
475
	 */
476
	function parse_smime($store, $message)
477
	{
478
		$props = mapi_getprops($message, array(PR_MESSAGE_CLASS, PR_MESSAGE_FLAGS,
479
			PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_ENTRYID, PR_SENT_REPRESENTING_SEARCH_KEY,
480
			PR_SENT_REPRESENTING_EMAIL_ADDRESS, PR_SENT_REPRESENTING_SMTP_ADDRESS, PR_SENT_REPRESENTING_ADDRTYPE));
481
		$read = $props[PR_MESSAGE_FLAGS] & MSGFLAG_READ;
482
483
		if(isset($props[PR_MESSAGE_CLASS]) && stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME.MultipartSigned') !== false) {
484
			// this is a signed message. decode it.
485
			$atable = mapi_message_getattachmenttable($message);
486
487
			$rows = mapi_table_queryallrows($atable, Array(PR_ATTACH_MIME_TAG, PR_ATTACH_NUM));
488
			$attnum = false;
489
490
			foreach($rows as $row) {
491
				if(isset($row[PR_ATTACH_MIME_TAG]) && $row[PR_ATTACH_MIME_TAG] == 'multipart/signed') {
492
					$attnum = $row[PR_ATTACH_NUM];
493
				}
494
			}
495
496
			if($attnum !== false) {
497
				$att = mapi_message_openattach($message, $attnum);
498
				$data = mapi_openproperty($att, PR_ATTACH_DATA_BIN);
499
500
				// Allowing to hook in before the signed attachment is removed
501
				$GLOBALS['PluginManager']->triggerHook('server.util.parse_smime.signed', array(
502
					'store' => $store,
503
					'props' => $props,
504
					'message' => &$message,
505
					'data' => &$data
506
				));
507
508
				// also copy recipients because they are lost after mapi_inetmapi_imtomapi
509
				$recipienttable = mapi_message_getrecipienttable($message);
510
				$messageRecipients = mapi_table_queryallrows($recipienttable, $GLOBALS["properties"]->getRecipientProperties());
511
512
				mapi_inetmapi_imtomapi($GLOBALS['mapisession']->getSession(), $store, $GLOBALS['mapisession']->getAddressbook(), $message, $data, Array("parse_smime_signed" => 1));
513
514
				if(!empty($messageRecipients)) {
515
					mapi_message_modifyrecipients($message, MODRECIP_ADD, $messageRecipients);
516
				}
517
518
				mapi_setprops($message, array(
519
					PR_MESSAGE_CLASS => $props[PR_MESSAGE_CLASS],
520
					PR_SENT_REPRESENTING_NAME => $props[PR_SENT_REPRESENTING_NAME],
521
					PR_SENT_REPRESENTING_ENTRYID => $props[PR_SENT_REPRESENTING_ENTRYID],
522
					PR_SENT_REPRESENTING_SEARCH_KEY => $props[PR_SENT_REPRESENTING_SEARCH_KEY],
523
					PR_SENT_REPRESENTING_EMAIL_ADDRESS => $props[PR_SENT_REPRESENTING_EMAIL_ADDRESS] ?? '',
524
					PR_SENT_REPRESENTING_SMTP_ADDRESS => $props[PR_SENT_REPRESENTING_SMTP_ADDRESS] ?? '',
525
					PR_SENT_REPRESENTING_ADDRTYPE => $props[PR_SENT_REPRESENTING_ADDRTYPE] ?? 'SMTP',
526
			));
527
528
			}
529
		} else if(isset($props[PR_MESSAGE_CLASS]) && stripos($props[PR_MESSAGE_CLASS], 'IPM.Note.SMIME') !== false) {
530
			// this is a encrypted message. decode it.
531
			$attachTable = mapi_message_getattachmenttable($message);
532
533
			$rows = mapi_table_queryallrows($attachTable, Array(PR_ATTACH_MIME_TAG, PR_ATTACH_NUM, PR_ATTACH_LONG_FILENAME));
534
			$attnum = false;
535
			foreach($rows as $row) {
536
				if(isset($row[PR_ATTACH_MIME_TAG]) && in_array($row[PR_ATTACH_MIME_TAG],array('application/x-pkcs7-mime','application/pkcs7-mime')) ) {
537
					$attnum = $row[PR_ATTACH_NUM];
538
				}
539
			}
540
541
			if($attnum !== false) {
542
				$att = mapi_message_openattach($message, $attnum);
543
				$data = mapi_openproperty($att, PR_ATTACH_DATA_BIN);
544
545
				// Allowing to hook in before the encrypted attachment is removed
546
				$GLOBALS['PluginManager']->triggerHook('server.util.parse_smime.encrypted', array(
547
					'store' => $store,
548
					'props' => $props,
549
					'message' => &$message,
550
					'data' => &$data
551
				));
552
553
				if (isSmimePluginEnabled()) {
554
					mapi_message_deleteattach($message, $attnum);
555
				}
556
			}
557
		}
558
		// mark the message as read if the main message has read flag
559
		if ($read) {
560
			$mprops = mapi_getprops($message, array(PR_MESSAGE_FLAGS));
561
			mapi_setprops($message, array(PR_MESSAGE_FLAGS => $mprops[PR_MESSAGE_FLAGS] | MSGFLAG_READ));
562
		}
563
	}
564
565
	/**
566
	 * Helper function which used to check smime plugin is enabled.
567
	 *
568
	 * @return Boolean true if smime plugin is enabled else false.
569
	 */
570
	function isSmimePluginEnabled() {
571
		return $GLOBALS['settings']->get("zarafa/v1/plugins/smime/enable", false);
572
	}
573
574
	/**
575
	 * Helper to stream a MAPI property.
576
	 *
577
	 * @param MAPIObject $mapiobj mapi message or store
0 ignored issues
show
Bug introduced by
The type MAPIObject was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
578
	 * @return String $datastring the streamed data
579
	 */
580
	function streamProperty($mapiobj, $proptag)
581
	{
582
		$stream = mapi_openproperty($mapiobj, $proptag, IID_IStream, 0, 0);
583
		$stat = mapi_stream_stat($stream);
584
		mapi_stream_seek($stream, 0, STREAM_SEEK_SET);
585
586
		$datastring = '';
587
		for($i = 0; $i < $stat['cb']; $i+= BLOCK_SIZE){
0 ignored issues
show
Bug introduced by
The constant BLOCK_SIZE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
588
			$datastring .= mapi_stream_read($stream, BLOCK_SIZE);
589
		}
590
591
		return $datastring;
592
	}
593
594
	/**
595
	 * Function will decode JSON string into objects.
596
	 *
597
	 * @param {String} $jsonString JSON data that should be decoded.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {String} at position 0 could not be parsed: Unknown type name '{' at position 0 in {String}.
Loading history...
598
	 * @param {Boolean} $toAssoc flag to indicate that associative arrays should be
599
	 * returned as objects or arrays, true means it will return associative array as arrays and
600
	 * false will return associative arrays as objects.
601
	 * @return {Object} decoded data.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
602
	 */
603
	function json_decode_data($jsonString, $toAssoc = false)
604
	{
605
		$data = json_decode($jsonString, $toAssoc);
606
		$errorString = '';
607
608
		switch(json_last_error())
609
		{
610
			case JSON_ERROR_DEPTH:
611
				$errorString = _("The maximum stack depth has been exceeded");
612
				break;
613
			case JSON_ERROR_CTRL_CHAR:
614
				$errorString = _("Control character error, possibly incorrectly encoded");
615
				break;
616
			case JSON_ERROR_STATE_MISMATCH:
617
				$errorString = _("Invalid or malformed JSON");
618
				break;
619
			case JSON_ERROR_SYNTAX:
620
				$errorString = _("Syntax error");
621
				break;
622
			case JSON_ERROR_UTF8:
623
				$errorString = _("Malformed UTF-8 characters, possibly incorrectly encoded");
624
				break;
625
		}
626
627
		if(!empty($errorString)) {
628
			throw new JSONException(sprintf(_("JSON Error: - %s") , $errorString), json_last_error(), null, _("Some problem encountered when encoding/decoding JSON data."));
0 ignored issues
show
Unused Code introduced by
The call to JSONException::__construct() has too many arguments starting with _('Some problem encounte...g/decoding JSON data.'). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

628
			throw /** @scrutinizer ignore-call */ new JSONException(sprintf(_("JSON Error: - %s") , $errorString), json_last_error(), null, _("Some problem encountered when encoding/decoding JSON data."));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
629
		}
630
631
		return $data;
632
	}
633
634
	/**
635
	 * Tries to open the IPM subtree. If opening fails, it will try to fix it by
636
	 * trying to find the correct entryid of the IPM subtree in the hierarchy.
637
	 * @param {MAPIObject} $store the store to retrieve IPM subtree from
0 ignored issues
show
Documentation Bug introduced by
The doc comment {MAPIObject} at position 0 could not be parsed: Unknown type name '{' at position 0 in {MAPIObject}.
Loading history...
638
	 * @return {mixed} false if the subtree is broken beyond quick repair,
0 ignored issues
show
Documentation Bug introduced by
The doc comment {mixed} at position 0 could not be parsed: Unknown type name '{' at position 0 in {mixed}.
Loading history...
639
	 * the IPM subtree resource otherwise
640
	 */
641
	function getSubTree($store) {
642
		$storeProps = mapi_getprops($store, array(PR_IPM_SUBTREE_ENTRYID));
643
		try {
644
			$ipmsubtree = mapi_msgstore_openentry($store, $storeProps[PR_IPM_SUBTREE_ENTRYID]);
645
		} catch (MAPIException $e) {
646
			if ($e->getCode() == MAPI_E_NOT_FOUND || $e->getCode() == MAPI_E_INVALID_ENTRYID) {
647
				$username = $GLOBALS["mapisession"]->getUserName();
648
				error_log(sprintf('Unable to open IPM_SUBTREE for %s, trying to correct PR_IPM_SUBTREE_ENTRYID', $username));
649
				$ipmsubtree = fix_ipmsubtree($store);
650
			}
651
		}
652
653
		return $ipmsubtree;
654
	}
655
656
	/**
657
	 * Fetches the full hierarchy and returns an array with a cache of the stat
658
	 * of the folders in the hierarchy. Passing the folderType is required for cases where
659
	 * the user has permission on the inbox folder, but no folder visible
660
	 * rights on the rest of the store.
661
	 *
662
	 * @param {String} $username the user who's store to retrieve hierarchy counters from.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {String} at position 0 could not be parsed: Unknown type name '{' at position 0 in {String}.
Loading history...
663
	 * If no username is given, the currently logged in user's store will be used.
664
	 * @param {String} $folderType if inbox use the inbox as root folder.
665
	 * @return {Array} folderStatCache a cache of the hierarchy folders.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Array} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Array}.
Loading history...
666
	 */
667
	function updateHierarchyCounters($username='', $folderType='')
668
	{
669
		// Open the correct store
670
		if ($username) {
671
			$userEntryid = $GLOBALS["mapisession"]->getStoreEntryIdOfUser($username);
672
			$store = $GLOBALS["mapisession"]->openMessageStore($userEntryid);
673
		} else {
674
			$store = $GLOBALS["mapisession"]->getDefaultMessageStore();
675
		}
676
677
		$props = array(PR_DISPLAY_NAME, PR_LOCAL_COMMIT_TIME_MAX, PR_CONTENT_COUNT, PR_CONTENT_UNREAD, PR_ENTRYID, PR_STORE_ENTRYID);
678
679
		if ($folderType === 'inbox') {
680
			try {
681
				$rootFolder = mapi_msgstore_getreceivefolder($store);
682
			} catch (MAPIException $e) {
683
				$username = $GLOBALS["mapisession"]->getUserName();
684
				error_log(sprintf("Unable to open Inbox for %s. MAPI Error '%s'", $username, get_mapi_error_name($e->getCode())));
685
				return [];
686
			}
687
		} else {
688
			$rootFolder = getSubTree($store);
689
		}
690
691
		$hierarchy = mapi_folder_gethierarchytable($rootFolder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);
692
		$rows = mapi_table_queryallrows($hierarchy, $props);
693
694
		// Append the Inbox folder itself.
695
		if ($folderType === 'inbox') {
696
			array_push($rows, mapi_getprops($rootFolder, $props));
697
		}
698
699
		$folderStatCache = array();
700
		foreach($rows as $folder) {
701
			$folderStatCache[$folder[PR_DISPLAY_NAME]] = array(
702
				'commit_time' => isset($folder[PR_LOCAL_COMMIT_TIME_MAX]) ? $folder[PR_LOCAL_COMMIT_TIME_MAX] : "0000000000",
703
				'entryid' => bin2hex($folder[PR_ENTRYID]),
704
				'store_entryid' => bin2hex($folder[PR_STORE_ENTRYID]),
705
				'content_count' => isset($folder[PR_CONTENT_COUNT]) ? $folder[PR_CONTENT_COUNT] : -1,
706
				'content_unread' => isset($folder[PR_CONTENT_UNREAD]) ? $folder[PR_CONTENT_UNREAD] : -1,
707
			);
708
		}
709
710
		return $folderStatCache;
711
	}
712
713
	/**
714
	 * Fix the PR_IPM_SUBTREE_ENTRYID in the Store properties when it is broken,
715
	 * by looking up the IPM_SUBTREE in the Hierarchytable and fetching the entryid
716
	 * and if found, setting the PR_IPM_SUBTREE_ENTRYID to that found entryid.
717
	 *
718
	 * @param {Object} $store the users MAPI Store
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
719
	 * @return {mixed} false if unable to correct otherwise return the subtree.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {mixed} at position 0 could not be parsed: Unknown type name '{' at position 0 in {mixed}.
Loading history...
720
	 */
721
	function fix_ipmsubtree($store)
722
	{
723
		$root = mapi_msgstore_openentry($store, null);
724
		$username = $GLOBALS["mapisession"]->getUserName();
725
		$hierarchytable = mapi_folder_gethierarchytable($root);
726
		mapi_table_restrict($hierarchytable, [RES_CONTENT,
727
				[
728
					FUZZYLEVEL	=> FL_PREFIX,
729
					ULPROPTAG	=> PR_DISPLAY_NAME,
730
					VALUE		=> [PR_DISPLAY_NAME => "IPM_SUBTREE"]
731
				]
732
		]);
733
734
		$folders = mapi_table_queryallrows($hierarchytable, array(PR_ENTRYID));
735
		if (empty($folders)) {
736
			error_log(sprintf("No IPM_SUBTREE found for %s, store is broken", $username));
737
			return false;
738
		}
739
740
		try {
741
			$entryid = $folders[0][PR_ENTRYID];
742
			$ipmsubtree = mapi_msgstore_openentry($store, $entryid);
743
		} catch (MAPIException $e) {
744
			error_log(sprintf('Unable to open IPM_SUBTREE for %s, IPM_SUBTREE folder can not be opened. MAPI error: %s',
745
					$username, get_mapi_error_name($e->getCode())));
746
			return false;
747
		}
748
749
		mapi_setprops($store, [PR_IPM_SUBTREE_ENTRYID => $entryid]);
750
		error_log(sprintf('Fixed PR_IPM_SUBTREE_ENTRYID for %s', $username));
751
752
		return $ipmsubtree;
753
	}
754
755
	/**
756
	 * Helper function which provide protocol used by current request.
757
	 * @return string It can be either https or http.
758
	 */
759
	function getRequestProtocol()
760
	{
761
		if(!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
762
			return $_SERVER['HTTP_X_FORWARDED_PROTO'];
763
		} else {
764
			return !empty($_SERVER['HTTPS']) ? "https" : "http";
765
		}
766
	}
767
768
	/**
769
	 * Helper function which defines that webapp has to use secure cookies
770
	 * or not. by default webapp always use secure cookies whether or not
771
	 * 'SECURE_COOKIES' defined. webapp only use insecure cookies
772
	 * where a user has explicitly set 'SECURE_COOKIES' to false.
773
	 *
774
	 * @return Boolean return false only when a user has explicitly set
775
	 * 'SECURE_COOKIES' to false else returns true.
776
	 */
777
	function useSecureCookies()
778
	{
779
		return !defined('SECURE_COOKIES') || SECURE_COOKIES !== false;
0 ignored issues
show
Bug introduced by
The constant SECURE_COOKIES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
780
	}
781
782
	/**
783
	 * Check if the eml stream is corrupted or not
784
	 * @param String $attachment Content fetched from PR_ATTACH_DATA_BIN property of an attachment.
785
	 * @return True if eml is broken, false otherwise.
786
	 */
787
	function isBrokenEml($attachment)
788
	{
789
		// Get header part to process further
790
		$splittedContent = preg_split("/\r?\n\r?\n/", $attachment);
791
792
		// Fetch raw header
793
		if (preg_match_all('/([^\n^:]+:)/', $splittedContent[0], $matches)) {
794
			$rawHeaders = $matches[1];
795
		}
796
797
		// Compare if necessary headers are present or not
798
		if (isset($rawHeaders) && in_array('From:', $rawHeaders) && in_array('Date:', $rawHeaders)) {
799
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type true.
Loading history...
800
		}
801
802
		return true;
803
	}
804
805
	/**
806
	 * Function returns the IP address of the client.
807
	 *
808
	 * @return String The IP address of the client.
809
	 */
810
	function getClientIPAddress() {
811
		// Here, there is a scenario where the server is behind a proxy, when that
812
		// happens, 'REMOTE_ADDR' will not return the real IP, there is another variable
813
		// 'HTTP_X_FORWARDED_FOR' which is set by a proxy server. But the risk in using that
814
		// is that it can be easily forged. 'REMOTE_ADDR' is the only reliable thing
815
		// as it is nearly impossible to be altered.
816
		return $_SERVER['REMOTE_ADDR'];
817
	}
818
819
	/**
820
	 * Helper function which return the webapp version.
821
	 *
822
	 * @returns String webapp version.
823
	 */
824
	function getWebappVersion()
825
	{
826
		return trim(file_get_contents('version'));
827
	}
828
829
	/**
830
	 * function which remove double quotes or PREF from vcf stream
831
	 * if it has.
832
	 *
833
	 * @param {String} $attachmentStream The attachment stream.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {String} at position 0 could not be parsed: Unknown type name '{' at position 0 in {String}.
Loading history...
834
	 */
835
	function processVCFStream(&$attachmentStream)
836
	{
837
		/**
838
		 * https://github.com/libical/libical/issues/488
839
		 * https://github.com/libical/libical/issues/490
840
		 *
841
		 * Because of above issues we need to remove
842
		 * double quotes or PREF from vcf stream if
843
		 * it exists in vcf stream.
844
		 */
845
		if (preg_match('/"/', $attachmentStream) > 0) {
846
			$attachmentStream = str_replace('"', '', $attachmentStream);
847
		}
848
849
		if (preg_match('/EMAIL;PREF=/', $attachmentStream) > 0) {
850
			$rows = explode("\n", $attachmentStream);
851
			foreach($rows as $key => $row) {
852
				if(preg_match("/EMAIL;PREF=/", $row)) {
853
					unset($rows[$key]);
854
				}
855
			}
856
857
			$attachmentStream = join("\n", $rows);
858
		}
859
	}
860
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
861