MessageParser.java

  1package eu.siacs.conversations.parser;
  2
  3import android.util.Log;
  4import android.util.Pair;
  5
  6import java.text.SimpleDateFormat;
  7import java.util.ArrayList;
  8import java.util.Arrays;
  9import java.util.Collections;
 10import java.util.Date;
 11import java.util.List;
 12import java.util.Locale;
 13import java.util.Set;
 14import java.util.UUID;
 15
 16import eu.siacs.conversations.Config;
 17import eu.siacs.conversations.R;
 18import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 19import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
 20import eu.siacs.conversations.entities.Account;
 21import eu.siacs.conversations.entities.Bookmark;
 22import eu.siacs.conversations.entities.Contact;
 23import eu.siacs.conversations.entities.Conversation;
 24import eu.siacs.conversations.entities.Message;
 25import eu.siacs.conversations.entities.MucOptions;
 26import eu.siacs.conversations.entities.ReadByMarker;
 27import eu.siacs.conversations.entities.ReceiptRequest;
 28import eu.siacs.conversations.http.HttpConnectionManager;
 29import eu.siacs.conversations.services.MessageArchiveService;
 30import eu.siacs.conversations.services.XmppConnectionService;
 31import eu.siacs.conversations.utils.CryptoHelper;
 32import eu.siacs.conversations.xml.Namespace;
 33import eu.siacs.conversations.xml.Element;
 34import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
 35import eu.siacs.conversations.xmpp.chatstate.ChatState;
 36import eu.siacs.conversations.xmpp.pep.Avatar;
 37import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 38import rocks.xmpp.addr.Jid;
 39
 40public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
 41
 42	public MessageParser(XmppConnectionService service) {
 43		super(service);
 44	}
 45
 46	private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
 47		ChatState state = ChatState.parse(packet);
 48		if (state != null && c != null) {
 49			final Account account = c.getAccount();
 50			Jid from = packet.getFrom();
 51			if (from.asBareJid().equals(account.getJid().asBareJid())) {
 52				c.setOutgoingChatState(state);
 53				if (state == ChatState.ACTIVE || state == ChatState.COMPOSING) {
 54					mXmppConnectionService.markRead(c);
 55					activateGracePeriod(account);
 56				}
 57				return false;
 58			} else {
 59				if (isTypeGroupChat) {
 60					MucOptions.User user = c.getMucOptions().findUserByFullJid(from);
 61					if (user != null) {
 62						return user.setChatState(state);
 63					} else {
 64						return false;
 65					}
 66				} else {
 67					return c.setIncomingChatState(state);
 68				}
 69			}
 70		}
 71		return false;
 72	}
 73
 74	private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean postpone) {
 75		final AxolotlService service = conversation.getAccount().getAxolotlService();
 76		final XmppAxolotlMessage xmppAxolotlMessage;
 77		try {
 78			xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.asBareJid());
 79		} catch (Exception e) {
 80			Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": invalid omemo message received " + e.getMessage());
 81			return null;
 82		}
 83		if (xmppAxolotlMessage.hasPayload()) {
 84			final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
 85			if (plaintextMessage != null) {
 86				Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
 87				finishedMessage.setFingerprint(plaintextMessage.getFingerprint());
 88				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount()) + " Received Message with session fingerprint: " + plaintextMessage.getFingerprint());
 89				return finishedMessage;
 90			}
 91		} else {
 92			Log.d(Config.LOGTAG,conversation.getAccount().getJid().asBareJid()+": received OMEMO key transport message");
 93			service.processReceivingKeyTransportMessage(xmppAxolotlMessage, postpone);
 94		}
 95		return null;
 96	}
 97
 98	private class Invite {
 99		final Jid jid;
100		final String password;
101		final Contact inviter;
102
103		Invite(Jid jid, String password, Contact inviter) {
104			this.jid = jid;
105			this.password = password;
106			this.inviter = inviter;
107		}
108
109		public boolean execute(Account account) {
110			if (jid != null) {
111				Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
112				if (!conversation.getMucOptions().online()) {
113					conversation.getMucOptions().setPassword(password);
114					mXmppConnectionService.databaseBackend.updateConversation(conversation);
115					mXmppConnectionService.joinMuc(conversation, inviter != null && inviter.mutualPresenceSubscription());
116					mXmppConnectionService.updateConversationUi();
117				}
118				return true;
119			}
120			return false;
121		}
122	}
123
124	private Invite extractInvite(Account account, Element message) {
125		Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
126		if (x != null) {
127			Element invite = x.findChild("invite");
128			if (invite != null) {
129				Element pw = x.findChild("password");
130				Jid from = invite.getAttributeAsJid("from");
131				Contact contact = from == null ? null : account.getRoster().getContact(from);
132				return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent() : null, contact);
133			}
134		} else {
135			x = message.findChild("x", "jabber:x:conference");
136			if (x != null) {
137				Jid from = message.getAttributeAsJid("from");
138				Contact contact = from == null ? null : account.getRoster().getContact(from);
139				return new Invite(x.getAttributeAsJid("jid"), x.getAttribute("password"), contact);
140			}
141		}
142		return null;
143	}
144
145	private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
146		final Jid by;
147		final boolean safeToExtract;
148		if (isTypeGroupChat) {
149			by = conversation.getJid().asBareJid();
150			safeToExtract = conversation.getMucOptions().hasFeature(Namespace.STANZA_IDS);
151		} else {
152			Account account = conversation.getAccount();
153			by = account.getJid().asBareJid();
154			safeToExtract = account.getXmppConnection().getFeatures().stanzaIds();
155		}
156		return safeToExtract ? extractStanzaId(packet, by) : null;
157	}
158
159	private static String extractStanzaId(Element packet, Jid by) {
160		for (Element child : packet.getChildren()) {
161			if (child.getName().equals("stanza-id")
162					&& Namespace.STANZA_IDS.equals(child.getNamespace())
163					&& by.equals(child.getAttributeAsJid("by"))) {
164				return child.getAttribute("id");
165			}
166		}
167		return null;
168	}
169
170	private void parseEvent(final Element event, final Jid from, final Account account) {
171		Element items = event.findChild("items");
172		String node = items == null ? null : items.getAttribute("node");
173		if ("urn:xmpp:avatar:metadata".equals(node)) {
174			Avatar avatar = Avatar.parseMetadata(items);
175			if (avatar != null) {
176				avatar.owner = from.asBareJid();
177				if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
178					if (account.getJid().asBareJid().equals(from)) {
179						if (account.setAvatar(avatar.getFilename())) {
180							mXmppConnectionService.databaseBackend.updateAccount(account);
181						}
182						mXmppConnectionService.getAvatarService().clear(account);
183						mXmppConnectionService.updateConversationUi();
184						mXmppConnectionService.updateAccountUi();
185					} else {
186						Contact contact = account.getRoster().getContact(from);
187						contact.setAvatar(avatar);
188						mXmppConnectionService.getAvatarService().clear(contact);
189						mXmppConnectionService.updateConversationUi();
190						mXmppConnectionService.updateRosterUi();
191					}
192				} else if (mXmppConnectionService.isDataSaverDisabled()) {
193					mXmppConnectionService.fetchAvatar(account, avatar);
194				}
195			}
196		} else if ("http://jabber.org/protocol/nick".equals(node)) {
197			final Element i = items.findChild("item");
198			final String nick = i == null ? null : i.findChildContent("nick", Namespace.NICK);
199			if (nick != null) {
200				Contact contact = account.getRoster().getContact(from);
201				if (contact.setPresenceName(nick)) {
202					mXmppConnectionService.getAvatarService().clear(contact);
203				}
204				mXmppConnectionService.updateConversationUi();
205				mXmppConnectionService.updateAccountUi();
206			}
207		} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
208			Element item = items.findChild("item");
209			Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
210			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
211			AxolotlService axolotlService = account.getAxolotlService();
212			axolotlService.registerDevices(from, deviceIds);
213			mXmppConnectionService.updateAccountUi();
214		}
215	}
216
217	private boolean handleErrorMessage(Account account, MessagePacket packet) {
218		if (packet.getType() == MessagePacket.TYPE_ERROR) {
219			Jid from = packet.getFrom();
220			if (from != null) {
221				Message message = mXmppConnectionService.markMessage(account,
222						from.asBareJid(),
223						packet.getId(),
224						Message.STATUS_SEND_FAILED,
225						extractErrorMessage(packet));
226			}
227			return true;
228		}
229		return false;
230	}
231
232	@Override
233	public void onMessagePacketReceived(Account account, MessagePacket original) {
234		if (handleErrorMessage(account, original)) {
235			return;
236		}
237		final MessagePacket packet;
238		Long timestamp = null;
239		boolean isCarbon = false;
240		String serverMsgId = null;
241		final Element fin = original.findChild("fin", Namespace.MAM_LEGACY);
242		if (fin != null) {
243			mXmppConnectionService.getMessageArchiveService().processFinLegacy(fin, original.getFrom());
244			return;
245		}
246		final boolean mamLegacy = original.hasChild("result", Namespace.MAM_LEGACY);
247		final Element result = original.findChild("result", mamLegacy ? Namespace.MAM_LEGACY : Namespace.MAM);
248		final MessageArchiveService.Query query = result == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(result.getAttribute("queryid"));
249		if (query != null && query.validFrom(original.getFrom())) {
250			Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", mamLegacy ? Namespace.MAM_LEGACY : Namespace.MAM);
251			if (f == null) {
252				return;
253			}
254			timestamp = f.second;
255			packet = f.first;
256			serverMsgId = result.getAttribute("id");
257			query.incrementMessageCount();
258		} else if (query != null) {
259			Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result from invalid sender");
260			return;
261		} else if (original.fromServer(account)) {
262			Pair<MessagePacket, Long> f;
263			f = original.getForwardedMessagePacket("received", "urn:xmpp:carbons:2");
264			f = f == null ? original.getForwardedMessagePacket("sent", "urn:xmpp:carbons:2") : f;
265			packet = f != null ? f.first : original;
266			if (handleErrorMessage(account, packet)) {
267				return;
268			}
269			timestamp = f != null ? f.second : null;
270			isCarbon = f != null;
271		} else {
272			packet = original;
273		}
274
275		if (timestamp == null) {
276			timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
277		}
278		final String body = packet.getBody();
279		final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
280		final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
281		final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
282		final Element oob = packet.findChild("x", Namespace.OOB);
283		final String oobUrl = oob != null ? oob.findChildContent("url") : null;
284		final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
285		final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
286		int status;
287		final Jid counterpart;
288		final Jid to = packet.getTo();
289		final Jid from = packet.getFrom();
290		final Element originId = packet.findChild("origin-id", Namespace.STANZA_IDS);
291		final String remoteMsgId;
292		if (originId != null && originId.getAttribute("id") != null) {
293			remoteMsgId = originId.getAttribute("id");
294		} else {
295			remoteMsgId = packet.getId();
296		}
297		boolean notify = false;
298
299		if (from == null) {
300			Log.d(Config.LOGTAG, "no from in: " + packet.toString());
301			return;
302		}
303
304		boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
305		if (query != null && !query.muc() && isTypeGroupChat) {
306			Log.e(Config.LOGTAG,account.getJid().asBareJid()+": received groupchat ("+from+") message on regular MAM request. skipping");
307			return;
308		}
309		boolean isMucStatusMessage = from.isBareJid() && mucUserElement != null && mucUserElement.hasChild("status");
310		boolean selfAddressed;
311		if (packet.fromAccount(account)) {
312			status = Message.STATUS_SEND;
313			selfAddressed = to == null || account.getJid().asBareJid().equals(to.asBareJid());
314			if (selfAddressed) {
315				counterpart = from;
316			} else {
317				counterpart = to != null ? to : account.getJid();
318			}
319		} else {
320			status = Message.STATUS_RECEIVED;
321			counterpart = from;
322			selfAddressed = false;
323		}
324
325		Invite invite = extractInvite(account, packet);
326		if (invite != null && invite.execute(account)) {
327			return;
328		}
329
330		if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null) && !isMucStatusMessage) {
331			final boolean conversationIsProbablyMuc = isTypeGroupChat || mucUserElement != null || account.getXmppConnection().getMucServers().contains(counterpart.getDomain());
332			final Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), conversationIsProbablyMuc, false, query, false);
333			final boolean conversationMultiMode = conversation.getMode() == Conversation.MODE_MULTI;
334
335			if (serverMsgId == null) {
336				serverMsgId = extractStanzaId(packet, isTypeGroupChat, conversation);
337			}
338
339
340			if (selfAddressed) {
341				if (mXmppConnectionService.markMessage(conversation, remoteMsgId, Message.STATUS_SEND_RECEIVED, serverMsgId)) {
342					return;
343				}
344				status = Message.STATUS_RECEIVED;
345				if (remoteMsgId != null && conversation.findMessageWithRemoteId(remoteMsgId,counterpart) != null) {
346					return;
347				}
348			}
349
350			if (isTypeGroupChat) {
351				if (conversation.getMucOptions().isSelf(counterpart)) {
352					status = Message.STATUS_SEND_RECEIVED;
353					isCarbon = true; //not really carbon but received from another resource
354					if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) {
355						return;
356					} else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
357						Message message = conversation.findSentMessageWithBody(packet.getBody());
358						if (message != null) {
359							mXmppConnectionService.markMessage(message, status);
360							return;
361						}
362					}
363				} else {
364					status = Message.STATUS_RECEIVED;
365				}
366			}
367			final Message message;
368			if (pgpEncrypted != null && Config.supportOpenPgp()) {
369				message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
370			} else if (axolotlEncrypted != null && Config.supportOmemo()) {
371				Jid origin;
372				Set<Jid> fallbacksBySourceId = Collections.emptySet();
373				if (conversationMultiMode) {
374					final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
375					origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
376					if (origin == null) {
377						try {
378							fallbacksBySourceId = account.getAxolotlService().findCounterpartsBySourceId(XmppAxolotlMessage.parseSourceId(axolotlEncrypted));
379						} catch (IllegalArgumentException e) {
380							//ignoring
381						}
382					}
383					if (origin == null && fallbacksBySourceId.size() == 0) {
384						Log.d(Config.LOGTAG, "axolotl message in anonymous conference received and no possible fallbacks");
385						return;
386					}
387				} else {
388					fallbacksBySourceId = Collections.emptySet();
389					origin = from;
390				}
391				if (origin != null) {
392					message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, query != null);
393				} else {
394					Message trial = null;
395					for(Jid fallback : fallbacksBySourceId) {
396						trial = parseAxolotlChat(axolotlEncrypted, fallback, conversation, status, query != null);
397						if (trial != null) {
398							Log.d(Config.LOGTAG,account.getJid().asBareJid()+": decoded muc message using fallback");
399							origin = fallback;
400							break;
401						}
402					}
403					message = trial;
404				}
405				if (message == null) {
406					if (query == null &&  extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
407						mXmppConnectionService.updateConversationUi();
408					}
409					if (query != null && status == Message.STATUS_SEND && remoteMsgId != null) {
410						Message previouslySent = conversation.findSentMessageWithUuid(remoteMsgId);
411						if (previouslySent != null && previouslySent.getServerMsgId() == null && serverMsgId != null) {
412							previouslySent.setServerMsgId(serverMsgId);
413							mXmppConnectionService.databaseBackend.updateMessage(previouslySent);
414							Log.d(Config.LOGTAG,account.getJid().asBareJid()+": encountered previously sent OMEMO message without serverId. updating...");
415						}
416					}
417					return;
418				}
419				if (conversationMultiMode) {
420					message.setTrueCounterpart(origin);
421				}
422			} else if (body == null && oobUrl != null) {
423				message = new Message(conversation, oobUrl, Message.ENCRYPTION_NONE, status);
424				message.setOob(true);
425				if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
426					message.setEncryption(Message.ENCRYPTION_DECRYPTED);
427				}
428			} else {
429				message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
430			}
431
432			message.setCounterpart(counterpart);
433			message.setRemoteMsgId(remoteMsgId);
434			message.setServerMsgId(serverMsgId);
435			message.setCarbon(isCarbon);
436			message.setTime(timestamp);
437			if (body != null && body.equals(oobUrl)) {
438				message.setOob(true);
439				if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
440					message.setEncryption(Message.ENCRYPTION_DECRYPTED);
441				}
442			}
443			message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
444			if (conversationMultiMode) {
445				final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
446				Jid trueCounterpart;
447				if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
448					trueCounterpart = message.getTrueCounterpart();
449				} else if (query != null && query.safeToExtractTrueCounterpart()) {
450					trueCounterpart = getTrueCounterpart(mucUserElement, fallback);
451				} else {
452					trueCounterpart = fallback;
453				}
454				if (trueCounterpart != null && trueCounterpart.asBareJid().equals(account.getJid().asBareJid())) {
455					status = isTypeGroupChat ? Message.STATUS_SEND_RECEIVED : Message.STATUS_SEND;
456				}
457				message.setStatus(status);
458				message.setTrueCounterpart(trueCounterpart);
459				if (!isTypeGroupChat) {
460					message.setType(Message.TYPE_PRIVATE);
461				}
462			} else {
463				updateLastseen(account, from);
464			}
465
466			if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
467				final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
468						counterpart,
469						message.getStatus() == Message.STATUS_RECEIVED,
470						message.isCarbon());
471				if (replacedMessage != null) {
472					final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
473							|| replacedMessage.getFingerprint().equals(message.getFingerprint());
474					final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
475							&& replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
476					final boolean duplicate = conversation.hasDuplicateMessage(message);
477					if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode) && !duplicate) {
478						Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
479						synchronized (replacedMessage) {
480							final String uuid = replacedMessage.getUuid();
481							replacedMessage.setUuid(UUID.randomUUID().toString());
482							replacedMessage.setBody(message.getBody());
483							replacedMessage.setEdited(replacedMessage.getRemoteMsgId());
484							replacedMessage.setRemoteMsgId(remoteMsgId);
485							if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
486								replacedMessage.setServerMsgId(message.getServerMsgId());
487							}
488							replacedMessage.setEncryption(message.getEncryption());
489							if (replacedMessage.getStatus() == Message.STATUS_RECEIVED) {
490								replacedMessage.markUnread();
491							}
492							extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
493							mXmppConnectionService.updateMessage(replacedMessage, uuid);
494							mXmppConnectionService.getNotificationService().updateNotification(false);
495							if (mXmppConnectionService.confirmMessages()
496									&& replacedMessage.getStatus() == Message.STATUS_RECEIVED
497									&& (replacedMessage.trusted() || replacedMessage.getType() == Message.TYPE_PRIVATE)
498									&& remoteMsgId != null
499									&& !selfAddressed
500									&& !isTypeGroupChat) {
501								processMessageReceipts(account, packet, query);
502							}
503							if (replacedMessage.getEncryption() == Message.ENCRYPTION_PGP) {
504								conversation.getAccount().getPgpDecryptionService().discard(replacedMessage);
505								conversation.getAccount().getPgpDecryptionService().decrypt(replacedMessage, false);
506							}
507						}
508						return;
509					} else {
510						Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
511					}
512				}
513			}
514
515			long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
516			if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
517				Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
518				return;
519			}
520
521			boolean checkForDuplicates = (isTypeGroupChat && packet.hasChild("delay", "urn:xmpp:delay"))
522					|| message.getType() == Message.TYPE_PRIVATE
523					|| message.getServerMsgId() != null;
524			if (checkForDuplicates ) {
525				final Message duplicate = conversation.findDuplicateMessage(message);
526				if (duplicate != null) {
527					final boolean serverMsgIdUpdated;
528					if (duplicate.getStatus() != Message.STATUS_RECEIVED
529							&& duplicate.getUuid().equals(message.getRemoteMsgId())
530							&& duplicate.getServerMsgId() == null
531							&& message.getServerMsgId() != null) {
532						duplicate.setServerMsgId(message.getServerMsgId());
533						mXmppConnectionService.databaseBackend.updateMessage(message);
534						serverMsgIdUpdated = true;
535					} else {
536						serverMsgIdUpdated = false;
537					}
538					Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart()+". serverMsgIdUpdated="+Boolean.toString(serverMsgIdUpdated));
539					return;
540				}
541			}
542
543			if (query != null && query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) {
544				conversation.prepend(query.getActualInThisQuery(),message);
545			} else {
546				conversation.add(message);
547			}
548			if (query != null) {
549				query.incrementActualMessageCount();
550			}
551
552			if (query == null || query.isCatchup()) { //either no mam or catchup
553				if (status == Message.STATUS_SEND || status == Message.STATUS_SEND_RECEIVED) {
554					mXmppConnectionService.markRead(conversation);
555					if (query == null) {
556						activateGracePeriod(account);
557					}
558				} else {
559					message.markUnread();
560					notify = true;
561				}
562			}
563
564			if (message.getEncryption() == Message.ENCRYPTION_PGP) {
565				notify = conversation.getAccount().getPgpDecryptionService().decrypt(message, notify);
566			}
567
568			if (query == null) {
569				extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
570				mXmppConnectionService.updateConversationUi();
571			}
572
573			if (mXmppConnectionService.confirmMessages()
574					&& message.getStatus() == Message.STATUS_RECEIVED
575					&& (message.trusted() || message.getType() == Message.TYPE_PRIVATE)
576					&& remoteMsgId != null
577					&& !selfAddressed
578					&& !isTypeGroupChat) {
579				processMessageReceipts(account, packet, query);
580			}
581
582			mXmppConnectionService.databaseBackend.createMessage(message);
583			final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
584			if (message.trusted() && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
585				manager.createNewDownloadConnection(message);
586			} else if (notify) {
587				if (query != null && query.isCatchup()) {
588					mXmppConnectionService.getNotificationService().pushFromBacklog(message);
589				} else {
590					mXmppConnectionService.getNotificationService().push(message);
591				}
592			}
593		} else if (!packet.hasChild("body")) { //no body
594
595			final Conversation conversation = mXmppConnectionService.find(account, from.asBareJid());
596			if (axolotlEncrypted != null) {
597				Jid origin;
598				if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
599					final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
600					origin = getTrueCounterpart(query != null ? mucUserElement : null, fallback);
601					if (origin == null) {
602						Log.d(Config.LOGTAG, "omemo key transport message in anonymous conference received");
603						return;
604					}
605				} else if (isTypeGroupChat) {
606					return;
607				} else {
608					origin = from;
609				}
610				try {
611					final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlEncrypted, origin.asBareJid());
612					account.getAxolotlService().processReceivingKeyTransportMessage(xmppAxolotlMessage, query != null);
613					Log.d(Config.LOGTAG,account.getJid().asBareJid()+": omemo key transport message received from "+origin);
614				} catch (Exception e) {
615					Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": invalid omemo key transport message received " + e.getMessage());
616					return;
617				}
618			}
619
620			if (query == null && extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet)) {
621				mXmppConnectionService.updateConversationUi();
622			}
623
624			if (isTypeGroupChat) {
625				if (packet.hasChild("subject")) {
626					if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
627						conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
628						String subject = packet.findInternationalizedChildContent("subject");
629						if (conversation.getMucOptions().setSubject(subject)) {
630							mXmppConnectionService.updateConversation(conversation);
631						}
632						final Bookmark bookmark = conversation.getBookmark();
633						if (bookmark != null && bookmark.getBookmarkName() == null) {
634							if (bookmark.setBookmarkName(subject)) {
635								mXmppConnectionService.pushBookmarks(account);
636							}
637						}
638						mXmppConnectionService.updateConversationUi();
639						return;
640					}
641				}
642			}
643			if (conversation != null && mucUserElement != null && from.isBareJid()) {
644				for (Element child : mucUserElement.getChildren()) {
645					if ("status".equals(child.getName())) {
646						try {
647							int code = Integer.parseInt(child.getAttribute("code"));
648							if ((code >= 170 && code <= 174) || (code >= 102 && code <= 104)) {
649								mXmppConnectionService.fetchConferenceConfiguration(conversation);
650								break;
651							}
652						} catch (Exception e) {
653							//ignored
654						}
655					} else if ("item".equals(child.getName())) {
656						MucOptions.User user = AbstractParser.parseItem(conversation, child);
657						Log.d(Config.LOGTAG, account.getJid() + ": changing affiliation for "
658								+ user.getRealJid() + " to " + user.getAffiliation() + " in "
659								+ conversation.getJid().asBareJid());
660						if (!user.realJidMatchesAccount()) {
661							boolean isNew = conversation.getMucOptions().updateUser(user);
662							mXmppConnectionService.getAvatarService().clear(conversation);
663							mXmppConnectionService.updateMucRosterUi();
664							mXmppConnectionService.updateConversationUi();
665							Contact contact = user.getContact();
666							if (!user.getAffiliation().ranks(MucOptions.Affiliation.MEMBER)) {
667								Jid jid = user.getRealJid();
668								List<Jid> cryptoTargets = conversation.getAcceptedCryptoTargets();
669								if (cryptoTargets.remove(user.getRealJid())) {
670									Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": removed " + jid + " from crypto targets of " + conversation.getName());
671									conversation.setAcceptedCryptoTargets(cryptoTargets);
672									mXmppConnectionService.updateConversation(conversation);
673								}
674							} else if (isNew
675									&& user.getRealJid() != null
676									&& conversation.getMucOptions().isPrivateAndNonAnonymous()
677									&& (contact == null || !contact.mutualPresenceSubscription())
678									&& account.getAxolotlService().hasEmptyDeviceList(user.getRealJid())) {
679								account.getAxolotlService().fetchDeviceIds(user.getRealJid());
680							}
681						}
682					}
683				}
684			}
685		}
686
687		Element received = packet.findChild("received", "urn:xmpp:chat-markers:0");
688		if (received == null) {
689			received = packet.findChild("received", "urn:xmpp:receipts");
690		}
691		if (received != null) {
692			String id = received.getAttribute("id");
693			if (packet.fromAccount(account)) {
694				if (query != null && id != null && packet.getTo() != null) {
695					query.removePendingReceiptRequest(new ReceiptRequest(packet.getTo(),id));
696				}
697			} else {
698				mXmppConnectionService.markMessage(account, from.asBareJid(), received.getAttribute("id"), Message.STATUS_SEND_RECEIVED);
699			}
700		}
701		Element displayed = packet.findChild("displayed", "urn:xmpp:chat-markers:0");
702		if (displayed != null) {
703			final String id = displayed.getAttribute("id");
704			final Jid sender = displayed.getAttributeAsJid("sender");
705			if (packet.fromAccount(account) && !selfAddressed) {
706				dismissNotification(account, counterpart, query);
707			} else if (isTypeGroupChat) {
708				Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
709				if (conversation != null && id != null && sender != null) {
710					Message message = conversation.findMessageWithRemoteId(id, sender);
711					if (message != null) {
712						final Jid fallback = conversation.getMucOptions().getTrueCounterpart(counterpart);
713						final Jid trueJid = getTrueCounterpart((query != null && query.safeToExtractTrueCounterpart()) ? mucUserElement : null, fallback);
714						final boolean trueJidMatchesAccount = account.getJid().asBareJid().equals(trueJid == null ? null : trueJid.asBareJid());
715						if (trueJidMatchesAccount || conversation.getMucOptions().isSelf(counterpart)) {
716							if (!message.isRead() && (query == null || query.isCatchup())) { //checking if message is unread fixes race conditions with reflections
717								mXmppConnectionService.markRead(conversation);
718							}
719						} else  if (!counterpart.isBareJid() && trueJid != null){
720							ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
721							if (message.addReadByMarker(readByMarker)) {
722								Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added read by (" + readByMarker.getRealJid() + ") to message '" + message.getBody() + "'");
723								mXmppConnectionService.updateMessage(message);
724							}
725						}
726					}
727				}
728			} else {
729				final Message displayedMessage = mXmppConnectionService.markMessage(account, from.asBareJid(), id, Message.STATUS_SEND_DISPLAYED);
730				Message message = displayedMessage == null ? null : displayedMessage.prev();
731				while (message != null
732						&& message.getStatus() == Message.STATUS_SEND_RECEIVED
733						&& message.getTimeSent() < displayedMessage.getTimeSent()) {
734					mXmppConnectionService.markMessage(message, Message.STATUS_SEND_DISPLAYED);
735					message = message.prev();
736				}
737				if (displayedMessage != null && selfAddressed) {
738					dismissNotification(account, counterpart, query);
739				}
740			}
741		}
742
743		Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
744		if (event != null) {
745			parseEvent(event, original.getFrom(), account);
746		}
747
748		final String nick = packet.findChildContent("nick", Namespace.NICK);
749		if (nick != null) {
750			Contact contact = account.getRoster().getContact(from);
751			if (contact.setPresenceName(nick)) {
752				mXmppConnectionService.getAvatarService().clear(contact);
753			}
754		}
755	}
756
757	private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query) {
758		Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
759		if (conversation != null && (query == null || query.isCatchup())) {
760			mXmppConnectionService.markRead(conversation); //TODO only mark messages read that are older than timestamp
761		}
762	}
763
764	private static Jid getTrueCounterpart(Element mucUserElement, Jid fallback) {
765		final Element item = mucUserElement == null ? null : mucUserElement.findChild("item");
766		Jid result = item == null ? null : item.getAttributeAsJid("jid");
767		return result != null ? result : fallback;
768	}
769
770	private void processMessageReceipts(Account account, MessagePacket packet, MessageArchiveService.Query query) {
771		final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
772		final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
773		if (query == null) {
774			final ArrayList<String> receiptsNamespaces = new ArrayList<>();
775			if (markable) {
776				receiptsNamespaces.add("urn:xmpp:chat-markers:0");
777			}
778			if (request) {
779				receiptsNamespaces.add("urn:xmpp:receipts");
780			}
781			if (receiptsNamespaces.size() > 0) {
782				MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
783						packet,
784						receiptsNamespaces,
785						packet.getType());
786				mXmppConnectionService.sendMessagePacket(account, receipt);
787			}
788		} else { //just catchup??
789			if (request) {
790				query.addPendingReceiptRequest(new ReceiptRequest(packet.getFrom(),packet.getId()));
791			}
792		}
793	}
794
795	private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
796
797	private void activateGracePeriod(Account account) {
798		long duration = mXmppConnectionService.getLongPreference("grace_period_length", R.integer.grace_period) * 1000;
799		Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": activating grace period till " + TIME_FORMAT.format(new Date(System.currentTimeMillis() + duration)));
800		account.activateGracePeriod(duration);
801	}
802}