Issues (1513)

lib/request/ping.php (10 issues)

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
6
 *
7
 * Provides the PING command
8
 */
9
10
class Ping extends RequestProcessor {
11
	/**
12
	 * Handles the Ping command.
13
	 *
14
	 * @param int $commandCode
15
	 *
16
	 * @return bool
17
	 */
18
	public function Handle($commandCode) {
19
		$interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30;
20
		$pingstatus = false;
21
		$fakechanges = [];
22
		$foundchanges = false;
23
24
		// Contains all requested folders (containers)
25
		$sc = new SyncCollections();
26
27
		// read from stream to see if the symc params are being sent
28
		$params_present = self::$decoder->getElementStartTag(SYNC_PING_PING);
29
30
		// Load all collections - do load states, check permissions and allow unconfirmed states
31
		try {
32
			$sc->LoadAllCollections(true, true, true, true, false);
33
		}
34
		catch (StateInvalidException $siex) {
35
			// if no params are present, indicate to send params, else do hierarchy sync
36
			if (!$params_present) {
37
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
38
				self::$topCollector->AnnounceInformation("StateInvalidException: require PingParameters", true);
39
			}
40
			elseif (self::$deviceManager->IsHierarchySyncRequired()) {
41
				// we could be in a looping  - see LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised()
42
				$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
43
				self::$topCollector->AnnounceInformation("Potential loop detection: require HierarchySync", true);
44
			}
45
			else {
46
				// we do not have a ping status for this, but SyncCollections should have generated fake changes for the folders which are broken
47
				$fakechanges = $sc->GetChangedFolderIds();
48
				$foundchanges = true;
49
50
				self::$topCollector->AnnounceInformation("StateInvalidException: force sync", true);
51
			}
52
		}
53
		catch (StatusException $stex) {
54
			$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
55
			self::$topCollector->AnnounceInformation("StatusException: require HierarchySync", true);
56
		}
57
58
		SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): reference PolicyKey for PING: %s", $sc->GetReferencePolicyKey()));
59
60
		// receive PING initialization data
61
		if ($params_present) {
62
			self::$topCollector->AnnounceInformation("Processing PING data");
63
			SLog::Write(LOGLEVEL_DEBUG, "HandlePing(): initialization data received");
64
65
			if (self::$decoder->getElementStartTag(SYNC_PING_LIFETIME)) {
66
				$sc->SetLifetime(self::$decoder->getElementContent());
67
				self::$decoder->getElementEndTag();
68
			}
69
70
			if (($el = self::$decoder->getElementStartTag(SYNC_PING_FOLDERS)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) {
71
				// cache requested (pingable) folderids
72
				$pingable = [];
73
74
				while (self::$decoder->getElementStartTag(SYNC_PING_FOLDER)) {
75
					WBXMLDecoder::ResetInWhile("pingFolder");
76
					while (WBXMLDecoder::InWhile("pingFolder")) {
77
						if (self::$decoder->getElementStartTag(SYNC_PING_SERVERENTRYID)) {
78
							$folderid = self::$decoder->getElementContent();
79
							self::$decoder->getElementEndTag();
80
						}
81
						if (self::$decoder->getElementStartTag(SYNC_PING_FOLDERTYPE)) {
82
							$class = self::$decoder->getElementContent();
83
							self::$decoder->getElementEndTag();
84
						}
85
86
						$e = self::$decoder->peek();
87
						if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
88
							self::$decoder->getElementEndTag();
89
90
							break;
91
						}
92
					}
93
94
					$spa = $sc->GetCollection($folderid);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $folderid does not seem to be defined for all execution paths leading up to this point.
Loading history...
95
					if (!$spa) {
96
						// The requested collection is not synchronized.
97
						// check if the HierarchyCache is available, if not, trigger a HierarchySync
98
						try {
99
							self::$deviceManager->GetFolderClassFromCacheByID($folderid);
100
							// ignore all folders with SYNC_FOLDER_TYPE_UNKNOWN
101
							if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_UNKNOWN) {
102
								SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ignoring folder id '%s' as it's of type UNKNOWN ", $folderid));
103
104
								continue;
105
							}
106
						}
107
						catch (NoHierarchyCacheAvailableException $nhca) {
108
							SLog::Write(LOGLEVEL_INFO, sprintf("HandlePing(): unknown collection '%s', triggering HierarchySync", $folderid));
109
							$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
110
						}
111
112
						// Trigger a Sync request because then the device will be forced to resync this folder.
113
						$fakechanges[$folderid] = 1;
114
						$foundchanges = true;
115
					}
116
					elseif ($class == $spa->GetContentClass()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $class does not seem to be defined for all execution paths leading up to this point.
Loading history...
The method GetContentClass() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
					elseif ($class == $spa->/** @scrutinizer ignore-call */ GetContentClass()) {
Loading history...
117
						$pingable[] = $folderid;
118
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): using saved sync state for '%s' id '%s'", $spa->GetContentClass(), $folderid));
119
					}
120
				}
121
				if (!self::$decoder->getElementEndTag()) {
122
					return false;
123
				}
124
125
				// update pingable flags
126
				foreach ($sc as $folderid => $spa) {
127
					// if the folderid is in $pingable, we should ping it, else remove the flag
128
					if (in_array($folderid, $pingable)) {
129
						$spa->SetPingableFlag(true);
130
					}
131
					else {
132
						$spa->DelPingableFlag();
133
					}
134
				}
135
			}
136
			if (!self::$decoder->getElementEndTag()) {
137
				return false;
138
			}
139
140
			if (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
141
				$pingstatus = SYNC_PINGSTATUS_HBOUTOFRANGE;
142
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ping lifetime not between bound (higher bound:'%d' lower bound:'%d' current lifetime:'%d'. Returning SYNC_PINGSTATUS_HBOUTOFRANGE.", PING_HIGHER_BOUND_LIFETIME, PING_LOWER_BOUND_LIFETIME, $sc->GetLifetime()));
0 ignored issues
show
PING_HIGHER_BOUND_LIFETIME of type false is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ping lifetime not between bound (higher bound:'%d' lower bound:'%d' current lifetime:'%d'. Returning SYNC_PINGSTATUS_HBOUTOFRANGE.", /** @scrutinizer ignore-type */ PING_HIGHER_BOUND_LIFETIME, PING_LOWER_BOUND_LIFETIME, $sc->GetLifetime()));
Loading history...
143
			}
144
			// save changed data
145
			foreach ($sc as $folderid => $spa) {
146
				$sc->SaveCollection($spa);
147
			}
148
		} // END SYNC_PING_PING
149
		else {
150
			// if no ping initialization data was sent, we check if we have pingable folders
151
			// if not, we indicate that there is nothing to do.
152
			if (!$sc->PingableFolders()) {
153
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
154
				SLog::Write(LOGLEVEL_DEBUG, "HandlePing(): no pingable folders found and no initialization data sent. Returning SYNC_PINGSTATUS_FAILINGPARAMS.");
155
			}
156
			elseif (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
157
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
158
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ping lifetime not between bound (higher bound:'%d' lower bound:'%d' current lifetime:'%d'. Returning SYNC_PINGSTATUS_FAILINGPARAMS.", PING_HIGHER_BOUND_LIFETIME, PING_LOWER_BOUND_LIFETIME, $sc->GetLifetime()));
159
			}
160
		}
161
162
		// Check for changes on the default LifeTime, set interval and ONLY on pingable collections
163
		try {
164
			if (!$pingstatus && empty($fakechanges)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingstatus of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
165
				self::$deviceManager->DoAutomaticASDeviceSaving(false);
166
				$foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval, true);
167
			}
168
		}
169
		catch (StatusException $ste) {
170
			switch ($ste->getCode()) {
171
				case SyncCollections::ERROR_NO_COLLECTIONS:
172
					$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
173
					break;
174
175
				case SyncCollections::ERROR_WRONG_HIERARCHY:
176
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
177
					self::$deviceManager->AnnounceProcessStatus(false, $pingstatus);
178
					break;
179
180
				case SyncCollections::OBSOLETE_CONNECTION:
181
					$foundchanges = false;
182
					break;
183
184
				case SyncCollections::HIERARCHY_CHANGED:
185
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
186
					break;
187
			}
188
		}
189
190
		self::$encoder->StartWBXML();
191
		self::$encoder->startTag(SYNC_PING_PING);
192
193
		self::$encoder->startTag(SYNC_PING_STATUS);
194
		if ($pingstatus) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingstatus of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
195
			self::$encoder->content($pingstatus);
196
		}
197
		else {
198
			self::$encoder->content($foundchanges ? SYNC_PINGSTATUS_CHANGES : SYNC_PINGSTATUS_HBEXPIRED);
199
		}
200
		self::$encoder->endTag();
201
202
		if (!$pingstatus) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingstatus of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
203
			self::$encoder->startTag(SYNC_PING_FOLDERS);
204
205
			if (empty($fakechanges)) {
206
				$changes = $sc->GetChangedFolderIds();
207
			}
208
			else {
209
				$changes = $fakechanges;
210
			}
211
212
			$announceAggregated = false;
213
			if (count($changes) > 1) {
214
				$announceAggregated = 0;
215
			}
216
			foreach ($changes as $folderid => $changecount) {
217
				if ($changecount > 0) {
218
					self::$encoder->startTag(SYNC_PING_FOLDER);
219
					self::$encoder->content($folderid);
220
					self::$encoder->endTag();
221
					if ($announceAggregated === false) {
222
						if (empty($fakechanges)) {
223
							self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true);
224
						}
225
					}
226
					else {
227
						$announceAggregated += $changecount;
228
					}
229
					self::$deviceManager->AnnounceProcessStatus($folderid, SYNC_PINGSTATUS_CHANGES);
230
				}
231
			}
