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
![]() |
|||||
66 | private static $occurrence; // TODO |
||||
0 ignored issues
–
show
|
|||||
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
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
![]() |
|||||
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
|
|||||
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
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
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
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
![]() |
|||||
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
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. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||
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 |