MessageParser.java

   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;
 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 =
1315                    this.inviter != null ? account.getRoster().getContact(this.inviter) : null;
1316            if (contact != null && contact.isBlocked()) {
1317                Log.d(
1318                        Config.LOGTAG,
1319                        account.getJid().asBareJid()
1320                                + ": ignore invite from "
1321                                + contact.getJid()
1322                                + " because contact is blocked");
1323                return false;
1324            }
1325            final AppSettings appSettings = new AppSettings(mXmppConnectionService);
1326            if ((contact != null && contact.showInContactList())
1327                    || appSettings.isAcceptInvitesFromStrangers()) {
1328                final Conversation conversation =
1329                        mXmppConnectionService.findOrCreateConversation(account, jid, true, false);
1330                if (conversation.getMucOptions().online()) {
1331                    Log.d(
1332                            Config.LOGTAG,
1333                            account.getJid().asBareJid()
1334                                    + ": received invite to "
1335                                    + jid
1336                                    + " but muc is considered to be online");
1337                    mXmppConnectionService.mucSelfPingAndRejoin(conversation);
1338                } else {
1339                    conversation.getMucOptions().setPassword(password);
1340                    mXmppConnectionService.databaseBackend.updateConversation(conversation);
1341                    mXmppConnectionService.joinMuc(
1342                            conversation, contact != null && contact.showInContactList());
1343                    mXmppConnectionService.updateConversationUi();
1344                }
1345                return true;
1346            } else {
1347                Log.d(
1348                        Config.LOGTAG,
1349                        account.getJid().asBareJid()
1350                                + ": ignoring invite from "
1351                                + this.inviter
1352                                + " because we are not accepting invites from strangers. direct="
1353                                + direct);
1354                return false;
1355            }
1356        }
1357    }
1358}