232
			if ($announceAggregated !== false) {
233
				self::$topCollector->AnnounceInformation(sprintf("Found %d changes in %d folders", $announceAggregated, count($changes)), true);
234
			}
235
			self::$encoder->endTag();
236
		}
237
		elseif ($pingstatus == SYNC_PINGSTATUS_HBOUTOFRANGE) {
238
			self::$encoder->startTag(SYNC_PING_LIFETIME);
239
			if ($sc->GetLifetime() > PING_HIGHER_BOUND_LIFETIME) {
240
				self::$encoder->content(PING_HIGHER_BOUND_LIFETIME);
241
			}
242
			else {
243
				self::$encoder->content(PING_LOWER_BOUND_LIFETIME);
244
			}
245
			self::$encoder->endTag();
246
		}
247
248
		self::$encoder->endTag();
249
250
		// update the waittime waited
251
		self::$waitTime = $sc->GetWaitedSeconds();
252
253
		return true;
254
	}
255
256
	/**
257
	 * Return true if the ping lifetime is between the specified bound (PING_HIGHER_BOUND_LIFETIME and PING_LOWER_BOUND_LIFETIME). If no bound are specified, it returns true.
258
	 *
259
	 * @param int $lifetime
260
	 *
261
	 * @return bool
262
	 */
263
	private function lifetimeBetweenBound($lifetime) {
264
		if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false) {
0 ignored issues
show
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
265
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME && $lifetime >= PING_LOWER_BOUND_LIFETIME;
266
		}
267
		if (PING_HIGHER_BOUND_LIFETIME !== false) {
0 ignored issues
show
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
268
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME;
269
		}
270
		if (PING_LOWER_BOUND_LIFETIME !== false) {
0 ignored issues
show
The condition PING_LOWER_BOUND_LIFETIME !== false is always false.
Loading history...
271
			return $lifetime >= PING_LOWER_BOUND_LIFETIME;
272
		}
273
274
		return true;
275
	}
276
}
277