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