Passed
Push — master ( 8aba83...48d49b )
by
unknown
18:08 queued 08:09
created

compareEntryIds()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 5
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 11
rs 10
1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2005-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6
 */
7
8
define('NOERROR', 0);
9
10
// Load all mapi defs
11
mapi_load_mapidefs(1);
12
13
/**
14
 * Function to make a MAPIGUID from a php string.
15
 * The C++ definition for the GUID is:
16
 *  typedef struct _GUID
17
 *  {
18
 *   unsigned long        Data1;
19
 *   unsigned short       Data2;
20
 *   unsigned short       Data3;
21
 *   unsigned char        Data4[8];
22
 *  } GUID;.
23
 *
24
 * A GUID is normally represented in the following form:
25
 * 	{00062008-0000-0000-C000-000000000046}
26
 *
27
 * @param string GUID
0 ignored issues
show
Bug introduced by
The type GUID 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...
28
 * @param mixed $guid
29
 */
30
function makeGuid($guid) {
31
	return pack("vvvv", hexdec(substr($guid, 5, 4)), hexdec(substr($guid, 1, 4)), hexdec(substr($guid, 10, 4)), hexdec(substr($guid, 15, 4))) . hex2bin(substr($guid, 20, 4)) . hex2bin(substr($guid, 25, 12));
32
}
33
34
/**
35
 * Function to get a human readable string from a MAPI error code.
36
 *
37
 *@param int $errcode the MAPI error code, if not given, we use mapi_last_hresult
38
 *
39
 *@return string The defined name for the MAPI error code
40
 */
41
function get_mapi_error_name($errcode = null) {
42
	if ($errcode === null) {
43
		$errcode = mapi_last_hresult();
44
	}
45
46
	if ($errcode !== 0) {
47
		// Retrieve constants categories, MAPI error names are defined in gromox.
48
		foreach (get_defined_constants(true)['user'] as $key => $value) {
49
			/*
50
			 * If PHP encounters a number beyond the bounds of the integer type,
51
			 * it will be interpreted as a float instead, so when comparing these error codes
52
			 * we have to manually typecast value to integer, so float will be converted in integer,
53
			 * but still its out of bound for integer limit so it will be auto adjusted to minus value
54
			 */
55
			if ($errcode == (int) $value) {
56
				// Check that we have an actual MAPI error or warning definition
57
				$prefix = substr($key, 0, 7);
58
				if ($prefix == "MAPI_E_" || $prefix == "MAPI_W_") {
59
					return $key;
60
				}
61
			}
62
		}
63
	}
64
	else {
65
		return "NOERROR";
66
	}
67
68
	// error code not found, return hex value (this is a fix for 64-bit systems, we can't use the dechex() function for this)
69
	$result = unpack("H*", pack("N", $errcode));
70
71
	return "0x" . $result[1];
72
}
73
74
/**
75
 * Parses properties from an array of strings. Each "string" may be either an ULONG, which is a direct property ID,
76
 * or a string with format "PT_TYPE:{GUID}:StringId" or "PT_TYPE:{GUID}:0xXXXX" for named
77
 * properties.
78
 *
79
 * @returns array of properties
80
 *
81
 * @param mixed $store
82
 * @param mixed $mapping
83
 */
84
function getPropIdsFromStrings($store, $mapping) {
85
	$props = [];
86
87
	$ids = ["name" => [], "id" => [], "guid" => [], "type" => []]; // this array stores all the information needed to retrieve a named property
88
	$num = 0;
89
90
	// caching
91
	$guids = [];
92
93
	foreach ($mapping as $name => $val) {
94
		if (is_string($val)) {
95
			$split = explode(":", $val);
96
97
			if (count($split) != 3) { // invalid string, ignore
98
				trigger_error(sprintf("Invalid property: %s \"%s\"", $name, $val), E_USER_NOTICE);
99
100
				continue;
101
			}
102
103
			if (substr($split[2], 0, 2) == "0x") {
104
				$id = hexdec(substr($split[2], 2));
105
			}
106
			elseif (preg_match('/^[1-9][0-9]{0,12}$/', $split[2])) {
107
				$id = (int) $split[2];
108
			}
109
			else {
110
				$id = $split[2];
111
			}
112
113
			// have we used this guid before?
114
			if (!defined($split[1])) {
115
				if (!array_key_exists($split[1], $guids)) {
116
					$guids[$split[1]] = makeguid($split[1]);
117
				}
118
				$guid = $guids[$split[1]];
119
			}
120
			else {
121
				$guid = constant($split[1]);
122
			}
123
124
			// temp store info about named prop, so we have to call mapi_getidsfromnames just one time
125
			$ids["name"][$num] = $name;
126
			$ids["id"][$num] = $id;
127
			$ids["guid"][$num] = $guid;
128
			$ids["type"][$num] = $split[0];
129
			++$num;
130
		}
131
		else {
132
			// not a named property
133
			$props[$name] = $val;
134
		}
135
	}
136
137
	if (empty($ids["id"])) {
138
		return $props;
139
	}
140
141
	// get the ids
142
	$named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]);
