Utils::ConvertCodepage()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
nop 3
dl 0
loc 16
rs 10
c 1
b 0
f 0
nc 3
1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Several utility functions
9
 */
10
11
class Utils {
12
	/**
13
	 * Prints a variable as string
14
	 * If a boolean is sent, 'true' or 'false' is displayed.
15
	 *
16
	 * @param string $var
17
	 *
18
	 * @return string
19
	 */
20
	public static function PrintAsString($var) {
21
		return ($var) ? (($var === true) ? 'true' : $var) : (($var === false) ? 'false' : (($var === '') ? 'empty' : ($var ?? 'null')));
0 ignored issues
show
introduced by
The condition $var === false is always false.
Loading history...
introduced by
The condition $var === true is always false.
Loading history...
22
	}
23
24
	/**
25
	 * Splits a "domain\user" string into two values
26
	 * If the string contains only the user, domain is returned empty.
27
	 *
28
	 * @param string $domainuser
29
	 *
30
	 * @return array index 0: user  1: domain
31
	 */
32
	public static function SplitDomainUser($domainuser) {
33
		$pos = strrpos($domainuser, '\\');
34
		if ($pos === false) {
35
			$user = $domainuser;
36
			$domain = '';
37
		}
38
		else {
39
			$domain = substr($domainuser, 0, $pos);
40
			$user = substr($domainuser, $pos + 1);
41
		}
42
43
		return [$user, $domain];
44
	}
45
46
	/**
47
	 * Build an address string from the components.
48
	 *
49
	 * @param string $street  the street
50
	 * @param string $zip     the zip code
51
	 * @param string $city    the city
52
	 * @param string $state   the state
53
	 * @param string $country the country
54
	 *
55
	 * @return string the address string or null
56
	 */
57
	public static function BuildAddressString($street, $zip, $city, $state, $country) {
58
		$out = $country ?? "";
59
60
		$zcs = $zip ?? "";
61
		if ($city != "") {
62
			$zcs .= (($zcs) ? " " : "") . $city;
63
		}
64
		if ($state != "") {
65
			$zcs .= (($zcs) ? " " : "") . $state;
66
		}
67
		if ($zcs) {
68
			$out = $zcs . "\r\n" . $out;
69
		}
70
71
		if ($street != "") {
72
			$out = $street . (($out) ? "\r\n\r\n" . $out : "");
73
		}
74
75
		return $out ?? null;
76
	}
77
78
	/**
79
	 * Build the fileas string from the components according to the configuration.
80
	 *
81
	 * @param string $lastname
82
	 * @param string $firstname
83
	 * @param string $middlename
84
	 * @param string $company
85
	 *
86
	 * @return string fileas
87
	 */
88
	public static function BuildFileAs($lastname = "", $firstname = "", $middlename = "", $company = "") {
89
		if (defined('FILEAS_ORDER')) {
90
			$fileas = $lastfirst = $firstlast = "";
91
			$names = trim($firstname . " " . $middlename);
92
			$lastname = trim($lastname);
93
			$company = trim($company);
94
95
			// lastfirst is "lastname, firstname middlename"
96
			// firstlast is "firstname middlename lastname"
97
			if (strlen($lastname) > 0) {
98
				$lastfirst = $lastname;
99
				if (strlen($names) > 0) {
100
					$lastfirst .= ", {$names}";
101
					$firstlast = "{$names} {$lastname}";
102
				}
103
				else {
104
					$firstlast = $lastname;
105
				}
106
			}
107
			elseif (strlen($names) > 0) {
108
				$lastfirst = $firstlast = $names;
109
			}
110
111
			// if fileas with a company is selected
112
			// but company is empty then it will
113
			// fallback to firstlast or lastfirst
114
			// (depending on which is selected for company)
115
			switch (FILEAS_ORDER) {
116
				case SYNC_FILEAS_COMPANYONLY:
117
					if (strlen($company) > 0) {
118
						$fileas = $company;
119
					}
120
					elseif (strlen($firstlast) > 0) {
121
						$fileas = $lastfirst;
122
					}
123
					break;
124
125
				case SYNC_FILEAS_COMPANYLAST:
126
					if (strlen($company) > 0) {
127
						$fileas = $company;
128
						if (strlen($lastfirst) > 0) {
129
							$fileas .= "({$lastfirst})";
130
						}
131
					}
132
					elseif (strlen($lastfirst) > 0) {
133
						$fileas = $lastfirst;
134
					}
135
					break;
136
137
				case SYNC_FILEAS_COMPANYFIRST:
138
					if (strlen($company) > 0) {
139
						$fileas = $company;
140
						if (strlen($firstlast) > 0) {
141
							$fileas .= " ({$firstlast})";
142
						}
143
					}
144
					elseif (strlen($firstlast) > 0) {
145
						$fileas = $firstlast;
146
					}
147
					break;
148
149
				case SYNC_FILEAS_FIRSTCOMPANY:
150
					if (strlen($firstlast) > 0) {
151
						$fileas = $firstlast;
152
						if (strlen($company) > 0) {
153
							$fileas .= " ({$company})";
154
						}
155
					}
156
					elseif (strlen($company) > 0) {
157
						$fileas = $company;
158
					}
159
					break;
160
161
				case SYNC_FILEAS_LASTCOMPANY:
162
					if (strlen($lastfirst) > 0) {
163
						$fileas = $lastfirst;
164
						if (strlen($company) > 0) {
165
							$fileas .= " ({$company})";
166
						}
167
					}
168
					elseif (strlen($company) > 0) {
169
						$fileas = $company;
170
					}
171
					break;
172
173
				case SYNC_FILEAS_LASTFIRST:
174
					if (strlen($lastfirst) > 0) {
175
						$fileas = $lastfirst;
176
					}
177
					break;
178
179
				default:
180
					$fileas = $firstlast;
181
					break;
182
			}
183
			if (strlen($fileas) == 0) {
184
				SLog::Write(LOGLEVEL_DEBUG, "Fileas is empty.");
185
			}
186
187
			return $fileas;
188
		}
189
		SLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined. Add it to your config.php.");
190
191
		return null;
192
	}
193
194
	/**
195
	 * Extracts the basedate of the GlobalObjectID and the RecurStartTime.
196
	 *
197
	 * @param string $goid           OL compatible GlobalObjectID
198
	 * @param long   $recurStartTime
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...
199
	 *
200
	 * @return long basedate
201
	 */
202
	public static function ExtractBaseDate($goid, $recurStartTime) {
203
		$hexbase = substr(bin2hex($goid), 32, 8);
204
		$day = hexdec(substr($hexbase, 6, 2));
205
		$month = hexdec(substr($hexbase, 4, 2));
206
		$year = hexdec(substr($hexbase, 0, 4));
207
208
		if ($day && $month && $year) {
209
			$h = $recurStartTime >> 12;
210
			$m = ($recurStartTime - $h * 4096) >> 6;
211
			$s = $recurStartTime - $h * 4096 - $m * 64;
212
213
			return gmmktime($h, $m, $s, $month, $day, $year);
0 ignored issues
show
Bug introduced by
It seems like $day can also be of type double; however, parameter $day of gmmktime() does only seem to accept integer, 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

213
			return gmmktime($h, $m, $s, $month, /** @scrutinizer ignore-type */ $day, $year);
Loading history...
Bug Best Practice introduced by
The expression return gmmktime($h, $m, $s, $month, $day, $year) returns the type integer which is incompatible with the documented return type long.
Loading history...
Bug introduced by
It seems like $year can also be of type double; however, parameter $year of gmmktime() does only seem to accept integer, 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

213
			return gmmktime($h, $m, $s, $month, $day, /** @scrutinizer ignore-type */ $year);
Loading history...
Bug introduced by
It seems like $month can also be of type double; however, parameter $month of gmmktime() does only seem to accept integer, 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

213
			return gmmktime($h, $m, $s, /** @scrutinizer ignore-type */ $month, $day, $year);
Loading history...
214
		}
215
216
		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 long.
Loading history...
217
	}
218
219
	/**
220
	 * Converts SYNC_FILTERTYPE into a timestamp.
221
	 *
222
	 * @param int $filtertype Filtertype
223
	 *
224
	 * @return long
225
	 */
226
	public static function GetCutOffDate($filtertype) {
227
		$back = Utils::GetFiltertypeInterval($filtertype);
228
229
		if ($back === false) {
230
			return 0; // unlimited
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 returns the type integer which is incompatible with the documented return type long.
Loading history...
231
		}
232
233
		return time() - $back;
0 ignored issues
show
Bug Best Practice introduced by
The expression return time() - $back returns the type integer which is incompatible with the documented return type long.
Loading history...
234
	}
235
236
	/**
237
	 * Returns the interval indicated by the filtertype.
238
	 *
239
	 * @param int $filtertype
240
	 *
241
	 * @return bool|int returns false on invalid filtertype
242
	 */
243
	public static function GetFiltertypeInterval($filtertype) {
244
		return match ((int) $filtertype) {
245
			SYNC_FILTERTYPE_1DAY => 60 * 60 * 24,
246
			SYNC_FILTERTYPE_3DAYS => 60 * 60 * 24 * 3,
247
			SYNC_FILTERTYPE_1WEEK => 60 * 60 * 24 * 7,
248
			SYNC_FILTERTYPE_2WEEKS => 60 * 60 * 24 * 14,
249
			SYNC_FILTERTYPE_1MONTH => 60 * 60 * 24 * 31,
250
			SYNC_FILTERTYPE_3MONTHS => 60 * 60 * 24 * 31 * 3,
251
			SYNC_FILTERTYPE_6MONTHS => 60 * 60 * 24 * 31 * 6,
252
			default => false,
253
		};
254
	}
255
256
	/**
257
	 * Converts SYNC_TRUNCATION into bytes.
258
	 *
259
	 * @param int       SYNC_TRUNCATION
0 ignored issues
show
Bug introduced by
The type SYNC_TRUNCATION 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...
260
	 * @param mixed $truncation
261
	 *
262
	 * @return int
263
	 */
264
	public static function GetTruncSize($truncation) {
265
		return match ((int) $truncation) {
266
			SYNC_TRUNCATION_HEADERS => 0,
267
			SYNC_TRUNCATION_512B => 512,
268
			SYNC_TRUNCATION_1K => 1024,
269
			SYNC_TRUNCATION_2K => 2 * 1024,
270
			SYNC_TRUNCATION_5K => 5 * 1024,
271
			SYNC_TRUNCATION_10K => 10 * 1024,
272
			SYNC_TRUNCATION_20K => 20 * 1024,
273
			SYNC_TRUNCATION_50K => 50 * 1024,
274
			SYNC_TRUNCATION_100K => 100 * 1024,
275
			SYNC_TRUNCATION_ALL => 1024 * 1024,
276
			default => 1024,
277
		};
278
	}
279
280
	/**
281
	 * Truncate an UTF-8 encoded string correctly.
282
	 *
283
	 * If it's not possible to truncate properly, an empty string is returned
284
	 *
285
	 * @param string $string   the string
286
	 * @param string $length   position where string should be cut
287
	 * @param bool   $htmlsafe doesn't cut html tags in half, doesn't ensure correct html - default: false
288
	 *
289
	 * @return string truncated string
290
	 */
291
	public static function Utf8_truncate($string, $length, $htmlsafe = false) {
292
		// skip empty strings
293
		if (empty($string)) {
294
			return "";
295
		}
296
297
		// make sure length is always an integer
298
		$length = (int) $length;
299
300
		// if the input string is shorter then the trunction, make sure it's valid UTF-8!
301
		if (strlen($string) <= $length) {
302
			$length = strlen($string) - 1;
303
		}
304
305
		// The intent is not to cut HTML tags in half which causes displaying issues.
306
		// The used method just tries to cut outside of tags, without checking tag validity and closing tags.
307
		if ($htmlsafe) {
308
			$offset = 0 - strlen($string) + $length;
309
			$validPos = strrpos($string, "<", $offset);
310
			if ($validPos > strrpos($string, ">", $offset)) {
311
				$length = $validPos;
312
			}
313
		}
314
315
		while ($length >= 0) {
316
			if ((ord($string[$length]) < 0x80) || (ord($string[$length]) >= 0xC0)) {
317
				return substr($string, 0, $length);
318
			}
319
			--$length;
320
		}
321
322
		return "";
323
	}
324
325
	/**
326
	 * Indicates if the specified folder type is a system folder.
327
	 *
328
	 * @param int $foldertype
329
	 *
330
	 * @return bool
331
	 */
332
	public static function IsSystemFolder($foldertype) {
333
		return (
334
			$foldertype == SYNC_FOLDER_TYPE_INBOX ||
335
			$foldertype == SYNC_FOLDER_TYPE_DRAFTS ||
336
			$foldertype == SYNC_FOLDER_TYPE_WASTEBASKET ||
337
			$foldertype == SYNC_FOLDER_TYPE_SENTMAIL ||
338
			$foldertype == SYNC_FOLDER_TYPE_OUTBOX ||
339
			$foldertype == SYNC_FOLDER_TYPE_TASK ||
340
			$foldertype == SYNC_FOLDER_TYPE_APPOINTMENT ||
341
			$foldertype == SYNC_FOLDER_TYPE_CONTACT ||
342
			$foldertype == SYNC_FOLDER_TYPE_NOTE ||
343
			$foldertype == SYNC_FOLDER_TYPE_JOURNAL
344
		) ? true : false;
345
	}
346
347
	/**
348
	 * Checks for valid email addresses
349
	 * The used regex actually only checks if a valid email address is part of the submitted string
350
	 * it also returns true for the mailbox format, but this is not checked explicitly.
351
	 *
352
	 * @param string $email address to be checked
353
	 *
354
	 * @return bool
355
	 */
356
	public static function CheckEmail($email) {
357
		return str_contains($email, '@') ? true : false;
358
	}
359
360
	/**
361
	 * Checks if a string is base64 encoded.
362
	 *
363
	 * @param string $string the string to be checked
364
	 *
365
	 * @return bool
366
	 */
367
	public static function IsBase64String($string) {
368
		return (bool) preg_match("#^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+/]{4})?$#", $string);
369
	}
370
371
	/**
372
	 * Returns a command string for a given command code.
373
	 *
374
	 * @param int $code
375
	 *
376
	 * @return string or false if code is unknown
377
	 */
378
	public static function GetCommandFromCode($code) {
379
		return match ($code) {
380
			GSync::COMMAND_SYNC => 'Sync',
381
			GSync::COMMAND_SENDMAIL => 'SendMail',
382
			GSync::COMMAND_SMARTFORWARD => 'SmartForward',
383
			GSync::COMMAND_SMARTREPLY => 'SmartReply',
384
			GSync::COMMAND_GETATTACHMENT => 'GetAttachment',
385
			GSync::COMMAND_FOLDERSYNC => 'FolderSync',
386
			GSync::COMMAND_FOLDERCREATE => 'FolderCreate',
387
			GSync::COMMAND_FOLDERDELETE => 'FolderDelete',
388
			GSync::COMMAND_FOLDERUPDATE => 'FolderUpdate',
389
			GSync::COMMAND_MOVEITEMS => 'MoveItems',
390
			GSync::COMMAND_GETITEMESTIMATE => 'GetItemEstimate',
391
			GSync::COMMAND_MEETINGRESPONSE => 'MeetingResponse',
392
			GSync::COMMAND_SEARCH => 'Search',
393
			GSync::COMMAND_SETTINGS => 'Settings',
394
			GSync::COMMAND_PING => 'Ping',
395
			GSync::COMMAND_ITEMOPERATIONS => 'ItemOperations',
396
			GSync::COMMAND_PROVISION => 'Provision',
397
			GSync::COMMAND_RESOLVERECIPIENTS => 'ResolveRecipients',
398
			GSync::COMMAND_VALIDATECERT => 'ValidateCert',
399
			GSync::COMMAND_GETHIERARCHY => 'GetHierarchy',
400
			GSync::COMMAND_CREATECOLLECTION => 'CreateCollection',
401
			GSync::COMMAND_DELETECOLLECTION => 'DeleteCollection',
402
			GSync::COMMAND_MOVECOLLECTION => 'MoveCollection',
403
			GSync::COMMAND_NOTIFY => 'Notify',
404
			GSync::COMMAND_FIND => 'Find',
405
			default => false,
406
		};
407
	}
408
409
	/**
410
	 * Returns a command code for a given command.
411
	 *
412
	 * @param string $command
413
	 *
414
	 * @return int or false if command is unknown
415
	 */
416
	public static function GetCodeFromCommand($command) {
417
		switch ($command) {
418
			case 'Sync':                 return GSync::COMMAND_SYNC;
419
420
			case 'SendMail':             return GSync::COMMAND_SENDMAIL;
421
422
			case 'SmartForward':         return GSync::COMMAND_SMARTFORWARD;
423
424
			case 'SmartReply':           return GSync::COMMAND_SMARTREPLY;
425
426
			case 'GetAttachment':        return GSync::COMMAND_GETATTACHMENT;
427
428
			case 'FolderSync':           return GSync::COMMAND_FOLDERSYNC;
429
430
			case 'FolderCreate':         return GSync::COMMAND_FOLDERCREATE;
431
432
			case 'FolderDelete':         return GSync::COMMAND_FOLDERDELETE;
433
434
			case 'FolderUpdate':         return GSync::COMMAND_FOLDERUPDATE;
435
436
			case 'MoveItems':            return GSync::COMMAND_MOVEITEMS;
437
438
			case 'GetItemEstimate':      return GSync::COMMAND_GETITEMESTIMATE;
439
440
			case 'MeetingResponse':      return GSync::COMMAND_MEETINGRESPONSE;
441
442
			case 'Search':               return GSync::COMMAND_SEARCH;
443
444
			case 'Settings':             return GSync::COMMAND_SETTINGS;
445
446
			case 'Ping':                 return GSync::COMMAND_PING;
447
448
			case 'ItemOperations':       return GSync::COMMAND_ITEMOPERATIONS;
449
450
			case 'Provision':            return GSync::COMMAND_PROVISION;
451
452
			case 'ResolveRecipients':    return GSync::COMMAND_RESOLVERECIPIENTS;
453
454
			case 'ValidateCert':         return GSync::COMMAND_VALIDATECERT;
455
456
				// Deprecated commands
457
			case 'GetHierarchy':         return GSync::COMMAND_GETHIERARCHY;
458
459
			case 'CreateCollection':     return GSync::COMMAND_CREATECOLLECTION;
460
461
			case 'DeleteCollection':     return GSync::COMMAND_DELETECOLLECTION;
462
463
			case 'MoveCollection':       return GSync::COMMAND_MOVECOLLECTION;
464
465
			case 'Notify':               return GSync::COMMAND_NOTIFY;
466
467
			case 'Find':                 return GSync::COMMAND_FIND;
468
		}
469
470
		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 integer.
Loading history...
471
	}
472
473
	/**
474
	 * Normalize the given timestamp to the start of the day.
475
	 *
476
	 * @param long $timestamp
477
	 *
478
	 * @return long
479
	 */
480
	public static function getDayStartOfTimestamp($timestamp) {
481
		return $timestamp - ($timestamp % (60 * 60 * 24));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $timestamp - $timestamp % 60 * 60 * 24 returns the type integer which is incompatible with the documented return type long.
Loading history...
482
	}
483
484
	/**
485
	 * Returns a formatted string output from an optional timestamp.
486
	 * If no timestamp is sent, NOW is used.
487
	 *
488
	 * @param long $timestamp
489
	 *
490
	 * @return string
491
	 */
492
	public static function GetFormattedTime($timestamp = false) {
493
		if (!$timestamp) {
494
			$timestamp = time();
495
		}
496
497
		return date("d/m/Y H:i:s", $timestamp);
0 ignored issues
show
Bug introduced by
It seems like $timestamp can also be of type long; however, parameter $timestamp of date() does only seem to accept integer|null, 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

497
		return date("d/m/Y H:i:s", /** @scrutinizer ignore-type */ $timestamp);
Loading history...
498
	}
499
500
	/**
501
	 * Get charset name from a codepage.
502
	 *
503
	 * @see http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx
504
	 *
505
	 * Table taken from common/codepage.cpp
506
	 *
507
	 * @param int codepage Codepage
0 ignored issues
show
Bug introduced by
The type codepage 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...
508
	 * @param mixed $codepage
509
	 *
510
	 * @return string iconv-compatible charset name
511
	 */
512
	public static function GetCodepageCharset($codepage) {
513
		$codepages = [
514
			20106 => "DIN_66003",
515
			20108 => "NS_4551-1",
516
			20107 => "SEN_850200_B",
517
			950 => "big5",
518
			50221 => "csISO2022JP",
519
			51932 => "euc-jp",
520
			51936 => "euc-cn",
521
			51949 => "euc-kr",
522
			949 => "euc-kr",
523
			936 => "gb18030",
524
			52936 => "csgb2312",
525
			852 => "ibm852",
526
			866 => "ibm866",
527
			50220 => "iso-2022-jp",
528
			50222 => "iso-2022-jp",
529
			50225 => "iso-2022-kr",
530
			1252 => "windows-1252",
531
			28591 => "iso-8859-1",
532
			28592 => "iso-8859-2",
533
			28593 => "iso-8859-3",
534
			28594 => "iso-8859-4",
535
			28595 => "iso-8859-5",
536
			28596 => "iso-8859-6",
537
			28597 => "iso-8859-7",
538
			28598 => "iso-8859-8",
539
			28599 => "iso-8859-9",
540
			28603 => "iso-8859-13",
541
			28605 => "iso-8859-15",
542
			20866 => "koi8-r",
543
			21866 => "koi8-u",
544
			932 => "shift-jis",
545
			1200 => "unicode",
546
			1201 => "unicodebig",
547
			65000 => "utf-7",
548
			65001 => "utf-8",
549
			1250 => "windows-1250",
550
			1251 => "windows-1251",
551
			1253 => "windows-1253",
552
			1254 => "windows-1254",
553
			1255 => "windows-1255",
554
			1256 => "windows-1256",
555
			1257 => "windows-1257",
556
			1258 => "windows-1258",
557
			874 => "windows-874",
558
			20127 => "us-ascii",
559
		];
560
561
		// Defaulting to iso-8859-15 since it is more likely for someone to make a mistake in the codepage
562
		// when using west-european charsets then when using other charsets since utf-8 is binary compatible
563
		// with the bottom 7 bits of west-european
564
		return $codepages[$codepage] ?? "iso-8859-15";
565
	}
566
567
	/**
568
	 * Converts a string encoded with codepage into an UTF-8 string.
569
	 *
570
	 * @param int    $codepage
571
	 * @param string $string
572
	 *
573
	 * @return string
574
	 */
575
	public static function ConvertCodepageStringToUtf8($codepage, $string) {
576
		if (function_exists("iconv")) {
577
			$charset = self::GetCodepageCharset($codepage);
578
579
			return iconv($charset, "utf-8", $string);
580
		}
581
582
		SLog::Write(LOGLEVEL_WARN, "Utils::ConvertCodepageStringToUtf8() 'iconv' is not available. Charset conversion skipped.");
583
584
		return $string;
585
	}
586
587
	/**
588
	 * Converts a string to another charset.
589
	 *
590
	 * @param int    $in
591
	 * @param int    $out
592
	 * @param string $string
593
	 *
594
	 * @return string
595
	 */
596
	public static function ConvertCodepage($in, $out, $string) {
597
		// do nothing if both charsets are the same
598
		if ($in == $out) {
599
			return $string;
600
		}
601
602
		if (function_exists("iconv")) {
603
			$inCharset = self::GetCodepageCharset($in);
604
			$outCharset = self::GetCodepageCharset($out);
605
606
			return iconv($inCharset, $outCharset, $string);
607
		}
608
609
		SLog::Write(LOGLEVEL_WARN, "Utils::ConvertCodepage() 'iconv' is not available. Charset conversion skipped.");
610
611
		return $string;
612
	}
613
614
	/**
615
	 * Returns the best match of preferred body preference types.
616
	 *
617
	 * @param array $bpTypes
618
	 *
619
	 * @return int
620
	 */
621
	public static function GetBodyPreferenceBestMatch($bpTypes) {
622
		if ($bpTypes === false) {
0 ignored issues
show
introduced by
The condition $bpTypes === false is always false.
Loading history...
623
			return SYNC_BODYPREFERENCE_PLAIN;
624
		}
625
		// The bettter choice is HTML and then MIME in order to save bandwidth
626
		// because MIME is a complete message including the headers and attachments
627
		if (in_array(SYNC_BODYPREFERENCE_HTML, $bpTypes)) {
628
			return SYNC_BODYPREFERENCE_HTML;
629
		}
630
		if (in_array(SYNC_BODYPREFERENCE_MIME, $bpTypes)) {
631
			return SYNC_BODYPREFERENCE_MIME;
632
		}
633
634
		return SYNC_BODYPREFERENCE_PLAIN;
635
	}
636
637
	/**
638
	 * Returns AS-style LastVerbExecuted value from the server value.
639
	 *
640
	 * @param int $verb
641
	 *
642
	 * @return int
643
	 */
644
	public static function GetLastVerbExecuted($verb) {
645
		return match ((int) $verb) {
646
			NOTEIVERB_REPLYTOSENDER => AS_REPLYTOSENDER,
647
			NOTEIVERB_REPLYTOALL => AS_REPLYTOALL,
648
			NOTEIVERB_FORWARD => AS_FORWARD,
649
			default => 0,
650
		};
651
	}
652
653
	/**
654
	 * Returns the local part from email address.
655
	 *
656
	 * @param string $email
657
	 *
658
	 * @return string
659
	 */
660
	public static function GetLocalPartFromEmail($email) {
661
		$pos = strpos($email, '@');
662
		if ($pos === false) {
663
			return $email;
664
		}
665
666
		return substr($email, 0, $pos);
667
	}
668
669
	/**
670
	 * Format bytes to a more human readable value.
671
	 *
672
	 * @param int $bytes
673
	 * @param int $precision
674
	 *
675
	 * @return string|void
676
	 */
677
	public static function FormatBytes($bytes, $precision = 2) {
678
		if ($bytes <= 0) {
679
			return '0 B';
680
		}
681
682
		$units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB'];
683
		$base = log($bytes, 1024);
684
		$fBase = floor($base);
685
		$pow = 1024 ** ($base - $fBase);
686
687
		return sprintf("%.{$precision}f %s", $pow, $units[$fBase]);
688
	}
689
690
	/**
691
	 * Returns folder origin identifier from its id.
692
	 *
693
	 * @param string $folderid
694
	 *
695
	 * @return bool|string matches values of DeviceManager::FLD_ORIGIN_*
696
	 */
697
	public static function GetFolderOriginFromId($folderid) {
698
		$origin = substr($folderid, 0, 1);
699
700
		switch ($origin) {
701
			case DeviceManager::FLD_ORIGIN_CONFIG:
702
			case DeviceManager::FLD_ORIGIN_GAB:
703
			case DeviceManager::FLD_ORIGIN_SHARED:
704
			case DeviceManager::FLD_ORIGIN_USER:
705
			case DeviceManager::FLD_ORIGIN_IMPERSONATED:
706
				return $origin;
707
		}
708
		SLog::Write(LOGLEVEL_WARN, sprintf("Utils->GetFolderOriginFromId(): Unknown folder origin for folder with id '%s'", $folderid));
709
710
		return false;
711
	}
712
713
	/**
714
	 * Returns folder origin as string from its id.
715
	 *
716
	 * @param string $folderid
717
	 *
718
	 * @return string
719
	 */
720
	public static function GetFolderOriginStringFromId($folderid) {
721
		$origin = substr($folderid, 0, 1);
722
723
		switch ($origin) {
724
			case DeviceManager::FLD_ORIGIN_CONFIG:
725
				return 'configured';
726
727
			case DeviceManager::FLD_ORIGIN_GAB:
728
				return 'GAB';
729
730
			case DeviceManager::FLD_ORIGIN_SHARED:
731
				return 'shared';
732
733
			case DeviceManager::FLD_ORIGIN_USER:
734
				return 'user';
735
736
			case DeviceManager::FLD_ORIGIN_IMPERSONATED:
737
				return 'impersonated';
738
		}
739
		SLog::Write(LOGLEVEL_WARN, sprintf("Utils->GetFolderOriginStringFromId(): Unknown folder origin for folder with id '%s'", $folderid));
740
741
		return 'unknown';
742
	}
743
744
	/**
745
	 * Splits the id into folder id and message id parts. A colon in the $id indicates
746
	 * that the id has folderid:messageid format.
747
	 *
748
	 * @param string $id
749
	 *
750
	 * @return array
751
	 */
752
	public static function SplitMessageId($id) {
753
		if (str_contains($id, ':')) {
754
			return explode(':', $id);
755
		}
756
757
		return [null, $id];
758
	}
759
760
	/**
761
	 * Transforms an AS timestamp into a unix timestamp.
762
	 *
763
	 * @param string $ts
764
	 *
765
	 * @return long
766
	 */
767
	public static function ParseDate($ts) {
768
		if (preg_match("/(\\d{4})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(T(\\d{2})[^0-9]*(\\d{2})[^0-9]*(\\d{2})(.\\d+)?Z){0,1}$/", $ts, $matches)) {
769
			if ($matches[1] >= 2038) {
770
				$matches[1] = 2038;
771
				$matches[2] = 1;
772
				$matches[3] = 18;
773
				$matches[5] = $matches[6] = $matches[7] = 0;
774
			}
775
776
			if (!isset($matches[5])) {
777
				$matches[5] = 0;
778
			}
779
			if (!isset($matches[6])) {
780
				$matches[6] = 0;
781
			}
782
			if (!isset($matches[7])) {
783
				$matches[7] = 0;
784
			}
785
786
			return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return gmmktime($matches...atches[3], $matches[1]) returns the type integer which is incompatible with the documented return type long.
Loading history...
787
		}
788
789
		return 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 returns the type integer which is incompatible with the documented return type long.
Loading history...
790
	}
791
792
	/**
793
	 * Transforms an unix timestamp into an AS timestamp or a human readable format.
794
	 *
795
	 * Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items
796
	 * and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for
797
	 * exchange' depends on this quirk. So we have to send a different date type depending on where
798
	 * it's used. Sigh.
799
	 *
800
	 * @param int $ts
801
	 * @param int $type int (StreamerType) (optional) if not set a human readable format is returned
802
	 *
803
	 * @return string
804
	 */
805
	public static function FormatDate($ts, $type = "") {
806
		// fallback to a human readable format (used for logging)
807
		$formatString = "yyyy-MM-dd HH:mm:SS' UTC'";
808
		if ($type == Streamer::STREAMER_TYPE_DATE) {
809
			$formatString = "yyyyMMdd'T'HHmmSS'Z'";
810
		}
811
		elseif ($type == Streamer::STREAMER_TYPE_DATE_DASHES) {
812
			$formatString = "yyyy-MM-dd'T'HH:mm:SS'.000Z'";
813
		}
814
815
		$formatter = datefmt_create(
816
			'en_US',
817
			IntlDateFormatter::FULL,
818
			IntlDateFormatter::FULL,
819
			'UTC',
820
			IntlDateFormatter::GREGORIAN,
821
			$formatString
822
		);
823
824
		return datefmt_format($formatter, $ts);
825
	}
826
827
	/**
828
	 * Returns the appropriate SyncObjectResponse object based on message class.
829
	 *
830
	 * @param string $messageClass
831
	 *
832
	 * @return object
833
	 */
834
	public static function GetResponseFromMessageClass($messageClass) {
835
		$messageClass = strtolower($messageClass);
836
837
		return match ($messageClass) {
838
			'syncappointment' => new SyncAppointmentResponse(),
839
			'synccontact' => new SyncContactResponse(),
840
			'syncnote' => new SyncNoteResponse(),
841
			'synctask' => new SyncTaskResponse(),
842
			default => new SyncMailResponse(),
843
		};
844
845
		return new SyncMailResponse();
0 ignored issues
show
Unused Code introduced by
return new SyncMailResponse() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
846
	}
847
}
848