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");
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 {
275 Log.d(Config.LOGTAG, account.getJid().asBareJid() + " received pubsub notification for node=" + node);
276 }
277 }
278
279 private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
280 final Element delete = event.findChild("delete");
281 final String node = delete == null ? null : delete.getAttribute("node");
282 if (Namespace.NICK.equals(node)) {
283 Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
284 setNick(account, from, null);
285 } else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
286 account.setBookmarks(Collections.emptyMap());
287 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
288 } else if (Namespace.AVATAR_METADATA.equals(node) && account.getJid().asBareJid().equals(from)) {
289 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": deleted avatar metadata node");
290 }
291 }
292
293 private void parsePurgeEvent(final Element event, final Jid from, final Account account) {
294 final Element purge = event.findChild("purge");
295 final String node = purge == null ? null : purge.getAttribute("node");
296 if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
297 account.setBookmarks(Collections.emptyMap());
298 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
299 }
300 }
301
302 private void setNick(Account account, Jid user, String nick) {
303 if (user.asBareJid().equals(account.getJid().asBareJid())) {
304 account.setDisplayName(nick);
305 if (QuickConversationsService.isQuicksy()) {
306 mXmppConnectionService.getAvatarService().clear(account);
307 }
308 } else {
309 Contact contact = account.getRoster().getContact(user);
310 if (contact.setPresenceName(nick)) {
311 mXmppConnectionService.syncRoster(account);
312 mXmppConnectionService.getAvatarService().clear(contact);
313 }
314 }
315 mXmppConnectionService.updateConversationUi();
316 mXmppConnectionService.updateAccountUi();
317 }
318
319 private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
320 if (packet.getType() == MessagePacket.TYPE_ERROR) {
321 if (packet.fromServer(account)) {
322 final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS);
323 if (forwarded != null) {
324 return handleErrorMessage(account, forwarded.first);
325 }
326 }
327 final Jid from = packet.getFrom();
328 final String id = packet.getId();
329 if (from != null && id != null) {
330 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
331 final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
332 mXmppConnectionService.getJingleConnectionManager()
333 .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.FAILED);
334 return true;
335 }
336 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
337 final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
338 final String message = extractErrorMessage(packet);
339 mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message);
340 return true;
341 }
342 mXmppConnectionService.markMessage(account,
343 from.asBareJid(),
344 id,
345 Message.STATUS_SEND_FAILED,
346 extractErrorMessage(packet));
347 final Element error = packet.findChild("error");
348 final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
349 if (pingWorthyError) {
350 Conversation conversation = mXmppConnectionService.find(account, from);
351 if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
352 if (conversation.getMucOptions().online()) {
353 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ping worthy error for seemingly online muc at " + from);
354 mXmppConnectionService.mucSelfPingAndRejoin(conversation);
355 }
356 }
357 }
358 }
359 return true;
360 }
361 return false;
362 }
363
364 @Override
365 public void onMessagePacketReceived(Account account, MessagePacket original) {
366 if (handleErrorMessage(account, original)) {
367 return;
368 }
369 final MessagePacket packet;
370 Long timestamp = null;
371 boolean isCarbon = false;
372 String serverMsgId = null;
373 final Element fin = original.findChild("fin", MessageArchiveService.Version.MAM_0.namespace);
374 if (fin != null) {
375 mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
376 return;
377 }
378 final Element result = MessageArchiveService.Version.findResult(original);
379 final String queryId = result == null ? null : result.getAttribute("queryid");
380 final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
381 if (query != null && query.validFrom(original.getFrom())) {
382 final Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
383 if (f == null) {
384 return;
385 }
386 timestamp = f.second;
387 packet = f.first;
388 serverMsgId = result.getAttribute("id");
389 query.incrementMessageCount();
390 if (handleErrorMessage(account, packet)) {
391 return;
392 }
393 } else if (query != null) {
394 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
395 return;
396 } else if (original.fromServer(account)) {
397 Pair<MessagePacket, Long> f;
398 f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
399 f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
400 packet = f != null ? f.first : original;
401 if (handleErrorMessage(account, packet)) {
402 return;
403 }
404 timestamp = f != null ? f.second : null;
405 isCarbon = f != null;
406 } else {
407 packet = original;
408 }
409
410 if (timestamp == null) {
411 timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
412 }
413 final LocalizedContent body = packet.getBody();
414 final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
415 final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
416 final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
417 final Element oob = packet.findChild("x", Namespace.OOB);
418 final String oobUrl = oob != null ? oob.findChildContent("url") : null;
419 final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
420 final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
421 int status;
422 final Jid counterpart;
423 final Jid to = packet.getTo();
424 final Jid from = packet.getFrom();
425 final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
426 final String remoteMsgId;
427 if (originId != null && originId.getAttribute("id") != null) {
428 remoteMsgId = originId.getAttribute("id");
429 } else {
430 remoteMsgId = packet.getId();
431 }
432 boolean notify = false;
433
434 if (from == null || !InvalidJid.isValid(from) || !InvalidJid.isValid(to)) {
435 Log.e(Config.LOGTAG, "encountered invalid message from='" + from + "' to='" + to + "'");
436 return;
437 }
438
439 boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
440 if (query != null && !query.muc() && isTypeGroupChat) {
441 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
442 return;
443 }
444 boolean isMucStatusMessage = InvalidJid.hasValidFrom(packet) && from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
445 boolean selfAddressed;
446 if (packet.fromAccount(account)) {
447 status = Message.STATUS_SEND;
448 selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
449 if (selfAddressed) {
450 counterpart = from;
451 } else {
452 counterpart = to != null ? to : account.getJid();
453 }
454 } else {
455 status = Message.STATUS_RECEIVED;
456 counterpart = from;
457 selfAddressed = false;
458 }
459
460 final Invite invite = extractInvite(packet);
461 if (invite != null) {
462 if (invite.jid.asBareJid().equals(account.getJid().asBareJid())) {
463 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite to "+invite.jid+" because it matches account");
464 } else if (isTypeGroupChat) {
465 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because it was received as group chat");
466 } else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
467 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC");
468 } else {
469 invite.execute(account);
470 return;
471 }
472 }
473
474 if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
475 final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServersWithholdAccount().contains(counterpart.getDomain().toEscapedString());
476 final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
477 final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
478
479 if (serverMsgId == null) {
480 serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
481 }
482
483
484 if (selfAddressed) {
485 if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
486 return;
487 }
488 status = Message.STATUS_RECEIVED;
489 if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId, counterpart) != null) {
490 return;
491 }
492 }
493
494 if (isTypeGroupChat) {
495 if (conversation.getMucOptions().isSelf(counterpart)) {
496 status = Message.STATUS_SEND_RECEIVED;
497 isCarbon = true; //not really carbon but received from another resource
498 if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId, body)) {
499 return;
500 } else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
501 if (body != null) {
502 Message message = conversation.findSentMessageWithBody(body.content);
503 if (message != null) {
504 mXmppConnectionService.markMessage(message, status);
505 return;
506 }
507 }
508 }
509 } else {
510 status = Message.STATUS_RECEIVED;
511 }
512 }
513 final Message message;
514 if (pgpEncrypted != null && Config.supportOpenPgp()) {
515 message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
516 } else if (axolotlEncrypted != null && Config.supportOmemo()) {
517 Jid origin;
518 Set<Jid> fallbacksBySourceId = Collections.emptySet();
519 if (conversationMultiMode) {
520 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
521 origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
522 if (origin == null) {
523 try {
524 fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
525 } catch (IllegalArgumentException e) {
526 //ignoring
527 }
528 }
529 if (origin == null && fallbacksBySourceId.size() == 0) {
530 Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
531 return;
532 }
533 } else {
534 fallbacksBySourceId = Collections.emptySet();
535 origin = from;
536 }
537
538 final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
539 final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
540
541 if (origin != null) {
542 message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates, query != null);
543 } else {
544 Message trial = null;
545 for (Jid fallback : fallbacksBySourceId) {
546 trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, checkedForDuplicates && fallbacksBySourceId.size() == 1, query != null);
547 if (trial != null) {
548 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": decoded muc message using fallback");
549 origin = fallback;
550 break;
551 }
552 }
553 message = trial;
554 }
555 if (message == null) {
556 if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
557 mXmppConnectionService.updateConversationUi();
558 }
559 if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
560 Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
561 if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
562 previouslySent.setServerMsgId(serverMsgId);
563 mXmppConnectionService.databaseBackend.updateMessage(previouslySent, false);
564 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered previously sent OMEMO message without serverId. updating...");
565 }
566 }
567 return;
568 }
569 if (conversationMultiMode) {
570 message.setTrueCounterpart(origin);
571 }
572 } else if (body == null && oobUrl != null) {
573 message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
574 message.setOob(true);
575 if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
576 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
577 }
578 } else {
579 message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
580 if (body.count > 1) {
581 message.setBodyLanguage(body.language);
582 }
583 }
584
585 message.setCounterpart(counterpart);
586 message.setRemoteMsgId(remoteMsgId);
587 message.setServerMsgId(serverMsgId);
588 message.setCarbon(isCarbon);
589 message.setTime(timestamp);
590 if (body != null && body.content != null && body.content.equals(oobUrl)) {
591 message.setOob(true);
592 if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
593 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
594 }
595 }
596 message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
597 if (conversationMultiMode) {
598 message.setMucUser(conversation.getMucOptions().findUserByFullJid(counterpart));
599 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
600 Jid trueCounterpart;
601 if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
602 trueCounterpart = message.getTrueCounterpart();
603 } else if (query != null && query.safeToExtractTrueCounterpart()) {
604 trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
605 } else {
606 trueCounterpart = fallback;
607 }
608 if (trueCounterpart != null && isTypeGroupChat) {
609 if (trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
610 status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
611 } else {
612 status = Message.STATUS_RECEIVED;
613 message.setCarbon(false);
614 }
615 }
616 message.setStatus(status);
617 message.setTrueCounterpart(trueCounterpart);
618 if (!isTypeGroupChat) {
619 message.setType(Message.TYPE_PRIVATE);
620 }
621 } else {
622 updateLastseen(account, from);
623 }
624
625 if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
626 final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
627 counterpart,
628 message.getStatus() == Message.STATUS_RECEIVED,
629 message.isCarbon());
630 if (replacedMessage != null) {
631 final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
632 || replacedMessage.getFingerprint().equals(message.getFingerprint());
633 final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
634 && message.getTrueCounterpart() != null
635 && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
636 final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
637 final boolean duplicate = conversation.hasDuplicateMessage(message);
638 if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
639 Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
640 synchronized (replacedMessage) {
641 final String uuid = replacedMessage.getUuid();
642 replacedMessage.setUuid(UUID.randomUUID().toString());
643 replacedMessage.setBody(message.getBody());
644 replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
645 replacedMessage.setRemoteMsgId(remoteMsgId);
646 if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
647 replacedMessage.setServerMsgId(message.getServerMsgId());
648 }
649 replacedMessage.setEncryption(message.getEncryption());
650 if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
651 replacedMessage.markUnread();
652 }
653 extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
654 mXmppConnectionService.updateMessage(replacedMessage, uuid);
655 if (mXmppConnectionService.confirmMessages()
656 && replacedMessage.getStatus() == Message.STATUS_RECEIVED
657 && (replacedMessage.trusted() || replacedMessage.isPrivateMessage()) //TODO do we really want to send receipts for all PMs?
658 && remoteMsgId != null
659 && !selfAddressed
660 && !isTypeGroupChat) {
661 processMessageReceipts(account, packet, remoteMsgId, query);
662 }
663 if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
664 conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
665 conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
666 }
667 }
668 mXmppConnectionService.getNotificationService().updateNotification();
669 return;
670 } else {
671 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
672 }
673 }
674 }
675
676 long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
677 if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
678 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
679 return;
680 }
681
682 boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
683 || message.isPrivateMessage()
684 || message.getServerMsgId() != null
685 || (query == null && mXmppConnectionService.getMessageArchiveService().isCatchupInProgress(conversation));
686 if (checkForDuplicates) {
687 final Message duplicate = conversation.findDuplicateMessage(message);
688 if (duplicate != null) {
689 final boolean serverMsgIdUpdated;
690 if (duplicate.getStatus() != Message.STATUS_RECEIVED
691 && duplicate.getUuid().equals(message.getRemoteMsgId())
692 && duplicate.getServerMsgId() == null
693 && message.getServerMsgId() != null) {
694 duplicate.setServerMsgId(message.getServerMsgId());
695 if (mXmppConnectionService.databaseBackend.updateMessage(duplicate, false)) {
696 serverMsgIdUpdated = true;
697 } else {
698 serverMsgIdUpdated = false;
699 Log.e(Config.LOGTAG, "failed to update message");
700 }
701 } else {
702 serverMsgIdUpdated = false;
703 }
704 Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
705 return;
706 }
707 }
708
709 if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
710 conversation.prepend(query.getActualInThisQuery(), message);
711 } else {
712 conversation.add(message);
713 }
714 if (query != null) {
715 query.incrementActualMessageCount();
716 }
717
718 if (query == null || query.isCatchup()) { //either no mam or catchup
719 if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
720 mXmppConnectionService.markRead(conversation);
721 if (query == null) {
722 activateGracePeriod(account);
723 }
724 } else {
725 message.markUnread();
726 notify = true;
727 }
728 }
729
730 if (message.getEncryption() == Message.ENCRYPTION_PGP) {
731 notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
732 } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || message.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
733 notify = false;
734 }
735
736 if (query == null) {
737 extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
738 mXmppConnectionService.updateConversationUi();
739 }
740
741 if (mXmppConnectionService.confirmMessages()
742 && message.getStatus() == Message.STATUS_RECEIVED
743 && (message.trusted() || message.isPrivateMessage())
744 && remoteMsgId != null
745 && !selfAddressed
746 && !isTypeGroupChat) {
747 processMessageReceipts(account, packet, remoteMsgId, query);
748 }
749
750 mXmppConnectionService.databaseBackend.createMessage(message);
751 final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
752 if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
753 manager.createNewDownloadConnection(message);
754 } else if (notify) {
755 if (query != null && query.isCatchup()) {
756 mXmppConnectionService.getNotificationService().pushFromBacklog(message);
757 } else {
758 mXmppConnectionService.getNotificationService().push(message);
759 }
760 }
761 } else if (!packet.hasChild("body")) { //no body
762
763 final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
764 if (axolotlEncrypted != null) {
765 Jid origin;
766 if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
767 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
768 origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
769 if (origin == null) {
770 Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
771 return;
772 }
773 } else if (isTypeGroupChat) {
774 return;
775 } else {
776 origin = from;
777 }
778 try {
779 final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
780 account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
781 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": omemo key transport message received from " + origin);
782 } catch (Exception e) {
783 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
784 return;
785 }
786 }
787
788 if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
789 mXmppConnectionService.updateConversationUi();
790 }
791
792 if (isTypeGroupChat) {
793 if (packet.hasChild("subject")) { //TODO usually we would want to check for lack of body; however some servers do set a body :(
794 if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
795 conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
796 final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
797 if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
798 mXmppConnectionService.updateConversation(conversation);
799 }
800 mXmppConnectionService.updateConversationUi();
801 return;
802 }
803 }
804 }
805 if (conversation != null && mucUserElement != null && InvalidJid.hasValidFrom(packet) && from.isBareJid()) {
806 for (Element child : mucUserElement.getChildren()) {
807 if ("status".equals(child.getName())) {
808 try {
809 int code = Integer.parseInt(child.getAttribute("code"));
810 if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
811 mXmppConnectionService.fetchConferenceConfiguration(conversation);
812 break;
813 }
814 } catch (Exception e) {
815 //ignored
816 }
817 } else if ("item".equals(child.getName())) {
818 MucOptions.User user = AbstractParser.parseItem(conversation, child);
819 Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
820 + user.getRealJid() + " to " + user.getAffiliation() + " in "
821 + conversation.getJid().asBareJid());
822 if (!user.realJidMatchesAccount()) {
823 boolean isNew = conversation.getMucOptions().updateUser(user);
824 mXmppConnectionService.getAvatarService().clear(conversation);
825 mXmppConnectionService.updateMucRosterUi();
826 mXmppConnectionService.updateConversationUi();
827 Contact contact = user.getContact();
828 if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
829 Jid jid = user.getRealJid();
830 List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
831 if (cryptoTargets.remove(user.getRealJid())) {
832 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
833 conversation.setAcceptedCryptoTargets(cryptoTargets);
834 mXmppConnectionService.updateConversation(conversation);
835 }
836 } else if (isNew
837 && user.getRealJid() != null
838 && conversation.getMucOptions().isPrivateAndNonAnonymous()
839 && (contact == null || !contact.mutualPresenceSubscription())
840 && account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
841 account.getAxolotlService().fetchDeviceIds(user.getRealJid());
842 }
843 }
844 }
845 }
846 }
847 if (!isTypeGroupChat) {
848 for (Element child : packet.getChildren()) {
849 if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) {
850 final String action = child.getName();
851 final String sessionId = child.getAttribute("id");
852 if (sessionId == null) {
853 break;
854 }
855 if (query == null) {
856 if (serverMsgId == null) {
857 serverMsgId = extractStanzaId(account, packet);
858 }
859 mXmppConnectionService
860 .getJingleConnectionManager()
861 .deliverMessage(
862 account,
863 packet.getTo(),
864 packet.getFrom(),
865 child,
866 remoteMsgId,
867 serverMsgId,
868 timestamp);
869 final Contact contact = account.getRoster().getContact(from);
870 if (mXmppConnectionService.confirmMessages()
871 && !contact.isSelf()
872 && remoteMsgId != null
873 && contact.showInContactList()) {
874 processMessageReceipts(account, packet, remoteMsgId, null);
875 }
876 } else if (query.isCatchup()) {
877 if ("propose".equals(action)) {
878 final Element description = child.findChild("description");
879 final String namespace = description == null ? null : description.getNamespace();
880 if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
881 final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
882 final Message preExistingMessage = c.findRtpSession(sessionId, status);
883 if (preExistingMessage != null) {
884 preExistingMessage.setServerMsgId(serverMsgId);
885 mXmppConnectionService.updateMessage(preExistingMessage);
886 break;
887 }
888 final Message message = new Message(
889 c,
890 status,
891 Message.TYPE_RTP_SESSION,
892 sessionId
893 );
894 message.setServerMsgId(serverMsgId);
895 message.setTime(timestamp);
896 message.setBody(new RtpSessionStatus(false, 0).toString());
897 c.add(message);
898 mXmppConnectionService.databaseBackend.createMessage(message);
899 }
900 } else if ("proceed".equals(action)) {
901 //status needs to be flipped to find the original propose
902 final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
903 final int s = packet.fromAccount(account) ? Message.STATUS_RECEIVED : Message.STATUS_SEND;
904 final Message message = c.findRtpSession(sessionId, s);
905 if (message != null) {
906 message.setBody(new RtpSessionStatus(true, 0).toString());
907 if (serverMsgId != null) {
908 message.setServerMsgId(serverMsgId);
909 }
910 message.setTime(timestamp);
911 mXmppConnectionService.updateMessage(message, true);
912 } else {
913 Log.d(Config.LOGTAG, "unable to find original rtp session message for received propose");
914 }
915
916 }
917 } else {
918 //MAM reloads (non catchups
919 if ("propose".equals(action)) {
920 final Element description = child.findChild("description");
921 final String namespace = description == null ? null : description.getNamespace();
922 if (Namespace.JINGLE_APPS_RTP.equals(namespace)) {
923 final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false);
924 final Message preExistingMessage = c.findRtpSession(sessionId, status);
925 if (preExistingMessage != null) {
926 preExistingMessage.setServerMsgId(serverMsgId);
927 mXmppConnectionService.updateMessage(preExistingMessage);
928 break;
929 }
930 final Message message = new Message(
931 c,
932 status,
933 Message.TYPE_RTP_SESSION,
934 sessionId
935 );
936 message.setServerMsgId(serverMsgId);
937 message.setTime(timestamp);
938 message.setBody(new RtpSessionStatus(true, 0).toString());
939 if (query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
940 c.prepend(query.getActualInThisQuery(), message);
941 } else {
942 c.add(message);
943 }
944 query.incrementActualMessageCount();
945 mXmppConnectionService.databaseBackend.createMessage(message);
946 }
947 }
948 }
949 break;
950 }
951 }
952 }
953 }
954
955 Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
956 if (received == null) {
957 received = packet.findChild("received", "urn:xmpp:receipts");
958 }
959 if (received != null) {
960 String id = received.getAttribute("id");
961 if (packet.fromAccount(account)) {
962 if (query != null && id != null && packet.getTo() != null) {
963 query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(), id));
964 }
965 } else if (id != null) {
966 if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX)) {
967 final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX.length());
968 mXmppConnectionService.getJingleConnectionManager()
969 .updateProposedSessionDiscovered(account, from, sessionId, JingleConnectionManager.DeviceDiscoveryState.DISCOVERED);
970 } else {
971 mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_RECEIVED);
972 }
973 }
974 }
975 Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
976 if (displayed != null) {
977 final String id = displayed.getAttribute("id");
978 final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
979 if (packet.fromAccount(account) && !selfAddressed) {
980 dismissNotification(account, counterpart, query, id);
981 if (query == null) {
982 activateGracePeriod(account);
983 }
984 } else if (isTypeGroupChat) {
985 final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
986 final Message message;
987 if (conversation != null && id != null) {
988 if (sender != null) {
989 message = conversation.findMessageWithRemoteId(id, sender);
990 } else {
991 message = conversation.findMessageWithServerMsgId(id);
992 }
993 } else {
994 message = null;
995 }
996 if (message != null) {
997 final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
998 final Jid trueJid = getTrueCounterpart((query != null && query.safeToExtractTrueCounterpart()) ? mucUserElement : null, fallback);
999 final boolean trueJidMatchesAccount = account.getJid().asBareJid().equals(trueJid == null ? null : trueJid.asBareJid());
1000 if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
1001 if (!message.isRead() && (query == null || query.isCatchup())) { //checking if message is unread fixes race conditions with reflections
1002 mXmppConnectionService.markRead(conversation);
1003 }
1004 } else if (!counterpart.isBareJid() && trueJid != null) {
1005 final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
1006 if (message.addReadByMarker(readByMarker)) {
1007 mXmppConnectionService.updateMessage(message, false);
1008 }
1009 }
1010 }
1011 } else {
1012 final Message displayedMessage = mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
1013 Message message = displayedMessage == null ? null : displayedMessage.prev();
1014 while (message != null
1015 && message.getStatus() == Message.STATUS_SEND_RECEIVED
1016 && message.getTimeSent() < displayedMessage.getTimeSent()) {
1017 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
1018 message = message.prev();
1019 }
1020 if (displayedMessage != null && selfAddressed) {
1021 dismissNotification(account, counterpart, query, id);
1022 }
1023 }
1024 }
1025
1026 final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
1027 if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) {
1028 if (event.hasChild("items")) {
1029 parseEvent(event, original.getFrom(), account);
1030 } else if (event.hasChild("delete")) {
1031 parseDeleteEvent(event, original.getFrom(), account);
1032 } else if (event.hasChild("purge")) {
1033 parsePurgeEvent(event, original.getFrom(), account);
1034 }
1035 }
1036
1037 final String nick = packet.findChildContent("nick", Namespace.NICK);
1038 if (nick != null && InvalidJid.hasValidFrom(original)) {
1039 if (mXmppConnectionService.isMuc(account, from)) {
1040 return;
1041 }
1042 final Contact contact = account.getRoster().getContact(from);
1043 if (contact.setPresenceName(nick)) {
1044 mXmppConnectionService.syncRoster(account);
1045 mXmppConnectionService.getAvatarService().clear(contact);
1046 }
1047 }
1048 }
1049
1050 private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
1051 final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
1052 if (conversation != null && (query == null || query.isCatchup())) {
1053 final String displayableId = conversation.findMostRecentRemoteDisplayableId();
1054 if (displayableId != null && displayableId.equals(id)) {
1055 mXmppConnectionService.markRead(conversation);
1056 } else {
1057 Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": received dismissing display marker that did not match our last id in that conversation");
1058 }
1059 }
1060 }
1061
1062 private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) {
1063 final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
1064 final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
1065 if (query == null) {
1066 final ArrayList<String> receiptsNamespaces = new ArrayList<>();
1067 if (markable) {
1068 receiptsNamespaces.add("urn:xmpp:chat-markers:0");
1069 }
1070 if (request) {
1071 receiptsNamespaces.add("urn:xmpp:receipts");
1072 }
1073 if (receiptsNamespaces.size() > 0) {
1074 final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
1075 packet.getFrom(),
1076 remoteMsgId,
1077 receiptsNamespaces,
1078 packet.getType());
1079 mXmppConnectionService.sendMessagePacket(account, receipt);
1080 }
1081 } else if (query.isCatchup()) {
1082 if (request) {
1083 query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(), remoteMsgId));
1084 }
1085 }
1086 }
1087
1088 private void activateGracePeriod(Account account) {
1089 long duration = mXmppConnectionService.getLongPreference("grace_period_length", R.integer.grace_period) * 1000;
1090 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
1091 account.activateGracePeriod(duration);
1092 }
1093
1094 private class Invite {
1095 final Jid jid;
1096 final String password;
1097 final boolean direct;
1098 final Jid inviter;
1099
1100 Invite(Jid jid, String password, boolean direct, Jid inviter) {
1101 this.jid = jid;
1102 this.password = password;
1103 this.direct = direct;
1104 this.inviter = inviter;
1105 }
1106
1107 public boolean execute(final Account account) {
1108 if (this.jid == null) {
1109 return false;
1110 }
1111 final Contact contact = this.inviter != null ? account.getRoster().getContact(this.inviter) : null;
1112 if (contact != null && contact.isBlocked()) {
1113 Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignore invite from "+contact.getJid()+" because contact is blocked");
1114 return false;
1115 }
1116 final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
1117 if (conversation.getMucOptions().online()) {
1118 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received invite to " + jid + " but muc is considered to be online");
1119 mXmppConnectionService.mucSelfPingAndRejoin(conversation);
1120 } else {
1121 conversation.getMucOptions().setPassword(password);
1122 mXmppConnectionService.databaseBackend.updateConversation(conversation);
1123 mXmppConnectionService.joinMuc(conversation, contact != null && contact.showInContactList());
1124 mXmppConnectionService.updateConversationUi();
1125 }
1126 return true;
1127 }
1128 }
1129}