Passed
Push — master ( 22f211...6252d4 )
by
unknown
19:34 queued 05:30
created

Request::SetUserIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
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-2025 grommunio GmbH
7
 *
8
 * This class checks and processes all incoming data of the request.
9
 */
10
11
class Request {
12
	public const MAXMEMORYUSAGE = 0.9;     // use max. 90% of allowed memory when syncing
13
	public const UNKNOWN = "unknown";
14
	public const IMPERSONATE_DELIM = '#';
15
16
	/**
17
	 * self::filterEvilInput() options.
18
	 */
19
	public const LETTERS_ONLY = 1;
20
	public const HEX_ONLY = 2;
21
	public const WORDCHAR_ONLY = 3;
22
	public const NUMBERS_ONLY = 4;
23
	public const NUMBERSDOT_ONLY = 5;
24
	public const HEX_EXTENDED = 6;
25
	public const ISO8601 = 7;
26
	public const HEX_EXTENDED2 = 8;
27
28
	/**
29
	 * Command parameters for base64 encoded requests (AS >= 12.1).
30
	 */
31
	public const COMMANDPARAM_ATTACHMENTNAME = 0;
32
	public const COMMANDPARAM_COLLECTIONID = 1; // deprecated
33
	public const COMMANDPARAM_COLLECTIONNAME = 2; // deprecated
34
	public const COMMANDPARAM_ITEMID = 3;
35
	public const COMMANDPARAM_LONGID = 4;
36
	public const COMMANDPARAM_PARENTID = 5; // deprecated
37
	public const COMMANDPARAM_OCCURRENCE = 6;
38
	public const COMMANDPARAM_OPTIONS = 7; // used by SmartReply, SmartForward, SendMail, ItemOperations
39
	public const COMMANDPARAM_USER = 8; // used by any command
40
	// possible bitflags for COMMANDPARAM_OPTIONS
41
	public const COMMANDPARAM_OPTIONS_SAVEINSENT = 0x01;
42
	public const COMMANDPARAM_OPTIONS_ACCEPTMULTIPART = 0x02;
43
44
	private static $input;
45
	private static $output;
46
	private static $headers;
47
	private static $command;
48
	private static $method;
49
	private static $remoteAddr;
50
	private static $getUser;
51
	private static $devid;
52
	private static $devtype;
53
	private static $authUserString;
54
	private static $authUser;
55
	private static $authDomain;
56
	private static $authPassword;
57
	private static $impersonatedUser;
58
	private static $userIdentifier;
59
	private static $asProtocolVersion;
60
	private static $policykey;
61
	private static $useragent;
62
	private static $attachmentName;
63
	private static $collectionId;
64
	private static $itemId;
65
	private static $longId; // TODO
0 ignored issues
show
introduced by
The private property $longId is not used, and could be removed.
Loading history...
66
	private static $occurrence; // TODO
0 ignored issues
show
introduced by
The private property $occurrence is not used, and could be removed.
Loading history...
67
	private static $saveInSent;
68
	private static $acceptMultipart;
69
	private static $base64QueryDecoded;
70
	private static $expectedConnectionTimeout;
71
	private static $memoryLimit;
72
73
	/**
74
	 * Initializes request data.
75
	 */
76
	public static function Initialize() {
77
		// try to open stdin & stdout
78
		self::$input = fopen("php://input", "r");
79
		self::$output = fopen("php://output", "w+");
80
81
		// Parse the standard GET parameters
82
		if (isset($_GET["Cmd"])) {
83
			self::$command = self::filterEvilInput($_GET["Cmd"], self::LETTERS_ONLY);
84
		}
85
86
		// getUser is unfiltered, as everything is allowed.. even "/", "\" or ".."
87
		if (isset($_GET["User"])) {
88
			self::$getUser = strtolower($_GET["User"]);
89
			if (defined('USE_FULLEMAIL_FOR_LOGIN') && !USE_FULLEMAIL_FOR_LOGIN) {
90
				self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
91
			}
92
		}
93
		if (isset($_GET["DeviceId"])) {
94
			self::$devid = strtolower(self::filterEvilInput($_GET["DeviceId"], self::WORDCHAR_ONLY));
95
		}
96
		if (isset($_GET["DeviceType"])) {
97
			self::$devtype = self::filterEvilInput($_GET["DeviceType"], self::LETTERS_ONLY);
98
		}
99
		if (isset($_GET["AttachmentName"])) {
100
			self::$attachmentName = self::filterEvilInput($_GET["AttachmentName"], self::HEX_EXTENDED2);
101
		}
102
		if (isset($_GET["CollectionId"])) {
103
			self::$collectionId = self::filterEvilInput($_GET["CollectionId"], self::HEX_EXTENDED2);
104
		}
105
		if (isset($_GET["ItemId"])) {
106
			self::$itemId = self::filterEvilInput($_GET["ItemId"], self::HEX_EXTENDED2);
107
		}
108
		if (isset($_GET["SaveInSent"]) && $_GET["SaveInSent"] == "T") {
109
			self::$saveInSent = true;
110
		}
111
112
		if (isset($_SERVER["REQUEST_METHOD"])) {
113
			self::$method = self::filterEvilInput($_SERVER["REQUEST_METHOD"], self::LETTERS_ONLY);
114
		}
115
		// TODO check IPv6 addresses
116
		if (isset($_SERVER["REMOTE_ADDR"])) {
117
			self::$remoteAddr = self::filterIP($_SERVER["REMOTE_ADDR"]);
118
		}
119
120
		// in protocol version > 14 mobile send these inputs as encoded query string
121
		if (!isset(self::$command) && !empty($_SERVER['QUERY_STRING']) && Utils::IsBase64String($_SERVER['QUERY_STRING'])) {
122
			self::decodeBase64URI();
123
			if (!isset(self::$command) && isset(self::$base64QueryDecoded['Command'])) {
124
				self::$command = Utils::GetCommandFromCode(self::$base64QueryDecoded['Command']);
125
			}
126
127
			if (!isset(self::$getUser) && isset(self::$base64QueryDecoded[self::COMMANDPARAM_USER])) {
128
				self::$getUser = strtolower(self::$base64QueryDecoded[self::COMMANDPARAM_USER]);
129
				if (defined('USE_FULLEMAIL_FOR_LOGIN') && !USE_FULLEMAIL_FOR_LOGIN) {
130
					self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
131
				}
132
			}
133
134
			if (!isset(self::$devid) && isset(self::$base64QueryDecoded['DevID'])) {
135
				self::$devid = strtolower(self::filterEvilInput(self::$base64QueryDecoded['DevID'], self::WORDCHAR_ONLY));
136
			}
137
138
			if (!isset(self::$devtype) && isset(self::$base64QueryDecoded['DevType'])) {
139
				self::$devtype = self::filterEvilInput(self::$base64QueryDecoded['DevType'], self::LETTERS_ONLY);
140
			}
141
142
			if (isset(self::$base64QueryDecoded['PolKey'])) {
143
				self::$policykey = (int) self::filterEvilInput(self::$base64QueryDecoded['PolKey'], self::NUMBERS_ONLY);
144
			}
145
146
			if (isset(self::$base64QueryDecoded['ProtVer'])) {
147
				self::$asProtocolVersion = self::filterEvilInput(self::$base64QueryDecoded['ProtVer'], self::NUMBERS_ONLY) / 10;
148
			}
149
150
			if (isset(self::$base64QueryDecoded[self::COMMANDPARAM_ATTACHMENTNAME])) {
151
				self::$attachmentName = self::filterEvilInput(self::$base64QueryDecoded[self::COMMANDPARAM_ATTACHMENTNAME], self::HEX_EXTENDED2);
152
			}
153
154
			if (isset(self::$base64QueryDecoded[self::COMMANDPARAM_COLLECTIONID])) {
155
				self::$collectionId = self::filterEvilInput(self::$base64QueryDecoded[self::COMMANDPARAM_COLLECTIONID], self::HEX_EXTENDED2);
156
			}
157
158
			if (isset(self::$base64QueryDecoded[self::COMMANDPARAM_ITEMID])) {
159
				self::$itemId = self::filterEvilInput(self::$base64QueryDecoded[self::COMMANDPARAM_ITEMID], self::HEX_EXTENDED2);
160
			}
161
162
			if (isset(self::$base64QueryDecoded[self::COMMANDPARAM_OPTIONS]) && (ord(self::$base64QueryDecoded[self::COMMANDPARAM_OPTIONS]) & self::COMMANDPARAM_OPTIONS_SAVEINSENT)) {
163
				self::$saveInSent = true;
164
			}
165
166
			if (isset(self::$base64QueryDecoded[self::COMMANDPARAM_OPTIONS]) && (ord(self::$base64QueryDecoded[self::COMMANDPARAM_OPTIONS]) & self::COMMANDPARAM_OPTIONS_ACCEPTMULTIPART)) {
167
				self::$acceptMultipart = true;
168
			}
169
		}
170
171
		// in base64 encoded query string user is not necessarily set
172
		if (!isset(self::$getUser) && isset($_SERVER['PHP_AUTH_USER'])) {
173
			list(self::$getUser) = Utils::SplitDomainUser(strtolower($_SERVER['PHP_AUTH_USER']));
174
			if (defined('USE_FULLEMAIL_FOR_LOGIN') && !USE_FULLEMAIL_FOR_LOGIN) {
175
				self::$getUser = Utils::GetLocalPartFromEmail(self::$getUser);
176
			}
177
		}
178
179
		// authUser & authPassword are unfiltered!
180
		// split username & domain if received as one
181
		if (isset($_SERVER['PHP_AUTH_USER'])) {
182
			list(self::$authUserString, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
183
			self::$authPassword = (isset($_SERVER['PHP_AUTH_PW'])) ? $_SERVER['PHP_AUTH_PW'] : "";
184
		}
185
186
		// process impersonation
187
		self::$authUser = self::$authUserString;
188
189
		if (defined('USE_FULLEMAIL_FOR_LOGIN') && !USE_FULLEMAIL_FOR_LOGIN) {
190
			self::$authUser = Utils::GetLocalPartFromEmail(self::$authUser);
191
		}
192
193
		// get & convert configured memory limit
194
		$memoryLimit = ini_get('memory_limit');
195
		if ($memoryLimit == -1) {
196
			self::$memoryLimit = false;
197
		}
198
		else {
199
			preg_replace_callback(
200
				'/(\-?\d+)(.?)/',
201
				function ($m) {
202
					self::$memoryLimit = $m[1] * pow(1024, strpos('BKMG', $m[2])) * self::MAXMEMORYUSAGE;
203
				},
204
				strtoupper($memoryLimit)
205
			);
206
		}
207
	}
208
209
	/**
210
	 * Reads and processes the request headers.
211
	 */
212
	public static function ProcessHeaders() {
213
		self::$headers = array_change_key_case(apache_request_headers(), CASE_LOWER);
0 ignored issues
show
Bug introduced by
It seems like apache_request_headers() can also be of type true; however, parameter $array of array_change_key_case() does only seem to accept array, 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
		self::$headers = array_change_key_case(/** @scrutinizer ignore-type */ apache_request_headers(), CASE_LOWER);
Loading history...
214
		self::$useragent = (isset(self::$headers["user-agent"])) ? self::$headers["user-agent"] : self::UNKNOWN;
215
		if (!isset(self::$asProtocolVersion)) {
216
			self::$asProtocolVersion = (isset(self::$headers["ms-asprotocolversion"])) ? self::filterEvilInput(self::$headers["ms-asprotocolversion"], self::NUMBERSDOT_ONLY) : GSync::GetLatestSupportedASVersion();
217
		}
218
219
		// if policykey is not yet set, try to set it from the header
220
		// the policy key might be set in Request::Initialize from the base64 encoded query
221
		if (!isset(self::$policykey)) {
222
			if (isset(self::$headers["x-ms-policykey"])) {
223
				self::$policykey = (int) self::filterEvilInput(self::$headers["x-ms-policykey"], self::NUMBERS_ONLY);
224
			}
225
			else {
226
				self::$policykey = 0;
227
			}
228
		}
229
230
		if (isset(self::$base64QueryDecoded)) {
231
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Request::ProcessHeaders(): base64 query string: '%s' (decoded: '%s')", $_SERVER['QUERY_STRING'], http_build_query(self::$base64QueryDecoded, '', ',')));
232
			if (isset(self::$policykey)) {
233
				self::$headers["x-ms-policykey"] = self::$policykey;
234
			}
235
236
			if (isset(self::$asProtocolVersion)) {
237
				self::$headers["ms-asprotocolversion"] = self::$asProtocolVersion;
238
			}
239
		}
240
241
		if (!isset(self::$acceptMultipart) && isset(self::$headers["ms-asacceptmultipart"]) && strtoupper(self::$headers["ms-asacceptmultipart"]) == "T") {
242
			self::$acceptMultipart = true;
243
		}
244
245
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Request::ProcessHeaders() ASVersion: %s", self::$asProtocolVersion));
246
247
		if (defined('USE_CUSTOM_REMOTE_IP_HEADER') && USE_CUSTOM_REMOTE_IP_HEADER !== false) {
0 ignored issues
show
introduced by
The condition USE_CUSTOM_REMOTE_IP_HEADER !== false is always false.
Loading history...
248
			// make custom header compatible with Apache modphp
249
			$header = $apacheHeader = strtolower(USE_CUSTOM_REMOTE_IP_HEADER);
250
			if (substr($apacheHeader, 0, 5) === 'http_') {
251
				$apacheHeader = substr($apacheHeader, 5);
252
			}
253
			$apacheHeader = str_replace("_", "-", $apacheHeader);
254
			if (isset(self::$headers[$header]) || isset(self::$headers[$apacheHeader])) {
255
				$remoteIP = isset(self::$headers[$header]) ? self::$headers[$header] : self::$headers[$apacheHeader];
256
				// X-Forwarded-For may contain multiple IPs separated by comma: client, proxy1, proxy2.
257
				// In such case we will only check the client IP.
258
				if (strpos($remoteIP, ',') !== false) {
259
					$remoteIP = trim(explode(',', $remoteIP)[0]);
260
				}
261
				$remoteIP = self::filterIP($remoteIP);
262
				if ($remoteIP) {
263
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Using custom header '%s' to determine remote IP: %s - connect is coming from IP: %s", USE_CUSTOM_REMOTE_IP_HEADER, $remoteIP, self::$remoteAddr));
264
					self::$remoteAddr = $remoteIP;
265
				}
266
			}
267
		}
268
	}
269
270
	/**
271
	 * @return bool data sent or not
272
	 */
273
	public static function HasAuthenticationInfo() {
274
		return self::$authUser != "" && self::$authPassword != "";
275
	}
276
277
	/*----------------------------------------------------------------------------------------------------------
278
	 * Getter & Checker
279
	 */
280
281
	/**
282
	 * Returns the input stream.
283
	 *
284
	 * @return bool|handle false if not available
0 ignored issues
show
Bug introduced by
The type handle 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...
285
	 */
286
	public static function GetInputStream() {
287
		if (isset(self::$input)) {
288
			return self::$input;
289
		}
290
291
		return false;
292
	}
293
294
	/**
295
	 * Returns the output stream.
296
	 *
297
	 * @return bool|handle false if not available
298
	 */
299
	public static function GetOutputStream() {
300
		if (isset(self::$output)) {
301
			return self::$output;
302
		}
303
304
		return false;
305
	}
306
307
	/**
308
	 * Returns the request method.
309
	 *
310
	 * @return string
311
	 */
312
	public static function GetMethod() {
313
		if (isset(self::$method)) {
314
			return self::$method;
315
		}
316
317
		return self::UNKNOWN;
318
	}
319
320
	/**
321
	 * Returns the value of the user parameter of the querystring.
322
	 *
323
	 * @return bool|string false if not available
324
	 */
325
	public static function GetGETUser() {
326
		if (isset(self::$getUser)) {
327
			return self::$getUser;
328
		}
329
330
		return self::UNKNOWN;
331
	}
332
333
	/**
334
	 * Returns the value of the ItemId parameter of the querystring.
335
	 *
336
	 * @return bool|string false if not available
337
	 */
338
	public static function GetGETItemId() {
339
		if (isset(self::$itemId)) {
340
			return self::$itemId;
341
		}
342
343
		return false;
344
	}
345
346
	/**
347
	 * Returns the value of the CollectionId parameter of the querystring.
348
	 *
349
	 * @return bool|string false if not available
350
	 */
351
	public static function GetGETCollectionId() {
352
		if (isset(self::$collectionId)) {
353
			return self::$collectionId;
354
		}
355
356
		return false;
357
	}
358
359
	/**
360
	 * Returns if the SaveInSent parameter of the querystring is set.
361
	 *
362
	 * @return bool
363
	 */
364
	public static function GetGETSaveInSent() {
365
		if (isset(self::$saveInSent)) {
366
			return self::$saveInSent;
367
		}
368
369
		return true;
370
	}
371
372
	/**
373
	 * Returns if the AcceptMultipart parameter of the querystring is set.
374
	 *
375
	 * @return bool
376
	 */
377
	public static function GetGETAcceptMultipart() {
378
		if (isset(self::$acceptMultipart)) {
379
			return self::$acceptMultipart;
380
		}
381
382
		return false;
383
	}
384
385
	/**
386
	 * Returns the value of the AttachmentName parameter of the querystring.
387
	 *
388
	 * @return bool|string false if not available
389
	 */
390
	public static function GetGETAttachmentName() {
391
		if (isset(self::$attachmentName)) {
392
			return self::$attachmentName;
393
		}
394
395
		return false;
396
	}
397
398
	/**
399
	 * Returns user that is synchronizing data.
400
	 * If impersonation is active it returns the impersonated user,
401
	 * else the auth user.
402
	 *
403
	 * @return bool|string false if not available
404
	 */
405
	public static function GetUser() {
406
		if (self::GetImpersonatedUser()) {
407
			return self::GetImpersonatedUser();
408
		}
409
410
		return self::GetAuthUser();
411
	}
412
413
	/**
414
	 * Returns the AuthUser string send by the client.
415
	 *
416
	 * @return bool|string false if not available
417
	 */
418
	public static function GetAuthUserString() {
419
		if (isset(self::$authUserString)) {
420
			return self::$authUserString;
421
		}
422
423
		return false;
424
	}
425
426
	/**
427
	 * Returns the impersonated user. If not available, returns false.
428
	 *
429
	 * @return bool|string false if not available
430
	 */
431
	public static function GetImpersonatedUser() {
432
		if (isset(self::$impersonatedUser)) {
433
			return self::$impersonatedUser;
434
		}
435
436
		return false;
437
	}
438
439
	/**
440
	 * Returns the authenticated user.
441
	 *
442
	 * @return bool|string false if not available
443
	 */
444
	public static function GetAuthUser() {
445
		if (isset(self::$authUser)) {
446
			return self::$authUser;
447
		}
448
449
		return false;
450
	}
451
452
	/**
453
	 * Returns the authenticated domain for the user.
454
	 *
455
	 * @return bool|string false if not available
456
	 */
457
	public static function GetAuthDomain() {
458
		if (isset(self::$authDomain)) {
459
			return self::$authDomain;
460
		}
461
462
		return false;
463
	}
464
465
	/**
466
	 * Returns the transmitted password.
467
	 *
468
	 * @return bool|string false if not available
469
	 */
470
	public static function GetAuthPassword() {
471
		if (isset(self::$authPassword)) {
472
			return self::$authPassword;
473
		}
474
475
		return false;
476
	}
477
478
	/**
479
	 * Returns the RemoteAddress.
480
	 *
481
	 * @return string
482
	 */
483
	public static function GetRemoteAddr() {
484
		if (isset(self::$remoteAddr)) {
485
			return self::$remoteAddr;
486
		}
487
488
		return "UNKNOWN";
489
	}
490
491
	/**
492
	 * Returns the command to be executed.
493
	 *
494
	 * @return bool|string false if not available
495
	 */
496
	public static function GetCommand() {
497
		if (isset(self::$command)) {
498
			return self::$command;
499
		}
500
501
		return false;
502
	}
503
504
	/**
505
	 * Returns the command code which is being executed.
506
	 *
507
	 * @return bool|string false if not available
508
	 */
509
	public static function GetCommandCode() {
510
		if (isset(self::$command)) {
511
			return Utils::GetCodeFromCommand(self::$command);
512
		}
513
514
		return false;
515
	}
516
517
	/**
518
	 * Returns the device id transmitted.
519
	 *
520
	 * @return bool|string false if not available
521
	 */
522
	public static function GetDeviceID() {
523
		if (isset(self::$devid)) {
524
			return self::$devid;
525
		}
526
527
		return false;
528
	}
529
530
	/**
531
	 * Returns the device type if transmitted.
532
	 *
533
	 * @return bool|string false if not available
534
	 */
535
	public static function GetDeviceType() {
536
		if (isset(self::$devtype)) {
537
			return self::$devtype;
538
		}
539
540
		return false;
541
	}
542
543
	/**
544
	 * Returns the value of supported AS protocol from the headers.
545
	 *
546
	 * @return bool|string false if not available
547
	 */
548
	public static function GetProtocolVersion() {
549
		if (isset(self::$asProtocolVersion)) {
550
			return self::$asProtocolVersion;
551
		}
552
553
		return false;
554
	}
555
556
	/**
557
	 * Returns the user agent sent in the headers.
558
	 *
559
	 * @return bool|string false if not available
560
	 */
561
	public static function GetUserAgent() {
562
		if (isset(self::$useragent)) {
563
			return self::$useragent;
564
		}
565
566
		return self::UNKNOWN;
567
	}
568
569
	/**
570
	 * Returns policy key sent by the device.
571
	 *
572
	 * @return bool|int false if not available
573
	 */
574
	public static function GetPolicyKey() {
575
		if (isset(self::$policykey)) {
576
			return self::$policykey;
577
		}
578
579
		return false;
580
	}
581
582
	/**
583
	 * Indicates if a policy key was sent by the device.
584
	 *
585
	 * @return bool
586
	 */
587
	public static function WasPolicyKeySent() {
588
		return isset(self::$headers["x-ms-policykey"]);
589
	}
590
591
	/**
592
	 * Indicates if grommunio-sync was called with a POST request.
593
	 *
594
	 * @return bool
595
	 */
596
	public static function IsMethodPOST() {
597
		return self::$method == "POST";
598
	}
599
600
	/**
601
	 * Indicates if grommunio-sync was called with a GET request.
602
	 *
603
	 * @return bool
604
	 */
605
	public static function IsMethodGET() {
606
		return self::$method == "GET";
607
	}
608
609
	/**
610
	 * Indicates if grommunio-sync was called with a OPTIONS request.
611
	 *
612
	 * @return bool
613
	 */
614
	public static function IsMethodOPTIONS() {
615
		return self::$method == "OPTIONS";
616
	}
617
618
	/**
619
	 * Sometimes strange device ids are submitted
620
	 * No device information should be saved when this happens.
621
	 *
622
	 * @return bool false if invalid
623
	 */
624
	public static function IsValidDeviceID() {
625
		if (self::GetDeviceID() === "validate") {
626
			return false;
627
		}
628
629
		return true;
630
	}
631
632
	/**
633
	 * Returns the amount of data sent in this request (from the headers).
634
	 *
635
	 * @return int
636
	 */
637
	public static function GetContentLength() {
638
		return (isset(self::$headers["content-length"])) ? (int) self::$headers["content-length"] : 0;
639
	}
640
641
	/**
642
	 * Returns the amount of seconds this request is able to be kept open without the client
643
	 * closing it. This depends on the vendor.
644
	 *
645
	 * @return bool
646
	 */
647
	public static function GetExpectedConnectionTimeout() {
648
		// Different vendors implement different connection timeouts.
649
		// In order to optimize processing, we return a specific time for the major
650
		// classes currently known (feedback welcome).
651
		// The amount of time returned is somehow lower than the max timeout so we have
652
		// time for processing.
653
654
		if (!isset(self::$expectedConnectionTimeout)) {
655
			// Apple and Windows Phone have higher timeouts (4min = 240sec)
656
			if (stripos(SYNC_TIMEOUT_LONG_DEVICETYPES, self::GetDeviceType()) !== false) {
0 ignored issues
show
Bug introduced by
It seems like self::GetDeviceType() can also be of type boolean; however, parameter $needle of stripos() 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

656
			if (stripos(SYNC_TIMEOUT_LONG_DEVICETYPES, /** @scrutinizer ignore-type */ self::GetDeviceType()) !== false) {
Loading history...
657
				self::$expectedConnectionTimeout = 210;
658
			}
659
			// Samsung devices have a intermediate timeout (90sec)
660
			elseif (stripos(SYNC_TIMEOUT_MEDIUM_DEVICETYPES, self::GetDeviceType()) !== false) {
661
				self::$expectedConnectionTimeout = 85;
662
			}
663
			else {
664
				// for all other devices, a timeout of 30 seconds is expected
665
				self::$expectedConnectionTimeout = 28;
666
			}
667
		}
668
669
		return self::$expectedConnectionTimeout;
670
	}
671
672
	/**
673
	 * Indicates if the maximum timeout for the devicetype of this request is
674
	 * almost reached.
675
	 *
676
	 * @return bool
677
	 */
678
	public static function IsRequestTimeoutReached() {
679
		return (time() - $_SERVER["REQUEST_TIME"]) >= self::GetExpectedConnectionTimeout();
680
	}
681
682
	/**
683
	 * Indicates if the memory usage limit is almost reached.
684
	 * Processing should stop then to prevent hard out-of-memory issues.
685
	 * The threshold is hardcoded at 90% in Request::MAXMEMORYUSAGE.
686
	 *
687
	 * @return bool
688
	 */
689
	public static function IsRequestMemoryLimitReached() {
690
		if (self::$memoryLimit === false) {
691
			return false;
692
		}
693
694
		return memory_get_peak_usage(true) >= self::$memoryLimit;
695
	}
696
697
	/**
698
	 * Returns a user identifier coming from the logged user store, mostly an email address.
699
	 *
700
	 * @return string
701
	 */
702
	public static function GetUserIdentifier() {
703
		return self::$userIdentifier;
704
	}
705
706
	/**
707
	 * Sets a user identifier coming from the logged user store.
708
	 *
709
	 * @param string $userIdentifier
710
	 */
711
	public static function SetUserIdentifier($userIdentifier) {
712
		self::$userIdentifier = $userIdentifier;
713
	}
714
715
	/*----------------------------------------------------------------------------------------------------------
716
	 * Private stuff
717
	 */
718
719
	/**
720
	 * Replaces all not allowed characters in a string.
721
	 *
722
	 * @param string $input        the input string
723
	 * @param int    $filter       one of the predefined filters: LETTERS_ONLY, HEX_ONLY, WORDCHAR_ONLY, NUMBERS_ONLY, NUMBERSDOT_ONLY
724
	 * @param char   $replacevalue (opt) a character the filtered characters should be replaced with
0 ignored issues
show
Bug introduced by
The type char 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...
725
	 *
726
	 * @return string
727
	 */
728
	private static function filterEvilInput($input, $filter, $replacevalue = '') {
729
		$re = false;
730
		if ($filter == self::LETTERS_ONLY) {
731
			$re = "/[^A-Za-z]/";
732
		}
733
		elseif ($filter == self::HEX_ONLY) {
734
			$re = "/[^A-Fa-f0-9]/";
735
		}
736
		elseif ($filter == self::WORDCHAR_ONLY) {
737
			$re = "/[^A-Za-z0-9]/";
738
		}
739
		elseif ($filter == self::NUMBERS_ONLY) {
740
			$re = "/[^0-9]/";
741
		}
742
		elseif ($filter == self::NUMBERSDOT_ONLY) {
743
			$re = "/[^0-9\\.]/";
744
		}
745
		elseif ($filter == self::HEX_EXTENDED) {
746
			$re = "/[^A-Fa-f0-9\\:\\.]/";
747
		}
748
		elseif ($filter == self::HEX_EXTENDED2) {
749
			$re = "/[^A-Fa-f0-9\\:USGI]/";
750
		} // Folder origin constants from DeviceManager::FLD_ORIGIN_* (C already hex)
751
		elseif ($filter == self::ISO8601) {
752
			$re = "/[^\\d{8}T\\d{6}Z]/";
753
		}
754
755
		return ($re) ? preg_replace($re, $replacevalue, $input) : '';
756
	}
757
758
	/**
759
	 * If $input is a valid IPv4 or IPv6 address, returns a valid compact IPv4 or IPv6 address string.
760
	 * Otherwise, it will strip all characters that are neither numerical or '.' and prefix with "bad-ip".
761
	 *
762
	 * @param string $input The ipv4/ipv6 address
763
	 *
764
	 * @return string
765
	 */
766
	private static function filterIP($input) {
767
		$in_addr = @inet_pton($input);
768
		if ($in_addr === false) {
769
			return 'badip-' . self::filterEvilInput($input, self::HEX_EXTENDED);
770
		}
771
772
		return inet_ntop($in_addr);
773
	}
774
775
	/**
776
	 * Returns base64 encoded "php://input"
777
	 * With POST request (our case), you can open and read
778
	 * multiple times "php://input".
779
	 *
780
	 * @param int $maxLength max. length to be returned. Default: return all
781
	 *
782
	 * @return string - base64 encoded wbxml
783
	 */
784
	public static function GetInputAsBase64($maxLength = -1) {
785
		$input = fopen('php://input', 'r');
786
		$wbxml = base64_encode(stream_get_contents($input, $maxLength));
787
		fclose($input);
788
789
		return $wbxml;
790
	}
791
792
	/**
793
	 * Decodes base64 encoded query parameters. Based on dw2412 contribution.
794
	 */
795
	private static function decodeBase64URI() {
796
		/*
797
		 * The query string has a following structure. Number in () is position:
798
		 * 1 byte       - protocol version (0)
799
		 * 1 byte       - command code (1)
800
		 * 2 bytes      - locale (2)
801
		 * 1 byte       - device ID length (4)
802
		 * variable     - device ID (4+device ID length)
803
		 * 1 byte       - policy key length (5+device ID length)
804
		 * 0 or 4 bytes - policy key (5+device ID length + policy key length)
805
		 * 1 byte       - device type length (6+device ID length + policy key length)
806
		 * variable     - device type (6+device ID length + policy key length + device type length)
807
		 * variable     - command parameters, array which consists of:
808
		 *                      1 byte      - tag
809
		 *                      1 byte      - length
810
		 *                      variable    - value of the parameter
811
		 *
812
		 */
813
		$decoded = base64_decode($_SERVER['QUERY_STRING']);
814
		$devIdLength = ord($decoded[4]); // device ID length
815
		$polKeyLength = ord($decoded[5 + $devIdLength]); // policy key length
816
		$devTypeLength = ord($decoded[6 + $devIdLength + $polKeyLength]); // device type length
817
		// unpack the decoded query string values
818
		self::$base64QueryDecoded = unpack("CProtVer/CCommand/vLocale/CDevIDLen/H" . ($devIdLength * 2) . "DevID/CPolKeyLen" . ($polKeyLength == 4 ? "/VPolKey" : "") . "/CDevTypeLen/A" . $devTypeLength . "DevType", $decoded);
819
820
		// get the command parameters
821
		$pos = 7 + $devIdLength + $polKeyLength + $devTypeLength;
822
		$decoded = substr($decoded, $pos);
823
		while (strlen($decoded) > 0) {
824
			$paramLength = ord($decoded[1]);
825
			$unpackedParam = unpack("CParamTag/CParamLength/A" . $paramLength . "ParamValue", $decoded);
826
			self::$base64QueryDecoded[ord($decoded[0])] = $unpackedParam['ParamValue'];
827
			// remove parameter from decoded query string
828
			$decoded = substr($decoded, 2 + $paramLength);
829
		}
830
	}
831
}
832