Issues (1500)

lib/request/ping.php (10 issues)

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

117
					elseif ($class == $spa->/** @scrutinizer ignore-call */ GetContentClass()) {
Loading history...
118
						$pingable[] = $folderid;
119
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): using saved sync state for '%s' id '%s'", $spa->GetContentClass(), $folderid));
120
					}
121
				}
122
				if (!self::$decoder->getElementEndTag()) {
123
					return false;
124
				}
125
126
				// update pingable flags
127
				foreach ($sc as $folderid => $spa) {
128
					// if the folderid is in $pingable, we should ping it, else remove the flag
129
					if (in_array($folderid, $pingable)) {
130
						$spa->SetPingableFlag(true);
131
					}
132
					else {
133
						$spa->DelPingableFlag();
134
					}
135
				}
136
			}
137
			if (!self::$decoder->getElementEndTag()) {
138
				return false;
139
			}
140
141
			if (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
142
				$pingstatus = SYNC_PINGSTATUS_HBOUTOFRANGE;
143
				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

143
				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...
144
			}
145
			// save changed data
146
			foreach ($sc as $folderid => $spa) {
147
				$sc->SaveCollection($spa);
148
			}
149
		} // END SYNC_PING_PING
150
		else {
151
			// if no ping initialization data was sent, we check if we have pingable folders
152
			// if not, we indicate that there is nothing to do.
153
			if (!$sc->PingableFolders()) {
154
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
155
				SLog::Write(LOGLEVEL_DEBUG, "HandlePing(): no pingable folders found and no initialization data sent. Returning SYNC_PINGSTATUS_FAILINGPARAMS.");
156
			}
157
			elseif (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
158
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
159
				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()));
160
			}
161
		}
162
163
		// Check for changes on the default LifeTime, set interval and ONLY on pingable collections
164
		try {
165
			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...
166
				self::$deviceManager->DoAutomaticASDeviceSaving(false);
167
				$foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval, true);
168
			}
169
		}
170
		catch (StatusException $ste) {
171
			switch ($ste->getCode()) {
172
				case SyncCollections::ERROR_NO_COLLECTIONS:
173
					$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
174
					break;
175
176
				case SyncCollections::ERROR_WRONG_HIERARCHY:
177
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
178
					self::$deviceManager->AnnounceProcessStatus(false, $pingstatus);
179
					break;
180
181
				case SyncCollections::OBSOLETE_CONNECTION:
182
					$foundchanges = false;
183
					break;
184
185
				case SyncCollections::HIERARCHY_CHANGED:
186
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
187
					break;
188
			}
189
		}
190
191
		self::$encoder->StartWBXML();
192
		self::$encoder->startTag(SYNC_PING_PING);
193
194
		self::$encoder->startTag(SYNC_PING_STATUS);
195
		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...
196
			self::$encoder->content($pingstatus);
197
		}
198
		else {
199
			self::$encoder->content($foundchanges ? SYNC_PINGSTATUS_CHANGES : SYNC_PINGSTATUS_HBEXPIRED);
200
		}
201
		self::$encoder->endTag();
202
203
		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...
204
			self::$encoder->startTag(SYNC_PING_FOLDERS);
205
206
			if (empty($fakechanges)) {
207
				$changes = $sc->GetChangedFolderIds();
208
			}
209
			else {
210
				$changes = $fakechanges;
211
			}
212
213
			$announceAggregated = false;
214
			if (count($changes) > 1) {
215
				$announceAggregated = 0;
216
			}
217
			foreach ($changes as $folderid => $changecount) {
218
				if ($changecount > 0) {
219
					self::$encoder->startTag(SYNC_PING_FOLDER);
220
					self::$encoder->content($folderid);
221
					self::$encoder->endTag();
222
					if ($announceAggregated === false) {
223
						if (empty($fakechanges)) {
224
							self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true);
225
						}
226
					}
227
					else {
228
						$announceAggregated += $changecount;
229
					}
230
					self::$deviceManager->AnnounceProcessStatus($folderid, SYNC_PINGSTATUS_CHANGES);
231
				}
232
			}
233
			if ($announceAggregated !== false) {
234
				self::$topCollector->AnnounceInformation(sprintf("Found %d changes in %d folders", $announceAggregated, count($changes)), true);
235
			}
236
			self::$encoder->endTag();
237
		}
238
		elseif ($pingstatus == SYNC_PINGSTATUS_HBOUTOFRANGE) {
239
			self::$encoder->startTag(SYNC_PING_LIFETIME);
240
			if ($sc->GetLifetime() > PING_HIGHER_BOUND_LIFETIME) {
241
				self::$encoder->content(PING_HIGHER_BOUND_LIFETIME);
242
			}
243
			else {
244
				self::$encoder->content(PING_LOWER_BOUND_LIFETIME);
245
			}
246
			self::$encoder->endTag();
247
		}
248
249
		self::$encoder->endTag();
250
251
		// update the waittime waited
252
		self::$waitTime = $sc->GetWaitedSeconds();
253
254
		return true;
255
	}
256
257
	/**
258
	 * 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.
259
	 *
260
	 * @param int $lifetime
261
	 *
262
	 * @return bool
263
	 */
264
	private function lifetimeBetweenBound($lifetime) {
265
		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...
266
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME && $lifetime >= PING_LOWER_BOUND_LIFETIME;
267
		}
268
		if (PING_HIGHER_BOUND_LIFETIME !== false) {
0 ignored issues
show
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
269
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME;
270
		}
271
		if (PING_LOWER_BOUND_LIFETIME !== false) {
0 ignored issues
show
The condition PING_LOWER_BOUND_LIFETIME !== false is always false.
Loading history...
272
			return $lifetime >= PING_LOWER_BOUND_LIFETIME;
273
		}
274
275
		return true;
276
	}
277
}
278