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