1package eu.siacs.conversations.parser;
2
3import android.util.Log;
4import android.util.Pair;
5
6import java.text.SimpleDateFormat;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collections;
10import java.util.Date;
11import java.util.List;
12import java.util.Locale;
13import java.util.Map;
14import java.util.Set;
15import java.util.UUID;
16
17import eu.siacs.conversations.Config;
18import eu.siacs.conversations.R;
19import eu.siacs.conversations.crypto.axolotl.AxolotlService;
20import eu.siacs.conversations.crypto.axolotl.BrokenSessionException;
21import eu.siacs.conversations.crypto.axolotl.NotEncryptedForThisDeviceException;
22import eu.siacs.conversations.crypto.axolotl.OutdatedSenderException;
23import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
24import eu.siacs.conversations.entities.Account;
25import eu.siacs.conversations.entities.Bookmark;
26import eu.siacs.conversations.entities.Contact;
27import eu.siacs.conversations.entities.Conversation;
28import eu.siacs.conversations.entities.Conversational;
29import eu.siacs.conversations.entities.Message;
30import eu.siacs.conversations.entities.MucOptions;
31import eu.siacs.conversations.entities.ReadByMarker;
32import eu.siacs.conversations.entities.ReceiptRequest;
33import eu.siacs.conversations.entities.RtpSessionStatus;
34import eu.siacs.conversations.http.HttpConnectionManager;
35import eu.siacs.conversations.services.MessageArchiveService;
36import eu.siacs.conversations.services.QuickConversationsService;
37import eu.siacs.conversations.services.XmppConnectionService;
38import eu.siacs.conversations.utils.CryptoHelper;
39import eu.siacs.conversations.xml.Element;
40import eu.siacs.conversations.xml.LocalizedContent;
41import eu.siacs.conversations.xml.Namespace;
42import eu.siacs.conversations.xmpp.InvalidJid;
43import eu.siacs.conversations.xmpp.Jid;
44import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
45import eu.siacs.conversations.xmpp.chatstate.ChatState;
46import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
47import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
48import eu.siacs.conversations.xmpp.pep.Avatar;
49import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
50
51public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
52
53 private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
54
55 private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
56 Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
57
58 public MessageParser(XmppConnectionService service) {
59 super(service);
60 }
61
62 private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
63 final Jid by;
64 final boolean safeToExtract;
65 if (isTypeGroupChat) {
66 by = conversation.getJid().asBareJid();
67 safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS);
68 } else {
69 Account account = conversation.getAccount();
70 by = account.getJid().asBareJid();
71 safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
72 }
73 return safeToExtract ? extractStanzaId(packet, by) : null;
74 }
75
76 private static String extractStanzaId(Account account, Element packet) {
77 final boolean safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
78 return safeToExtract ? extractStanzaId(packet, account.getJid().asBareJid()) : null;
79 }
80
81 private static String extractStanzaId(Element packet, Jid by) {
82 for (Element child : packet.getChildren()) {
83 if (child.getName().equals("stanza-id")
84 && Namespace.STANZA_IDS.equals(child.getNamespace())
85 && by.equals(InvalidJid.getNullForInvalid(child.getAttributeAsJid("by")))) {
86 return child.getAttribute("id");
87 }
88 }
89 return null;
90 }
91
92 private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
93 final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
94 Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
95 return result != null ? result : fallback;
96 }
97
98 private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
99 ChatState state = ChatState.parse(packet);
100 if (state != null && c != null) {
101 final Account account = c.getAccount();
102 final Jid from = packet.getFrom();
103 if (from.asBareJid().equals(account.getJid().asBareJid())) {
104 c.setOutgoingChatState(state);
105 if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) {
106 if (c.getContact().isSelf()) {
107 return false;
108 }
109 mXmppConnectionService.markRead(c);
110 activateGracePeriod(account);
111 }
112 return false;
113 } else {
114 if (isTypeGroupChat) {
115 MucOptions.User user = c.getMucOptions().findUserByFullJid(from);
116 if (user != null) {
117 return user.setChatState(state);
118 } else {
119 return false;
120 }
121 } else {
122 return c.setIncomingChatState(state);
123 }
124 }
125 }
126 return false;
127 }
128
129 private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
130 final AxolotlService service = conversation.getAccount().getAxolotlService();
131 final XmppAxolotlMessage xmppAxolotlMessage;
132 try {
133 xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
134 } catch (Exception e) {
135 Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
136 return null;
137 }
138 if (xmppAxolotlMessage.hasPayload()) {
139 final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage;
140 try {
141 plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
142 } catch (BrokenSessionException e) {
143 if (checkedForDuplicates) {
144 if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
145 service.reportBrokenSessionException(e, postpone);
146 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
147 } else {
148 Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted");
149 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
150 }
151 } else {
152 Log.d(Config.LOGTAG, "ignoring broken session exception because checkForDuplicates failed");
153 return null;
154 }
155 } catch (NotEncryptedForThisDeviceException e) {
156 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
157 } catch (OutdatedSenderException e) {
158 return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
159 }
160 if (plaintextMessage != null) {
161 Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
162 finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
163 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
164 return finishedMessage;
165 }
166 } else {
167 Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OMEMO key transport message");
168 service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
169 }
170 return null;
171 }
172
173 private Invite extractInvite(final Element message) {
174 final Element mucUser = message.findChild("x", Namespace.MUC_USER);
175 if (mucUser != null) {
176 final Element invite = mucUser.findChild("invite");
177 if (invite != null) {
178 final String password = mucUser.findChildContent("password");
179 final Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
180 final Jid to = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("to"));
181 if (to != null && from == null) {
182 Log.d(Config.LOGTAG,"do not parse outgoing mediated invite "+message);
183 return null;
184 }
185 final Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
186 if (room == null) {
187 return null;
188 }
189 return new Invite(room, password, false, from);
190 }
191 }
192 final Element conference = message.findChild("x", "jabber:x:conference");
193 if (conference != null) {
194 Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
195 Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid"));
196 if (room == null) {
197 return null;
198 }
199 return new Invite(room, conference.getAttribute("password"), true, from);
200 }
201 return null;
202 }
203
204 private void parseEvent(final Element event, final Jid from, final Account account) {
205 final Element items = event.findChild("items");
206 final String node = items == null ? null : items.getAttribute("node");
207 if ("urn:xmpp:avatar:metadata".equals(node)) {
208 Avatar avatar = Avatar.parseMetadata(items);
209 if (avatar != null) {
210 avatar.owner = from.asBareJid();
211 if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
212 if (account.getJid().asBareJid().equals(from)) {
213 if (account.setAvatar(avatar.getFilename())) {
214 mXmppConnectionService.databaseBackend.updateAccount(account);
215 mXmppConnectionService.notifyAccountAvatarHasChanged(account);
216 }
217 mXmppConnectionService.getAvatarService().clear(account);
218 mXmppConnectionService.updateConversationUi();
219 mXmppConnectionService.updateAccountUi();
220 } else {
221 final Contact contact = account.getRoster().getContact(from);
222 contact.setAvatar(avatar);
223 mXmppConnectionService.syncRoster(account);
224 mXmppConnectionService.getAvatarService().clear(contact);
225 mXmppConnectionService.updateConversationUi();
226 mXmppConnectionService.updateRosterUi();
227 }
228 } else if (mXmppConnectionService.isDataSaverDisabled()) {
229 mXmppConnectionService.fetchAvatar(account, avatar);
230 }
231 }
232 } else if (Namespace.NICK.equals(node)) {
233 final Element i = items.findChild("item");
234 final String nick = i == null ? null : i.findChildContent("nick", Namespace.NICK);
235 if (nick != null) {
236 setNick(account, from, nick);
237 }
238 } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
239 Element item = items.findChild("item");
240 Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
241 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
242 final AxolotlService axolotlService = account.getAxolotlService();
243 axolotlService.registerDevices(from, deviceIds);
244 } else if (Namespace.BOOKMARKS.equals(node) && account.getJid().asBareJid().equals(from)) {
245 if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
246 final Element i = items.findChild("item");
247 final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
248 Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
249 mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
250 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": processing bookmark PEP event");
251 } else {
252 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring bookmark PEP event because bookmark conversion was not detected");
253 }
254 } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
255 final Element item = items.findChild("item");
256 final Element retract = items.findChild("retract");
257 if (item != null) {
258 final Bookmark bookmark = Bookmark.parseFromItem(item, account);
259 if (bookmark != null) {
260 account.putBookmark(bookmark);
261 mXmppConnectionService.processModifiedBookmark(bookmark);
262 mXmppConnectionService.updateConversationUi();
263 }
264 }
265 if (retract != null) {
266 final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id"));
267 if (id != null) {
268 account.removeBookmark(id);
269 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
270 mXmppConnectionService.processDeletedBookmark(account, id);
271 mXmppConnectionService.updateConversationUi();
272 }
273 }
274 } else if (Namespace.MDS_DISPLAYED.equals(node) && account.getJid().asBareJid().equals(from)) {
275 final Element item = items.findChild("item");
276 mXmppConnectionService.processMdsItem(account, item);
277 } else {
278 Log.d(Config.LOGTAG, account.getJid().asBareJid() + " received pubsub notification for node=" + node);
279 }
280 }
281
282 private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
283 final Element delete = event.findChild("delete");
284 final String node = delete == null ? null : delete.getAttribute("node");
285 if (Namespace.NICK.equals(node)) {
286 Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
287 setNick(account, from, null);
288 } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
289 account.setBookmarks(Collections.emptyMap());
290 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
291 } else if (Namespace.AVATAR_METADATA.equals(node) && account.getJid().asBareJid().equals(from)) {
292 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted avatar metadata node");
293 }
294 }
295
296 private void parsePurgeEvent(final Element event, final Jid from, final Account account) {
297 final Element purge = event.findChild("purge");
298 final String node = purge == null ? null : purge.getAttribute("node");
299 if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
300 account.setBookmarks(Collections.emptyMap());
301 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
302 }
303 }
304
305 private void setNick(Account account, Jid user, String nick) {
306 if (user.asBareJid().equals(account.getJid().asBareJid())) {
307 account.setDisplayName(nick);
308 if (QuickConversationsService.isQuicksy()) {
309 mXmppConnectionService.getAvatarService().clear(account);
310 }
311 } else {
312 Contact contact = account.getRoster().getContact(user);
313 if (contact.setPresenceName(nick)) {
314 mXmppConnectionService.syncRoster(account);
315 mXmppConnectionService.getAvatarService().clear(contact);
316 }
317 }
318 mXmppConnectionService.updateConversationUi();
319 mXmppConnectionService.updateAccountUi();
320 }
321
322 private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
323 if (packet.getType() == MessagePacket.TYPE_ERROR) {
324 if (packet.fromServer(account)) {
325 final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS);
326 if (forwarded != null) {
327 return handleErrorMessage(account, forwarded.first);
328 }
329 }
330 final Jid from = packet.getFrom();
331 final String id = packet.getId();
332 if (from != null && id != null) {
333 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
334 final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
335 mXmppConnectionService.getJingleConnectionManager()
336 .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
337 return true;
338 }
339 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
340 final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
341 final String message = extractErrorMessage(packet);
342 mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message);
343 return true;
344 }
345 mXmppConnectionService.markMessage(account,
346 from.asBareJid(),
347 id,
348 Message.STATUS_SEND_FAILED,
349 extractErrorMessage(packet));
350 final Element error = packet.findChild("error");
351 final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
352 if (pingWorthyError) {
353 Conversation conversation = mXmppConnectionService.find(account, from);
354 if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
355 if (conversation.getMucOptions().online()) {
356 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ping worthy error for seemingly online muc at " + from);
357 mXmppConnectionService.mucSelfPingAndRejoin(conversation);
358 }
359 }
360 }
361 }
362 return true;
363 }
364 return false;
365 }
366
367 @Override
368 public void onMessagePacketReceived(Account account, MessagePacket original) {
369 if (handleErrorMessage(account, original)) {
370 return;
371 }
372 final MessagePacket packet;
373 Long timestamp = null;
374 boolean isCarbon = false;
375 String serverMsgId = null;
376 final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
377 if (fin != null) {
378 mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
379 return;
380 }
381 final Element result = MessageArchiveService.Version.findResult(original);
382 final String queryId = result == null ? null : result.getAttribute("queryid");
383 final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
384 final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
385 if (query != null && query.validFrom(original.getFrom())) {
386 final Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
387 if (f == null) {
388 return;
389 }
390 timestamp = f.second;
391 packet = f.first;
392 serverMsgId = result.getAttribute("id");
393 query.incrementMessageCount();
394 if (handleErrorMessage(account, packet)) {
395 return;
396 }
397 } else if (query != null) {
398 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
399 return;
400 } else if (original.fromServer(account)) {
401 Pair<MessagePacket, Long> f;
402 f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
403 f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
404 packet = f != null ? f.first : original;
405 if (handleErrorMessage(account, packet)) {
406 return;
407 }
408 timestamp = f != null ? f.second : null;
409 isCarbon = f != null;
410 } else {
411 packet = original;
412 }
413
414 if (timestamp == null) {
415 timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
416 }
417 final LocalizedContent body = packet.getBody();
418 final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
419 final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
420 final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
421 final Element oob = packet.findChild("x", Namespace.OOB);
422 final String oobUrl = oob != null ? oob.findChildContent("url") : null;
423 final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
424 final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
425 int status;
426 final Jid counterpart;
427 final Jid to = packet.getTo();
428 final Jid from = packet.getFrom();
429 final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
430 final String remoteMsgId;
431 if (originId != null && originId.getAttribute("id") != null) {
432 remoteMsgId = originId.getAttribute("id");
433 } else {
434 remoteMsgId = packet.getId();
435 }
436 boolean notify = false;
437
438 if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) {
439 Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
440 return;
441 }
442
443 boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
444 if (query != null && !query.muc() && isTypeGroupChat) {
445 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
446 return;
447 }
448 boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
449 boolean selfAddressed;
450 if (packet.fromAccount(account)) {
451 status = Message.STATUS_SEND;
452 selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
453 if (selfAddressed) {
454 counterpart = from;
455 } else {
456 counterpart = to != null ? to : account.getJid();
457 }
458 } else {
459 status = Message.STATUS_RECEIVED;
460 counterpart = from;
461 selfAddressed = false;
462 }
463
464 final Invite invite = extractInvite(packet);
465 if (invite != null) {
466 if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) {
467 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite to "+invite.jid+" because it matches account");
468 } else if (isTypeGroupChat) {
469 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because it was received as group chat");
470 } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
471 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC");
472 } else {
473 invite.execute(account);
474 return;
475 }
476 }
477
478 if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
479 final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
480 final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
481 final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
482
483 if (serverMsgId == null) {
484 serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
485 }
486
487
488 if (selfAddressed) {
489 if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
490 return;
491 }
492 status = Message.STATUS_RECEIVED;
493 if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
494 return;
495 }
496 }
497
498 if (isTypeGroupChat) {
499 if (conversation.getMucOptions().isSelf(counterpart)) {
500 status = Message.STATUS_SEND_RECEIVED;
501 isCarbon = true; //not really carbon but received from another resource
502 if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId, body)) {
503 return;
504 } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
505 if (body != null) {
506 Message message = conversation.findSentMessageWithBody(body.content);
507 if (message != null) {
508 mXmppConnectionService.markMessage(message, status);
509 return;
510 }
511 }
512 }
513 } else {
514 status = Message.STATUS_RECEIVED;
515 }
516 }
517 final Message message;
518 if (pgpEncrypted != null && Config.supportOpenPgp()) {
519 message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
520 } else if (axolotlEncrypted != null && Config.supportOmemo()) {
521 Jid origin;
522 Set<Jid> fallbacksBySourceId = Collections.emptySet();
523 if (conversationMultiMode) {
524 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
525 origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
526 if (origin == null) {
527 try {
528 fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
529 } catch (IllegalArgumentException e) {
530 //ignoring
531 }
532 }
533 if (origin == null && fallbacksBySourceId.size() == 0) {
534 Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
535 return;
536 }
537 } else {
538 fallbacksBySourceId = Collections.emptySet();
539 origin = from;
540 }
541
542 final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
543 final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
544
545 if (origin != null) {
546 message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates, query != null);
547 } else {
548 Message trial = null;
549 for (Jid fallback : fallbacksBySourceId) {
550 trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null);
551 if (trial != null) {
552 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
553 origin = fallback;
554 break;
555 }
556 }
557 message = trial;
558 }
559 if (message == null) {
560 if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
561 mXmppConnectionService.updateConversationUi();
562 }
563 if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
564 Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
565 if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
566 previouslySent.setServerMsgId(serverMsgId);
567 mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
568 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
569 }
570 }
571 return;
572 }
573 if (conversationMultiMode) {
574 message.setTrueCounterpart(origin);
575 }
576 } else if (body == null && oobUrl != null) {
577 message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
578 message.setOob(true);
579 if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
580 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
581 }
582 } else {
583 message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
584 if (body.count > 1) {
585 message.setBodyLanguage(body.language);
586 }
587 }
588
589 message.setCounterpart(counterpart);
590 message.setRemoteMsgId(remoteMsgId);
591 message.setServerMsgId(serverMsgId);
592 message.setCarbon(isCarbon);
593 message.setTime(timestamp);
594 if (body != null && body.content != null && body.content.equals(oobUrl)) {
595 message.setOob(true);
596 if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
597 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
598 }
599 }
600 message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
601 if (conversationMultiMode) {
602 message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart));
603 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
604 Jid trueCounterpart;
605 if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
606 trueCounterpart = message.getTrueCounterpart();
607 } else if (query != null && query.safeToExtractTrueCounterpart()) {
608 trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
609 } else {
610 trueCounterpart = fallback;
611 }
612 if (trueCounterpart != null && isTypeGroupChat) {
613 if (trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
614 status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
615 } else {
616 status = Message.STATUS_RECEIVED;
617 message.setCarbon(false);
618 }
619 }
620 message.setStatus(status);
621 message.setTrueCounterpart(trueCounterpart);
622 if (!isTypeGroupChat) {
623 message.setType(Message.TYPE_PRIVATE);
624 }
625 } else {
626 updateLastseen(account, from);
627 }
628
629 if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
630 final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
631 counterpart,
632 message.getStatus() == Message.STATUS_RECEIVED,
633 message.isCarbon());
634 if (replacedMessage != null) {
635 final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
636 || replacedMessage.getFingerprint().equals(message.getFingerprint());
637 final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
638 && message.getTrueCounterpart() != null
639 && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
640 final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
641 final boolean duplicate = conversation.hasDuplicateMessage(message);
642 if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
643 Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
644 synchronized (replacedMessage) {
645 final String uuid = replacedMessage.getUuid();
646 replacedMessage.setUuid(UUID.randomUUID().toString());
647 replacedMessage.setBody(message.getBody());
648 replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
649 replacedMessage.setRemoteMsgId(remoteMsgId);
650 if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
651 replacedMessage.setServerMsgId(message.getServerMsgId());
652 }
653 replacedMessage.setEncryption(message.getEncryption());
654 if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
655 replacedMessage.markUnread();
656 }
657 extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
658 mXmppConnectionService.updateMessage(replacedMessage, uuid);
659 if (mXmppConnectionService.confirmMessages()
660 && replacedMessage.getStatus() == Message.STATUS_RECEIVED
661 && (replacedMessage.trusted() || replacedMessage.isPrivateMessage()) //TODO do we really want to send receipts for all PMs?
662 && remoteMsgId != null
663 && !selfAddressed
664 && !isTypeGroupChat) {
665 processMessageReceipts(account, packet, remoteMsgId, query);
666 }
667 if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
668 conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
669 conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
670 }
671 }
672 mXmppConnectionService.getNotificationService().updateNotification();
673 return;
674 } else {
675 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
676 }
677 }
678 }
679
680 long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
681 if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
682 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
683 return;
684 }
685
686 boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
687 || message.isPrivateMessage()
688 || message.getServerMsgId() != null
689 || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
690 if (checkForDuplicates) {
691 final Message duplicate = conversation.findDuplicateMessage(message);
692 if (duplicate != null) {
693 final boolean serverMsgIdUpdated;
694 if (duplicate.getStatus() != Message.STATUS_RECEIVED
695 && duplicate.getUuid().equals(message.getRemoteMsgId())
696 && duplicate.getServerMsgId() == null
697 && message.getServerMsgId() != null) {
698 duplicate.setServerMsgId(message.getServerMsgId());
699 if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
700 serverMsgIdUpdated = true;
701 } else {
702 serverMsgIdUpdated = false;
703 Log.e(Config.LOGTAG, "failed to update message");
704 }
705 } else {
706 serverMsgIdUpdated = false;
707 }
708 Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
709 return;
710 }
711 }
712
713 if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
714 conversation.prepend(query.getActualInThisQuery(), message);
715 } else {
716 conversation.add(message);
717 }
718 if (query != null) {
719 query.incrementActualMessageCount();
720 }
721
722 if (query == null || query.isCatchup()) { //either no mam or catchup
723 if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
724 mXmppConnectionService.markRead(conversation);
725 if (query == null) {
726 activateGracePeriod(account);
727 }
728 } else {
729 message.markUnread();
730 notify = true;
731 }
732 }
733
734 if (message.getEncryption() == Message.ENCRYPTION_PGP) {
735 notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
736 } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
737 notify = false;
738 }
739
740 if (query == null) {
741 extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
742 mXmppConnectionService.updateConversationUi();
743 }
744
745 if (mXmppConnectionService.confirmMessages()
746 && message.getStatus() == Message.STATUS_RECEIVED
747 && (message.trusted() || message.isPrivateMessage())
748 && remoteMsgId != null
749 && !selfAddressed
750 && !isTypeGroupChat) {
751 processMessageReceipts(account, packet, remoteMsgId, query);
752 }
753
754 mXmppConnectionService.databaseBackend.createMessage(message);
755 final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
756 if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
757 manager.createNewDownloadConnection(message);
758 } else if (notify) {
759 if (query != null && query.isCatchup()) {
760 mXmppConnectionService.getNotificationService().pushFromBacklog(message);
761 } else {
762 mXmppConnectionService.getNotificationService().push(message);
763 }
764 }
765 } else if (!packet.hasChild("body")) { //no body
766
767 final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
768 if (axolotlEncrypted != null) {
769 Jid origin;
770 if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
771 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
772 origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
773 if (origin == null) {
774 Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
775 return;
776 }
777 } else if (isTypeGroupChat) {
778 return;
779 } else {
780 origin = from;
781 }
782 try {
783 final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
784 account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
785 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
786 } catch (Exception e) {
787 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
788 return;
789 }
790 }
791
792 if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
793 mXmppConnectionService.updateConversationUi();
794 }
795
796 if (isTypeGroupChat) {
797 if (packet.hasChild("subject") && !packet.hasChild("thread")) { // We already know it has no body per above
798 if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
799 conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
800 final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
801 if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
802 mXmppConnectionService.updateConversation(conversation);
803 }
804 mXmppConnectionService.updateConversationUi();
805 return;
806 }
807 }
808 }
809 if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
810 for (Element child : mucUserElement.getChildren()) {
811 if ("status".equals(child.getName())) {
812 try {
813 int code = Integer.parseInt(child.getAttribute("code"));
814 if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
815 mXmppConnectionService.fetchConferenceConfiguration(conversation);
816 break;
817 }
818 } catch (Exception e) {
819 //ignored
820 }
821 } else if ("item".equals(child.getName())) {
822 MucOptions.User user = AbstractParser.parseItem(conversation, child);
823 Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
824 + user.getRealJid() + " to " + user.getAffiliation() + " in "
825 + conversation.getJid().asBareJid());
826 if (!user.realJidMatchesAccount()) {
827 boolean isNew = conversation.getMucOptions().updateUser(user);
828 mXmppConnectionService.getAvatarService().clear(conversation);
829 mXmppConnectionService.updateMucRosterUi();
830 mXmppConnectionService.updateConversationUi();
831 Contact contact = user.getContact();
832 if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
833 Jid jid = user.getRealJid();
834 List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
835 if (cryptoTargets.remove(user.getRealJid())) {
836 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
837 conversation.setAcceptedCryptoTargets(cryptoTargets);
838 mXmppConnectionService.updateConversation(conversation);
839 }
840 } else if (isNew
841 && user.getRealJid() != null
842 && conversation.getMucOptions().isPrivateAndNonAnonymous()
843 && (contact == null || !contact.mutualPresenceSubscription())
844 && account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
845 account.getAxolotlService().fetchDeviceIds(user.getRealJid());
846 }
847 }
848 }
849 }
850 }
851 if (!isTypeGroupChat) {
852 for (Element child : packet.getChildren()) {
853 if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
854 final String action = child.getName();
855 final String sessionId = child.getAttribute("id");
856 if (sessionId == null) {
857 break;
858 }
859 if (query == null && offlineMessagesRetrieved) {
860 if (serverMsgId == null) {
861 serverMsgId = extractStanzaId(account, packet);
862 }
863 mXmppConnectionService
864 .getJingleConnectionManager()
865 .deliverMessage(
866 account,
867 packet.getTo(),
868 packet.getFrom(),
869 child,
870 remoteMsgId,
871 serverMsgId,
872 timestamp);
873 final Contact contact = account.getRoster().getContact(from);
874 if (mXmppConnectionService.confirmMessages()
875 && !contact.isSelf()
876 && remoteMsgId != null
877 && contact.showInContactList()) {
878 processMessageReceipts(account, packet, remoteMsgId, null);
879 }
880 } else if ((query != null && query.isCatchup()) || !offlineMessagesRetrieved) {
881 if ("propose".equals(action)) {
882 final Element description = child.findChild("description");
883 final String namespace =
884 description == null ? null : description.getNamespace();
885 if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
886 final Conversation c =
887 mXmppConnectionService.findOrCreateConversation(
888 account, counterpart.asBareJid(), false, false);
889 final Message preExistingMessage =
890 c.findRtpSession(sessionId, status);
891 if (preExistingMessage != null) {
892 preExistingMessage.setServerMsgId(serverMsgId);
893 mXmppConnectionService.updateMessage(preExistingMessage);
894 break;
895 }
896 final Message message =
897 new Message(
898 c, status, Message.TYPE_RTP_SESSION, sessionId);
899 message.setServerMsgId(serverMsgId);
900 message.setTime(timestamp);
901 message.setBody(new RtpSessionStatus(false, 0).toString());
902 c.add(message);
903 mXmppConnectionService.databaseBackend.createMessage(message);
904 }
905 } else if ("proceed".equals(action)) {
906 // status needs to be flipped to find the original propose
907 final Conversation c =
908 mXmppConnectionService.findOrCreateConversation(
909 account, counterpart.asBareJid(), false, false);
910 final int s =
911 packet.fromAccount(account)
912 ? Message.STATUS_RECEIVED
913 : Message.STATUS_SEND;
914 final Message message = c.findRtpSession(sessionId, s);
915 if (message != null) {
916 message.setBody(new RtpSessionStatus(true, 0).toString());
917 if (serverMsgId != null) {
918 message.setServerMsgId(serverMsgId);
919 }
920 message.setTime(timestamp);
921 mXmppConnectionService.updateMessage(message, true);
922 } else {
923 Log.d(
924 Config.LOGTAG,
925 "unable to find original rtp session message for received propose");
926 }
927
928 } else if ("finish".equals(action)) {
929 Log.d(
930 Config.LOGTAG,
931 "received JMI 'finish' during MAM catch-up. Can be used to update success/failure and duration");
932 }
933 } else {
934 //MAM reloads (non catchups
935 if ("propose".equals(action)) {
936 final Element description = child.findChild("description");
937 final String namespace = description == null ? null : description.getNamespace();
938 if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
939 final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
940 final Message preExistingMessage = c.findRtpSession(sessionId, status);
941 if (preExistingMessage != null) {
942 preExistingMessage.setServerMsgId(serverMsgId);
943 mXmppConnectionService.updateMessage(preExistingMessage);
944 break;
945 }
946 final Message message = new Message(
947 c,
948 status,
949 Message.TYPE_RTP_SESSION,
950 sessionId
951 );
952 message.setServerMsgId(serverMsgId);
953 message.setTime(timestamp);
954 message.setBody(new RtpSessionStatus(true, 0).toString());
955 if (query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
956 c.prepend(query.getActualInThisQuery(), message);
957 } else {
958 c.add(message);
959 }
960 query.incrementActualMessageCount();
961 mXmppConnectionService.databaseBackend.createMessage(message);
962 }
963 }
964 }
965 break;
966 }
967 }
968 }
969 }
970
971 Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
972 if (received == null) {
973 received = packet.findChild("received", "urn:xmpp:receipts");
974 }
975 if (received != null) {
976 String id = received.getAttribute("id");
977 if (packet.fromAccount(account)) {
978 if (query != null && id != null && packet.getTo() != null) {
979 query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
980 }
981 } else if (id != null) {
982 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
983 final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
984 mXmppConnectionService.getJingleConnectionManager()
985 .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
986 } else {
987 mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_RECEIVED);
988 }
989 }
990 }
991 final Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
992 if (displayed != null) {
993 final String id = displayed.getAttribute("id");
994 final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
995 if (packet.fromAccount(account) && !selfAddressed) {
996 final Conversation c =
997 mXmppConnectionService.find(account, counterpart.asBareJid());
998 final Message message =
999 (c == null || id == null) ? null : c.findReceivedWithRemoteId(id);
1000 if (message != null && (query == null || query.isCatchup())) {
1001 mXmppConnectionService.markReadUpTo(c, message);
1002 }
1003 if (query == null) {
1004 activateGracePeriod(account);
1005 }
1006 } else if (isTypeGroupChat) {
1007 final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
1008 final Message message;
1009 if (conversation != null && id != null) {
1010 if (sender != null) {
1011 message = conversation.findMessageWithRemoteId(id, sender);
1012 } else {
1013 message = conversation.findMessageWithServerMsgId(id);
1014 }
1015 } else {
1016 message = null;
1017 }
1018 if (message != null) {
1019 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
1020 final Jid trueJid = getTrueCounterpart((query != null && query.safeToExtractTrueCounterpart()) ? mucUserElement : null, fallback);
1021 final boolean trueJidMatchesAccount = account.getJid().asBareJid().equals(trueJid == null ? null : trueJid.asBareJid());
1022 if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
1023 if (!message.isRead() && (query == null || query.isCatchup())) { //checking if message is unread fixes race conditions with reflections
1024 mXmppConnectionService.markReadUpTo(conversation, message);
1025 }
1026 } else if (!counterpart.isBareJid() && trueJid != null) {
1027 final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
1028 if (message.addReadByMarker(readByMarker)) {
1029 mXmppConnectionService.updateMessage(message, false);
1030 }
1031 }
1032 }
1033 } else {
1034 final Message displayedMessage = mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
1035 Message message = displayedMessage == null ? null : displayedMessage.prev();
1036 while (message != null
1037 && message.getStatus() == Message.STATUS_SEND_RECEIVED
1038 && message.getTimeSent() < displayedMessage.getTimeSent()) {
1039 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
1040 message = message.prev();
1041 }
1042 if (displayedMessage != null && selfAddressed) {
1043 dismissNotification(account, counterpart, query, id);
1044 }
1045 }
1046 }
1047
1048 final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
1049 if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) {
1050 if (event.hasChild("items")) {
1051 parseEvent(event, original.getFrom(), account);
1052 } else if (event.hasChild("delete")) {
1053 parseDeleteEvent(event, original.getFrom(), account);
1054 } else if (event.hasChild("purge")) {
1055 parsePurgeEvent(event, original.getFrom(), account);
1056 }
1057 }
1058
1059 final String nick = packet.findChildContent("nick", Namespace.NICK);
1060 if (nick != null && InvalidJid.hasValidFrom(original)) {
1061 if (mXmppConnectionService.isMuc(account, from)) {
1062 return;
1063 }
1064 final Contact contact = account.getRoster().getContact(from);
1065 if (contact.setPresenceName(nick)) {
1066 mXmppConnectionService.syncRoster(account);
1067 mXmppConnectionService.getAvatarService().clear(contact);
1068 }
1069 }
1070 }
1071
1072 private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
1073 final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
1074 if (conversation != null && (query == null || query.isCatchup())) {
1075 final String displayableId = conversation.findMostRecentRemoteDisplayableId();
1076 if (displayableId != null && displayableId.equals(id)) {
1077 mXmppConnectionService.markRead(conversation);
1078 } else {
1079 Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": received dismissing display marker that did not match our last id in that conversation");
1080 }
1081 }
1082 }
1083
1084 private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) {
1085 final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
1086 final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
1087 if (query == null) {
1088 final ArrayList<String> receiptsNamespaces = new ArrayList<>();
1089 if (markable) {
1090 receiptsNamespaces.add("urn:xmpp:chat-markers:0");
1091 }
1092 if (request) {
1093 receiptsNamespaces.add("urn:xmpp:receipts");
1094 }
1095 if (receiptsNamespaces.size() > 0) {
1096 final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
1097 packet.getFrom(),
1098 remoteMsgId,
1099 receiptsNamespaces,
1100 packet.getType());
1101 mXmppConnectionService.sendMessagePacket(account, receipt);
1102 }
1103 } else if (query.isCatchup()) {
1104 if (request) {
1105 query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), remoteMsgId));
1106 }
1107 }
1108 }
1109
1110 private void activateGracePeriod(Account account) {
1111 long duration = mXmppConnectionService.getLongPreference("grace_period_length", R.integer.grace_period) * 1000;
1112 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
1113 account.activateGracePeriod(duration);
1114 }
1115
1116 private class Invite {
1117 final Jid jid;
1118 final String password;
1119 final boolean direct;
1120 final Jid inviter;
1121
1122 Invite(Jid jid, String password, boolean direct, Jid inviter) {
1123 this.jid = jid;
1124 this.password = password;
1125 this.direct = direct;
1126 this.inviter = inviter;
1127 }
1128
1129 public boolean execute(final Account account) {
1130 if (this.jid == null) {
1131 return false;
1132 }
1133 final Contact contact = this.inviter != null ? account.getRoster().getContact(this.inviter) : null;
1134 if (contact != null && contact.isBlocked()) {
1135 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite from "+contact.getJid()+" because contact is blocked");
1136 return false;
1137 }
1138 final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
1139 if (conversation.getMucOptions().online()) {
1140 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received invite to " + jid + " but muc is considered to be online");
1141 mXmppConnectionService.mucSelfPingAndRejoin(conversation);
1142 } else {
1143 conversation.getMucOptions().setPassword(password);
1144 mXmppConnectionService.databaseBackend.updateConversation(conversation);
1145 mXmppConnectionService.joinMuc(conversation, contact != null && contact.showInContactList());
1146 mXmppConnectionService.updateConversationUi();
1147 }
1148 return true;
1149 }
1150 }
1151}