143
	foreach ($named as $num => $prop) {
144
		$props[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop));
145
	}
146
147
	return $props;
148
}
149
150
/**
151
 * Check whether a call to mapi_getprops returned errors for some properties.
152
 * mapi_getprops function tries to get values of properties requested but somehow if
153
 * if a property value can not be fetched then it changes type of property tag as PT_ERROR
154
 * and returns error for that particular property, probable errors
155
 * that can be returned as value can be MAPI_E_NOT_FOUND, MAPI_E_NOT_ENOUGH_MEMORY.
156
 *
157
 * @param long  $property  Property to check for error
0 ignored issues
show
Bug introduced by
The type long 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...
158
 * @param array $propArray An array of properties
159
 *
160
 * @return mixed Gives back false when there is no error, if there is, gives the error
161
 */
162
function propIsError($property, $propArray) {
163
	if (array_key_exists(mapi_prop_tag(PT_ERROR, mapi_prop_id($property)), $propArray)) {
164
		return $propArray[mapi_prop_tag(PT_ERROR, mapi_prop_id($property))];
165
	}
166
167
	return false;
168
}
169
170
/* Macro Functions for PR_DISPLAY_TYPE_EX values */
171
/**
172
 * check addressbook object is a remote mailuser.
173
 *
174
 * @param mixed $value
175
 */
176
function DTE_IS_REMOTE_VALID($value) {
177
	return (bool) ($value & DTE_FLAG_REMOTE_VALID);
178
}
179
180
/**
181
 * check addressbook object is able to receive permissions.
182
 *
183
 * @param mixed $value
184
 */
185
function DTE_IS_ACL_CAPABLE($value) {
186
	return (bool) ($value & DTE_FLAG_ACL_CAPABLE);
187
}
188
189
function DTE_REMOTE($value) {
190
	return ($value & DTE_MASK_REMOTE) >> 8;
191
}
192
193
function DTE_LOCAL($value) {
194
	return $value & DTE_MASK_LOCAL;
195
}
196
197
/**
198
 * Note: Static function, more like a utility function.
199
 *
200
 * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
201
 * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
202
 * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
203
 * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
204
 *
205
 * @param $store resource The store in which the calendar resides
206
 * @param $calendar resource The calendar to get the items from
207
 * @param $viewstart int Timestamp of beginning of view window
208
 * @param $viewend int Timestamp of end of view window
209
 * @param $propsrequested array Array of properties to return
210
 * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
211
 *                    expanded so that it seems that there are only many single appointments in the table.
212
 */
