Request::GetExpectedConnectionTimeout()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 0
dl 0
loc 23
rs 10
c 0
b 0
f 0
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((string) $_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
			[self::$getUser] = Utils::SplitDomainUser(strtolower((string) $_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
			[self::$authUserString, self::$authDomain] = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']);
183
			self::$authPassword = $_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] * 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 = 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 (str_starts_with($apacheHeader, 'http_')) {
251
				$apacheHeader = substr($apacheHeader, 5);
252
			}
253
			$apacheHeader = str_replace("_", "-", $apacheHeader);
254
			if (isset(self::$headers[$header]) || isset(self::$headers[$apacheHeader])) {
255
				$remoteIP = 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 (str_contains((string) $remoteIP, ',')) {
259
					$remoteIP = trim(explode(',', (string) $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
		return self::$input ?? false;
288
	}
289
290
	/**
291
	 * Returns the output stream.
292
	 *
293
	 * @return bool|handle false if not available
294
	 */
295
	public static function GetOutputStream() {
296
		return self::$output ?? false;
297
	}
298
299
	/**
300
	 * Returns the request method.
301
	 *
302
	 * @return string
303
	 */
304
	public static function GetMethod() {
305
		return self::$method ?? self::UNKNOWN;
306
	}
307
308
	/**
309
	 * Returns the value of the user parameter of the querystring.
310
	 *
311
	 * @return bool|string false if not available
312
	 */
313
	public static function GetGETUser() {
314
		return self::$getUser ?? self::UNKNOWN;
315
	}
316
317
	/**
318
	 * Returns the value of the ItemId parameter of the querystring.
319
	 *
320
	 * @return bool|string false if not available
321
	 */
322
	public static function GetGETItemId() {
323
		return self::$itemId ?? false;
324
	}
325
326
	/**
327
	 * Returns the value of the CollectionId parameter of the querystring.
328
	 *
329
	 * @return bool|string false if not available
330
	 */
331
	public static function GetGETCollectionId() {
332
		return self::$collectionId ?? false;
333
	}
334
335
	/**
336
	 * Returns if the SaveInSent parameter of the querystring is set.
337
	 *
338
	 * @return bool
339
	 */
340
	public static function GetGETSaveInSent() {
341
		return self::$saveInSent ?? true;
342
	}
343
344
	/**
345
	 * Returns if the AcceptMultipart parameter of the querystring is set.
346
	 *
347
	 * @return bool
348
	 */
349
	public static function GetGETAcceptMultipart() {
350
		return self::$acceptMultipart ?? false;
351
	}
352
353
	/**
354
	 * Returns the value of the AttachmentName parameter of the querystring.
355
	 *
356
	 * @return bool|string false if not available
357
	 */
358
	public static function GetGETAttachmentName() {
359
		return self::$attachmentName ?? false;
360
	}
361
362
	/**
363
	 * Returns user that is synchronizing data.
364
	 * If impersonation is active it returns the impersonated user,
365
	 * else the auth user.
366
	 *
367
	 * @return bool|string false if not available
368
	 */
369
	public static function GetUser() {
370
		if (self::GetImpersonatedUser()) {
371
			return self::GetImpersonatedUser();
372
		}
373
374
		return self::GetAuthUser();
375
	}
376
377
	/**
378
	 * Returns the AuthUser string send by the client.
379
	 *
380
	 * @return bool|string false if not available
381
	 */
382
	public static function GetAuthUserString() {
383
		return self::$authUserString ?? false;
384
	}
385
386
	/**
387
	 * Returns the impersonated user. If not available, returns false.
388
	 *
389
	 * @return bool|string false if not available
390
	 */
391
	public static function GetImpersonatedUser() {
392
		return self::$impersonatedUser ?? false;
393
	}
394
395
	/**
396
	 * Returns the authenticated user.
397
	 *
398
	 * @return bool|string false if not available
399
	 */
400
	public static function GetAuthUser() {
401
		return self::$authUser ?? false;
402
	}
403
404
	/**
405
	 * Returns the authenticated domain for the user.
406
	 *
407
	 * @return bool|string false if not available
408
	 */
409
	public static function GetAuthDomain() {
410
		return self::$authDomain ?? false;
411
	}
412
413
	/**
414
	 * Returns the transmitted password.
415
	 *
416
	 * @return bool|string false if not available
417
	 */
418
	public static function GetAuthPassword() {
419
		return self::$authPassword ?? false;
420
	}
421
422
	/**
423
	 * Returns the RemoteAddress.
424
	 *
425
	 * @return string
426
	 */
427
	public static function GetRemoteAddr() {
428
		return self::$remoteAddr ?? "UNKNOWN";
429
	}
430
431
	/**
432
	 * Returns the command to be executed.
433
	 *
434
	 * @return bool|string false if not available
435
	 */
436
	public static function GetCommand() {
437
		return self::$command ?? false;
438
	}
439
440
	/**
441
	 * Returns the command code which is being executed.
442
	 *
443
	 * @return bool|string false if not available
444
	 */
445
	public static function GetCommandCode() {
446
		if (isset(self::$command)) {
447
			return Utils::GetCodeFromCommand(self::$command);
448
		}
449
450
		return false;
451
	}
452
453
	/**
454
	 * Returns the device id transmitted.
455
	 *
456
	 * @return bool|string false if not available
457
	 */
458
	public static function GetDeviceID() {
459
		return self::$devid ?? false;
460
	}
461
462
	/**
463
	 * Returns the device type if transmitted.
464
	 *
465
	 * @return bool|string false if not available
466
	 */
467
	public static function GetDeviceType() {
468
		return self::$devtype ?? false;
469
	}
470
471
	/**
472
	 * Returns the value of supported AS protocol from the headers.
473
	 *
474
	 * @return bool|string false if not available
475
	 */
476
	public static function GetProtocolVersion() {
477
		return self::$asProtocolVersion ?? false;
478
	}
479
480
	/**
481
	 * Returns the user agent sent in the headers.
482
	 *
483
	 * @return bool|string false if not available
484
	 */
485
	public static function GetUserAgent() {
486
		return self::$useragent ?? self::UNKNOWN;
487
	}
488
489
	/**
490
	 * Returns policy key sent by the device.
491
	 *
492
	 * @return bool|int false if not available
493
	 */
494
	public static function GetPolicyKey() {
495
		return self::$policykey ?? false;
496
	}
497
498
	/**
499
	 * Indicates if a policy key was sent by the device.
500
	 *
501
	 * @return bool
502
	 */
503
	public static function WasPolicyKeySent() {
504
		return isset(self::$headers["x-ms-policykey"]);
505
	}
506
507
	/**
508
	 * Indicates if grommunio-sync was called with a POST request.
509
	 *
510
	 * @return bool
511
	 */
512
	public static function IsMethodPOST() {
513
		return self::$method == "POST";
514
	}
515
516
	/**
517
	 * Indicates if grommunio-sync was called with a GET request.
518
	 *
519
	 * @return bool
520
	 */
521
	public static function IsMethodGET() {
522
		return self::$method == "GET";
523
	}
524
525
	/**
526
	 * Indicates if grommunio-sync was called with a OPTIONS request.
527
	 *
528
	 * @return bool
529
	 */
530
	public static function IsMethodOPTIONS() {
531
		return self::$method == "OPTIONS";
532
	}
533
534
	/**
535
	 * Sometimes strange device ids are submitted
536
	 * No device information should be saved when this happens.
537
	 *
538
	 * @return bool false if invalid
539
	 */
540
	public static function IsValidDeviceID() {
541
		if (self::GetDeviceID() === "validate") {
542
			return false;
543
		}
544
545
		return true;
546
	}
547
548
	/**
549
	 * Returns the amount of data sent in this request (from the headers).
550
	 *
551
	 * @return int
552
	 */
553
	public static function GetContentLength() {
554
		return (isset(self::$headers["content-length"])) ? (int) self::$headers["content-length"] : 0;
555
	}
556
557
	/**
558
	 * Returns the amount of seconds this request is able to be kept open without the client
559
	 * closing it. This depends on the vendor.
560
	 *
561
	 * @return bool
562
	 */
563
	public static function GetExpectedConnectionTimeout() {
564
		// Different vendors implement different connection timeouts.
565
		// In order to optimize processing, we return a specific time for the major
566
		// classes currently known (feedback welcome).
567
		// The amount of time returned is somehow lower than the max timeout so we have
568
		// time for processing.
569
570
		if (!isset(self::$expectedConnectionTimeout)) {
571
			// Apple and Windows Phone have higher timeouts (4min = 240sec)
572
			if (stripos(SYNC_TIMEOUT_LONG_DEVICETYPES, (string) self::GetDeviceType()) !== false) {
573
				self::$expectedConnectionTimeout = 210;
574
			}
575
			// Samsung devices have a intermediate timeout (90sec)
576
			elseif (stripos(SYNC_TIMEOUT_MEDIUM_DEVICETYPES, (string) self::GetDeviceType()) !== false) {
577
				self::$expectedConnectionTimeout = 85;
578
			}
579
			else {
580
				// for all other devices, a timeout of 30 seconds is expected
581
				self::$expectedConnectionTimeout = 28;
582
			}
583
		}
584
585
		return self::$expectedConnectionTimeout;
586
	}
587
588
	/**
589
	 * Indicates if the maximum timeout for the devicetype of this request is
590
	 * almost reached.
591
	 *
592
	 * @return bool
593
	 */
594
	public static function IsRequestTimeoutReached() {
595
		return (time() - $_SERVER["REQUEST_TIME"]) >= self::GetExpectedConnectionTimeout();
596
	}
597
598
	/**
599
	 * Indicates if the memory usage limit is almost reached.
600
	 * Processing should stop then to prevent hard out-of-memory issues.
601
	 * The threshold is hardcoded at 90% in Request::MAXMEMORYUSAGE.
602
	 *
603
	 * @return bool
604
	 */
605
	public static function IsRequestMemoryLimitReached() {
606
		if (self::$memoryLimit === false) {
607
			return false;
608
		}
609
610
		return memory_get_peak_usage(true) >= self::$memoryLimit;
611
	}
612
613
	/**
614
	 * Returns a user identifier coming from the logged user store, mostly an email address.
615
	 *
616
	 * @return string
617
	 */
618
	public static function GetUserIdentifier() {
619
		return self::$userIdentifier;
620
	}
621
622
	/**
623
	 * Sets a user identifier coming from the logged user store.
624
	 *
625
	 * @param string $userIdentifier
626
	 */
627
	public static function SetUserIdentifier($userIdentifier) {
628
		self::$userIdentifier = $userIdentifier;
629
	}
630
631
	/*----------------------------------------------------------------------------------------------------------
632
	 * Private stuff
633
	 */
634
635
	/**
636
	 * Replaces all not allowed characters in a string.
637
	 *
638
	 * @param string $input        the input string
639
	 * @param int    $filter       one of the predefined filters: LETTERS_ONLY, HEX_ONLY, WORDCHAR_ONLY, NUMBERS_ONLY, NUMBERSDOT_ONLY
640
	 * @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...
641
	 *
642
	 * @return string
643
	 */
644
	private static function filterEvilInput($input, $filter, $replacevalue = '') {
645
		$re = false;
646
		if ($filter == self::LETTERS_ONLY) {
647
			$re = "/[^A-Za-z]/";
648
		}
649
		elseif ($filter == self::HEX_ONLY) {
650
			$re = "/[^A-Fa-f0-9]/";
651
		}
652
		elseif ($filter == self::WORDCHAR_ONLY) {
653
			$re = "/[^A-Za-z0-9]/";
654
		}
655
		elseif ($filter == self::NUMBERS_ONLY) {
656
			$re = "/[^0-9]/";
657
		}
658
		elseif ($filter == self::NUMBERSDOT_ONLY) {
659
			$re = "/[^0-9\\.]/";
660
		}
661
		elseif ($filter == self::HEX_EXTENDED) {
662
			$re = "/[^A-Fa-f0-9\\:\\.]/";
663
		}
664
		elseif ($filter == self::HEX_EXTENDED2) {
665
			$re = "/[^A-Fa-f0-9\\:USGI]/";
666
		} // Folder origin constants from DeviceManager::FLD_ORIGIN_* (C already hex)
667
		elseif ($filter == self::ISO8601) {
668
			$re = "/[^\\d{8}T\\d{6}Z]/";
669
		}
670
671
		return ($re) ? preg_replace($re, $replacevalue, $input) : '';
672
	}
673
674
	/**
675
	 * If $input is a valid IPv4 or IPv6 address, returns a valid compact IPv4 or IPv6 address string.
676
	 * Otherwise, it will strip all characters that are neither numerical or '.' and prefix with "bad-ip".
677
	 *
678
	 * @param string $input The ipv4/ipv6 address
679
	 *
680
	 * @return string
681
	 */
682
	private static function filterIP($input) {
683
		$in_addr = @inet_pton($input);
684
		if ($in_addr === false) {
685
			return 'badip-' . self::filterEvilInput($input, self::HEX_EXTENDED);
686
		}
687
688
		return inet_ntop($in_addr);
689
	}
690
691
	/**
692
	 * Returns base64 encoded "php://input"
693
	 * With POST request (our case), you can open and read
694
	 * multiple times "php://input".
695
	 *
696
	 * @param int $maxLength max. length to be returned. Default: return all
697
	 *
698
	 * @return string - base64 encoded wbxml
699
	 */
700
	public static function GetInputAsBase64($maxLength = -1) {
701
		$input = fopen('php://input', 'r');
702
		$wbxml = base64_encode(stream_get_contents($input, $maxLength));
703
		fclose($input);
704
705
		return $wbxml;
706
	}
707
708
	/**
709
	 * Decodes base64 encoded query parameters. Based on dw2412 contribution.
710
	 */
711
	private static function decodeBase64URI() {
712
		/*
713
		 * The query string has a following structure. Number in () is position:
714
		 * 1 byte       - protocol version (0)
715
		 * 1 byte       - command code (1)
716
		 * 2 bytes      - locale (2)
717
		 * 1 byte       - device ID length (4)
718
		 * variable     - device ID (4+device ID length)
719
		 * 1 byte       - policy key length (5+device ID length)
720
		 * 0 or 4 bytes - policy key (5+device ID length + policy key length)
721
		 * 1 byte       - device type length (6+device ID length + policy key length)
722
		 * variable     - device type (6+device ID length + policy key length + device type length)
723
		 * variable     - command parameters, array which consists of:
724
		 *                      1 byte      - tag
725
		 *                      1 byte      - length
726
		 *                      variable    - value of the parameter
727
		 *
728
		 */
729
		$decoded = base64_decode((string) $_SERVER['QUERY_STRING']);
730
		$devIdLength = ord($decoded[4]); // device ID length
731
		$polKeyLength = ord($decoded[5 + $devIdLength]); // policy key length
732
		$devTypeLength = ord($decoded[6 + $devIdLength + $polKeyLength]); // device type length
733
		// unpack the decoded query string values
734
		self::$base64QueryDecoded = unpack("CProtVer/CCommand/vLocale/CDevIDLen/H" . ($devIdLength * 2) . "DevID/CPolKeyLen" . ($polKeyLength == 4 ? "/VPolKey" : "") . "/CDevTypeLen/A" . $devTypeLength . "DevType", $decoded);
735
736
		// get the command parameters
737
		$pos = 7 + $devIdLength + $polKeyLength + $devTypeLength;
738
		$decoded = substr($decoded, $pos);
739
		while (strlen($decoded) > 0) {
740
			$paramLength = ord($decoded[1]);
741
			$unpackedParam = unpack("CParamTag/CParamLength/A" . $paramLength . "ParamValue", $decoded);
742
			self::$base64QueryDecoded[ord($decoded[0])] = $unpackedParam['ParamValue'];
743
			// remove parameter from decoded query string
744
			$decoded = substr($decoded, 2 + $paramLength);
745
		}
746
	}
747
}
748