213
function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested) {
214
	$result = [];
215
	$properties = getPropIdsFromStrings($store, [
216
		"duedate" => "PT_SYSTIME:PSETID_Appointment:".PidLidAppointmentEndWhole,
217
		"startdate" => "PT_SYSTIME:PSETID_Appointment:".PidLidAppointmentStartWhole,
218
		"enddate_recurring" => "PT_SYSTIME:PSETID_Appointment:".PidLidClipEnd,
219
		"recurring" => "PT_BOOLEAN:PSETID_Appointment:".PidLidRecurring,
220
		"recurring_data" => "PT_BINARY:PSETID_Appointment:".PidLidAppointmentRecur,
221
		"timezone_data" => "PT_BINARY:PSETID_Appointment:".PidLidTimeZoneStruct,
222
		"label" => "PT_LONG:PSETID_Appointment:0x8214",
223
	]);
224
225
	// Create a restriction that will discard rows of appointments that are definitely not in our
226
	// requested time frame
227
228
	$table = mapi_folder_getcontentstable($calendar);
229
230
	$restriction =
231
		// OR
232
		[
233
			RES_OR,
234
			[
235
				[RES_AND,	// Normal items: itemEnd must be after viewStart, itemStart must be before viewEnd
236
					[
237
						[
238
							RES_PROPERTY,
239
							[
240
								RELOP => RELOP_GT,
241
								ULPROPTAG => $properties["duedate"],
242
								VALUE => $viewstart,
243
							],
244
						],
245
						[
246
							RES_PROPERTY,
247
							[
248
								RELOP => RELOP_LT,
249
								ULPROPTAG => $properties["startdate"],
250
								VALUE => $viewend,
251
							],
252
						],
253
					],
254
				],
255
				// OR
256
				[
257
					RES_PROPERTY,
258
					[
259
						RELOP => RELOP_EQ,
260
						ULPROPTAG => $properties["recurring"],
261
						VALUE => true,
262
					],
263
				],
264
			],	// EXISTS OR
265
		];		// global OR
266
267
	// Get requested properties, plus whatever we need
268
	$proplist = [PR_ENTRYID, $properties["recurring"], $properties["recurring_data"], $properties["timezone_data"]];
269
	$proplist = array_merge($proplist, $propsrequested);
270
271
	$rows = mapi_table_queryallrows($table, $proplist, $restriction);
272
273
	// $rows now contains all the items that MAY be in the window; a recurring item needs expansion before including in the output.
274
275
	foreach ($rows as $row) {
276
		$items = [];
277
278
		if (isset($row[$properties["recurring"]]) && $row[$properties["recurring"]]) {
279
			// Recurring item
280
			$rec = new Recurrence($store, $row);
281
282
			// GetItems guarantees that the item overlaps the interval <$viewstart, $viewend>
283
			$occurrences = $rec->getItems($viewstart, $viewend);
284
			foreach ($occurrences as $occurrence) {
285
				// The occurrence takes all properties from the main row, but overrides some properties (like start and end obviously)
286
				$item = $occurrence + $row;
287
				array_push($items, $item);
288
			}
289
		}
290
		else {
291
			// Normal item, it matched the search criteria and therefore overlaps the interval <$viewstart, $viewend>
292
			array_push($items, $row);
293
		}
294
295
		$result = array_merge($result, $items);
296
	}
297
298
	// All items are guaranteed to overlap the interval <$viewstart, $viewend>. Note that we may be returning a few extra
299
	// properties that the caller did not request (recurring, etc). This shouldn't be a problem though.
300
	return $result;
301
}
302
303
	/**
304
	 * Compares two entryIds. It is possible to have two different entryIds that should match as they
305
	 * represent the same object (in multiserver environments).
306
	 *
307
	 * @param {String} entryId1 EntryID
308
	 * @param {String} entryId2 EntryID
0 ignored issues
show
Bug introduced by
The type entryId2 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...
309
	 * @param mixed $entryId1
310
	 * @param mixed $entryId2
311
	 *
312
	 * @return {Boolean} Result of the comparison
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Boolean} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Boolean}.
Loading history...
313
	 */
314
	function compareEntryIds($entryId1, $entryId2) {
315
		if (!is_string($entryId1) || !is_string($entryId2)) {
316
			return false;
317
		}
318
319
		if ($entryId1 === $entryId2) {
320
			// if normal comparison succeeds then we can directly say that entryids are same
321
			return true;
322
		}
323
324
		return false;
325
	}
326
327
	/**
328
	 * Creates a goid from an ical uuid.
329
	 *
330
	 * @param $uid
331
	 *
332
	 * @return string binary string representation of goid
333
	 */
334
	function getGoidFromUid($uid) {
335
		return hex2bin("040000008200E00074C5B7101A82E0080000000000000000000000000000000000000000" .
336
					bin2hex(pack("V", 12 + strlen($uid)) . "vCal-Uid" . pack("V", 1) . $uid));
337
	}
338
339
	/**
340
	 * Creates an ical uuid from a goid.
341
	 *
342
	 * @param $goid
343
	 *
344
	 * @return string ical uuid
345
	 */
346
	function getUidFromGoid($goid) {
347
		// check if "vCal-Uid" is somewhere in outlookid case-insensitive
348
		$uid = stristr($goid, "vCal-Uid");
349
		if ($uid !== false) {
350
			// get the length of the ical id - go back 4 position from where "vCal-Uid" was found
351
			$begin = unpack("V", substr($goid, strlen($uid) * (-1) - 4, 4));
352
			// remove "vCal-Uid" and packed "1" and use the ical id length
353
			return substr($uid, 12, ($begin[1] - 12));
354
		}
355
356
		return null;
357
	}
358
359
	/**
360
	 * Returns an error message from error code.
361
	 *
362
	 * @param $e error code
363
	 *
364
	 * @return string error message
365
	 */
366
	function mapi_strerror($e) {
367
		switch ($e) {
368
			case 0: return "success";
369
370
			case MAPI_E_CALL_FAILED: return "An error of unexpected or unknown origin occurred";
371
372
			case MAPI_E_NOT_ENOUGH_MEMORY: return "Not enough memory was available to complete the operation";
373
374
			case MAPI_E_INVALID_PARAMETER: return "An invalid parameter was passed to a function or remote procedure call";
375
376
			case MAPI_E_INTERFACE_NOT_SUPPORTED: return "MAPI interface not supported";
377
378
			case MAPI_E_NO_ACCESS: return "An attempt was made to access a message store or object for which the user has insufficient permissions";
379
380
			case MAPI_E_NO_SUPPORT: return "Function is not implemented";
381
382
			case MAPI_E_BAD_CHARWIDTH: return "An incompatibility exists in the character sets supported by the caller and the implementation";
383
384
			case MAPI_E_STRING_TOO_LONG: return "In the context of this method call, a string exceeds the maximum permitted length";
385
386
			case MAPI_E_UNKNOWN_FLAGS: return "One or more values for a flags parameter were not valid";
387
388
			case MAPI_E_INVALID_ENTRYID: return "invalid entryid";
389
390
			case MAPI_E_INVALID_OBJECT: return "A method call was made using a reference to an object that has been destroyed or is not in a viable state";
391
392
			case MAPI_E_OBJECT_CHANGED: return "An attempt to commit changes failed because the object was changed separately";
393
394
			case MAPI_E_OBJECT_DELETED: return "An operation failed because the object was deleted separately";
395
396
			case MAPI_E_BUSY: return "A table operation failed because a separate operation was in progress at the same time";
397
398
			case MAPI_E_NOT_ENOUGH_DISK: return "Not enough disk space was available to complete the operation";
399
400
			case MAPI_E_NOT_ENOUGH_RESOURCES: return "Not enough system resources were available to complete the operation";
401
402
			case MAPI_E_NOT_FOUND: return "The requested object could not be found at the server";
403
404
			case MAPI_E_VERSION: return "Client and server versions are not compatible";
405
406
			case MAPI_E_LOGON_FAILED: return "A client was unable to log on to the server";
407
408
			case MAPI_E_SESSION_LIMIT: return "A server or service is unable to create any more sessions";
409
410
			case MAPI_E_USER_CANCEL: return "An operation failed because a user cancelled it";
411
412
			case MAPI_E_UNABLE_TO_ABORT: return "A ropAbort or ropAbortSubmit ROP request was unsuccessful";
413
414
			case MAPI_E_NETWORK_ERROR: return "An operation was unsuccessful because of a problem with network operations or services";
415
416
			case MAPI_E_DISK_ERROR: return "There was a problem writing to or reading from disk";
417
418
			case MAPI_E_TOO_COMPLEX: return "The operation requested is too complex for the server to handle (often w.r.t. restrictions)";
419
420
			case MAPI_E_BAD_COLUMN: return "The column requested is not allowed in this type of table";
421
422
			case MAPI_E_EXTENDED_ERROR: return "extended error";
423
424
			case MAPI_E_COMPUTED: return "A property cannot be updated because it is read-only, computed by the server";
425
426
			case MAPI_E_CORRUPT_DATA: return "There is an internal inconsistency in a database, or in a complex property value";
427
428
			case MAPI_E_UNCONFIGURED: return "unconfigured";
429
430
			case MAPI_E_FAILONEPROVIDER: return "failoneprovider";
431
432
			case MAPI_E_UNKNOWN_CPID: return "The server is not configured to support the code page requested by the client";
433
434
			case MAPI_E_UNKNOWN_LCID: return "The server is not configured to support the locale requested by the client";
435
436
			case MAPI_E_PASSWORD_CHANGE_REQUIRED: return "password change required";
437
438
			case MAPI_E_PASSWORD_EXPIRED: return "password expired";
439
440
			case MAPI_E_INVALID_WORKSTATION_ACCOUNT: return "invalid workstation account";
441
442
			case MAPI_E_INVALID_ACCESS_TIME: return "The operation failed due to clock skew between servers";
443
444
			case MAPI_E_ACCOUNT_DISABLED: return "account disabled";
445
446
			case MAPI_E_END_OF_SESSION: return "The server session has been destroyed, possibly by a server restart";
447
448
			case MAPI_E_UNKNOWN_ENTRYID: return "The EntryID passed to OpenEntry was created by a different MAPI provider";
449
450
			case MAPI_E_MISSING_REQUIRED_COLUMN: return "missing required column";
451
452
			case MAPI_W_NO_SERVICE: return "no service";
453
454
			case MAPI_E_BAD_VALUE: return "bad value";
455
456
			case MAPI_E_INVALID_TYPE: return "invalid type";
457
458
			case MAPI_E_TYPE_NO_SUPPORT: return "type no support";
459
460
			case MAPI_E_UNEXPECTED_TYPE: return "unexpected_type";
461
462
			case MAPI_E_TOO_BIG: return "The table is too big for the requested operation to complete";
463
464
			case MAPI_E_DECLINE_COPY: return "The provider implements this method by calling a support object method, and the caller has passed the MAPI_DECLINE_OK flag";
465
466
			case MAPI_E_UNEXPECTED_ID: return "unexpected id";
467
468
			case MAPI_W_ERRORS_RETURNED: return "The call succeeded, but the message store provider has error information available";
469
470
			case MAPI_E_UNABLE_TO_COMPLETE: return "A complex operation such as building a table row set could not be completed";
471
472
			case MAPI_E_TIMEOUT: return "An asynchronous operation did not succeed within the specified time-out";
473
474
			case MAPI_E_TABLE_EMPTY: return "A table essential to the operation is empty";
475
476
			case MAPI_E_TABLE_TOO_BIG: return "The table is too big for the requested operation to complete";
477
478
			case MAPI_E_INVALID_BOOKMARK: return "The bookmark passed to a table operation was not created on the same table";
479
480
			case MAPI_W_POSITION_CHANGED: return "position changed";
481
482
			case MAPI_W_APPROX_COUNT: return "approx count";
483
484
			case MAPI_E_WAIT: return "A wait time-out has expired";
485
486
			case MAPI_E_CANCEL: return "The operation had to be canceled";
487
488
			case MAPI_E_NOT_ME: return "not me";
489
490
			case MAPI_W_CANCEL_MESSAGE: return "cancel message";
491
492
			case MAPI_E_CORRUPT_STORE: return "corrupt store";
493
494
			case MAPI_E_NOT_IN_QUEUE: return "not in queue";
495
496
			case MAPI_E_NO_SUPPRESS: return "The server does not support the suppression of read receipts";
497
498
			case MAPI_E_COLLISION: return "A folder or item cannot be created because one with the same name or other criteria already exists";
499
500
			case MAPI_E_NOT_INITIALIZED: return "The subsystem is not ready";
501
502
			case MAPI_E_NON_STANDARD: return "non standard";
503
504
			case MAPI_E_NO_RECIPIENTS: return "A message cannot be sent because it has no recipients";
505
506
			case MAPI_E_SUBMITTED: return "A message cannot be opened for modification because it has already been sent";
507
508
			case MAPI_E_HAS_FOLDERS: return "A folder cannot be deleted because it still contains subfolders";
509
510
			case MAPI_E_HAS_MESSAGES: return "A folder cannot be deleted because it still contains messages";
511
512
			case MAPI_E_FOLDER_CYCLE: return "A folder move or copy operation would create a cycle";
513
514
			case MAPI_W_PARTIAL_COMPLETION: return "The call succeeded, but not all entries were successfully operated on";
515
516
			case MAPI_E_AMBIGUOUS_RECIP: return "An unresolved recipient matches more than one directory entry";
517
518
			case MAPI_E_STORE_FULL: return "Store full";
519
520
			default: return sprintf("%xh", $e);
521
		}
522
	}
523