PresenceParser.java

  1package eu.siacs.conversations.parser;
  2
  3import android.util.Log;
  4
  5import java.util.ArrayList;
  6import java.util.List;
  7
  8import eu.siacs.conversations.Config;
  9import eu.siacs.conversations.crypto.PgpEngine;
 10import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 11import eu.siacs.conversations.entities.Account;
 12import eu.siacs.conversations.entities.Contact;
 13import eu.siacs.conversations.entities.Conversation;
 14import eu.siacs.conversations.entities.Message;
 15import eu.siacs.conversations.entities.MucOptions;
 16import eu.siacs.conversations.entities.Presence;
 17import eu.siacs.conversations.generator.IqGenerator;
 18import eu.siacs.conversations.generator.PresenceGenerator;
 19import eu.siacs.conversations.services.XmppConnectionService;
 20import eu.siacs.conversations.utils.XmppUri;
 21import eu.siacs.conversations.xml.Element;
 22import eu.siacs.conversations.xml.Namespace;
 23import eu.siacs.conversations.xmpp.InvalidJid;
 24import eu.siacs.conversations.xmpp.Jid;
 25import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
 26import eu.siacs.conversations.xmpp.pep.Avatar;
 27import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 28
 29public class PresenceParser extends AbstractParser implements
 30		OnPresencePacketReceived {
 31
 32	public PresenceParser(XmppConnectionService service) {
 33		super(service);
 34	}
 35
 36	public void parseConferencePresence(PresencePacket packet, Account account) {
 37		final Conversation conversation = packet.getFrom() == null ? null : mXmppConnectionService.find(account, packet.getFrom().asBareJid());
 38		if (conversation != null) {
 39			final MucOptions mucOptions = conversation.getMucOptions();
 40			boolean before = mucOptions.online();
 41			int count = mucOptions.getUserCount();
 42			final List<MucOptions.User> tileUserBefore = mucOptions.getUsers(5);
 43			processConferencePresence(packet, conversation);
 44			final List<MucOptions.User> tileUserAfter = mucOptions.getUsers(5);
 45			if (!tileUserAfter.equals(tileUserBefore)) {
 46				mXmppConnectionService.getAvatarService().clear(mucOptions);
 47			}
 48			if (before != mucOptions.online() || (mucOptions.online() && count != mucOptions.getUserCount())) {
 49				mXmppConnectionService.updateConversationUi();
 50			} else if (mucOptions.online()) {
 51				mXmppConnectionService.updateMucRosterUi();
 52			}
 53		}
 54	}
 55
 56	private void processConferencePresence(PresencePacket packet, Conversation conversation) {
 57		MucOptions mucOptions = conversation.getMucOptions();
 58		final Jid jid = conversation.getAccount().getJid();
 59		final Jid from = packet.getFrom();
 60		if (!from.isBareJid()) {
 61			final String type = packet.getAttribute("type");
 62			final Element x = packet.findChild("x", Namespace.MUC_USER);
 63			Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
 64			final List<String> codes = getStatusCodes(x);
 65			if (type == null) {
 66				if (x != null) {
 67					Element item = x.findChild("item");
 68					if (item != null && !from.isBareJid()) {
 69						mucOptions.setError(MucOptions.Error.NONE);
 70						MucOptions.User user = parseItem(conversation, item, from);
 71						if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE) || (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && jid.equals(InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"))))) {
 72							if (mucOptions.setOnline()) {
 73								mXmppConnectionService.getAvatarService().clear(mucOptions);
 74							}
 75							if (mucOptions.setSelf(user)) {
 76								Log.d(Config.LOGTAG,"role or affiliation changed");
 77								mXmppConnectionService.databaseBackend.updateConversation(conversation);
 78							}
 79
 80							mXmppConnectionService.persistSelfNick(user);
 81							invokeRenameListener(mucOptions, true);
 82						}
 83						boolean isNew = mucOptions.updateUser(user);
 84						final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
 85						Contact contact = user.getContact();
 86						if (isNew
 87								&& user.getRealJid() != null
 88								&& mucOptions.isPrivateAndNonAnonymous()
 89								&& (contact == null || !contact.mutualPresenceSubscription())
 90								&& axolotlService.hasEmptyDeviceList(user.getRealJid())) {
 91							axolotlService.fetchDeviceIds(user.getRealJid());
 92						}
 93						if (codes.contains(MucOptions.STATUS_CODE_ROOM_CREATED) && mucOptions.autoPushConfiguration()) {
 94							Log.d(Config.LOGTAG,mucOptions.getAccount().getJid().asBareJid()
 95									+": room '"
 96									+mucOptions.getConversation().getJid().asBareJid()
 97									+"' created. pushing default configuration");
 98							mXmppConnectionService.pushConferenceConfiguration(mucOptions.getConversation(),
 99									IqGenerator.defaultChannelConfiguration(),
100									null);
101						}
102						if (mXmppConnectionService.getPgpEngine() != null) {
103							Element signed = packet.findChild("x", "jabber:x:signed");
104							if (signed != null) {
105								Element status = packet.findChild("status");
106								String msg = status == null ? "" : status.getContent();
107								long keyId = mXmppConnectionService.getPgpEngine().fetchKeyId(mucOptions.getAccount(), msg, signed.getContent());
108								if (keyId != 0) {
109									user.setPgpKeyId(keyId);
110								}
111							}
112						}
113						if (avatar != null) {
114							avatar.owner = from;
115							if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
116								if (user.setAvatar(avatar)) {
117									mXmppConnectionService.getAvatarService().clear(user);
118								}
119								if (user.getRealJid() != null) {
120									final Contact c = conversation.getAccount().getRoster().getContact(user.getRealJid());
121									c.setAvatar(avatar);
122									mXmppConnectionService.syncRoster(conversation.getAccount());
123									mXmppConnectionService.getAvatarService().clear(c);
124									mXmppConnectionService.updateRosterUi();
125								}
126							} else if (mXmppConnectionService.isDataSaverDisabled()) {
127								mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
128							}
129						}
130					}
131				}
132			} else if (type.equals("unavailable")) {
133				final boolean fullJidMatches = from.equals(mucOptions.getSelf().getFullJid());
134				if (x.hasChild("destroy") && fullJidMatches) {
135					Element destroy = x.findChild("destroy");
136					final Jid alternate = destroy == null ? null : InvalidJid.getNullForInvalid(destroy.getAttributeAsJid("jid"));
137					mucOptions.setError(MucOptions.Error.DESTROYED);
138					if (alternate != null) {
139						Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": muc destroyed. alternate location " + alternate);
140					}
141				} else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN) && fullJidMatches) {
142					mucOptions.setError(MucOptions.Error.SHUTDOWN);
143				} else if (codes.contains(MucOptions.STATUS_CODE_SELF_PRESENCE)) {
144					if (codes.contains(MucOptions.STATUS_CODE_KICKED)) {
145						mucOptions.setError(MucOptions.Error.KICKED);
146					} else if (codes.contains(MucOptions.STATUS_CODE_BANNED)) {
147						mucOptions.setError(MucOptions.Error.BANNED);
148					} else if (codes.contains(MucOptions.STATUS_CODE_LOST_MEMBERSHIP)) {
149						mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
150					} else if (codes.contains(MucOptions.STATUS_CODE_AFFILIATION_CHANGE)) {
151						mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
152					} else if (codes.contains(MucOptions.STATUS_CODE_SHUTDOWN)) {
153						mucOptions.setError(MucOptions.Error.SHUTDOWN);
154					} else if (!codes.contains(MucOptions.STATUS_CODE_CHANGED_NICK)) {
155						mucOptions.setError(MucOptions.Error.UNKNOWN);
156						Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
157					}
158				} else if (!from.isBareJid()){
159					Element item = x.findChild("item");
160					if (item != null) {
161						mucOptions.updateUser(parseItem(conversation, item, from));
162					}
163					MucOptions.User user = mucOptions.deleteUser(from);
164					if (user != null) {
165						mXmppConnectionService.getAvatarService().clear(user);
166					}
167				}
168			} else if (type.equals("error")) {
169				final Element error = packet.findChild("error");
170				if (error == null) {
171					return;
172				}
173				if (error.hasChild("conflict")) {
174					if (mucOptions.online()) {
175						invokeRenameListener(mucOptions, false);
176					} else {
177						mucOptions.setError(MucOptions.Error.NICK_IN_USE);
178					}
179				} else if (error.hasChild("not-authorized")) {
180					mucOptions.setError(MucOptions.Error.PASSWORD_REQUIRED);
181				} else if (error.hasChild("forbidden")) {
182					mucOptions.setError(MucOptions.Error.BANNED);
183				} else if (error.hasChild("registration-required")) {
184					mucOptions.setError(MucOptions.Error.MEMBERS_ONLY);
185				} else if (error.hasChild("resource-constraint")) {
186					mucOptions.setError(MucOptions.Error.RESOURCE_CONSTRAINT);
187				} else if (error.hasChild("remote-server-timeout")) {
188					mucOptions.setError(MucOptions.Error.REMOTE_SERVER_TIMEOUT);
189				} else if (error.hasChild("gone")) {
190					final String gone = error.findChildContent("gone");
191					final Jid alternate;
192					if (gone != null) {
193						final XmppUri xmppUri = new XmppUri(gone);
194						if (xmppUri.isValidJid()) {
195							alternate = xmppUri.getJid();
196						} else {
197							alternate = null;
198						}
199					} else {
200						alternate = null;
201					}
202					mucOptions.setError(MucOptions.Error.DESTROYED);
203					if (alternate != null) {
204						Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": muc destroyed. alternate location " + alternate);
205					}
206				} else {
207					final String text = error.findChildContent("text");
208					if (text != null && text.contains("attribute 'to'")) {
209						if (mucOptions.online()) {
210							invokeRenameListener(mucOptions, false);
211						} else {
212							mucOptions.setError(MucOptions.Error.INVALID_NICK);
213						}
214					} else {
215						mucOptions.setError(MucOptions.Error.UNKNOWN);
216						Log.d(Config.LOGTAG, "unknown error in conference: " + packet);
217					}
218				}
219			}
220		}
221	}
222
223	private static void invokeRenameListener(final MucOptions options, boolean success) {
224		if (options.onRenameListener != null) {
225			if (success) {
226				options.onRenameListener.onSuccess();
227			} else {
228				options.onRenameListener.onFailure();
229			}
230			options.onRenameListener = null;
231		}
232	}
233
234	private static List<String> getStatusCodes(Element x) {
235		List<String> codes = new ArrayList<>();
236		if (x != null) {
237			for (Element child : x.getChildren()) {
238				if (child.getName().equals("status")) {
239					String code = child.getAttribute("code");
240					if (code != null) {
241						codes.add(code);
242					}
243				}
244			}
245		}
246		return codes;
247	}
248
249	private void parseContactPresence(final PresencePacket packet, final Account account) {
250		final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
251		final Jid from = packet.getFrom();
252		if (from == null || from.equals(account.getJid())) {
253			return;
254		}
255		final String type = packet.getAttribute("type");
256		final Contact contact = account.getRoster().getContact(from);
257		if (type == null) {
258			final String resource = from.isBareJid() ? "" : from.getResource();
259			Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
260			if (avatar != null && (!contact.isSelf() || account.getAvatar() == null)) {
261				avatar.owner = from.asBareJid();
262				if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
263					if (avatar.owner.equals(account.getJid().asBareJid())) {
264						account.setAvatar(avatar.getFilename());
265						mXmppConnectionService.databaseBackend.updateAccount(account);
266						mXmppConnectionService.getAvatarService().clear(account);
267						mXmppConnectionService.updateConversationUi();
268						mXmppConnectionService.updateAccountUi();
269					} else {
270						contact.setAvatar(avatar);
271						mXmppConnectionService.syncRoster(account);
272						mXmppConnectionService.getAvatarService().clear(contact);
273						mXmppConnectionService.updateConversationUi();
274						mXmppConnectionService.updateRosterUi();
275					}
276				} else if (mXmppConnectionService.isDataSaverDisabled()){
277					mXmppConnectionService.fetchAvatar(account, avatar);
278				}
279			}
280
281			if (mXmppConnectionService.isMuc(account, from)) {
282				return;
283			}
284
285			int sizeBefore = contact.getPresences().size();
286
287			final String show = packet.findChildContent("show");
288			final Element caps = packet.findChild("c", "http://jabber.org/protocol/caps");
289			final String message = packet.findChildContent("status");
290			final Presence presence = Presence.parse(show, caps, message);
291			contact.updatePresence(resource, presence);
292			if (presence.hasCaps()) {
293				mXmppConnectionService.fetchCaps(account, from, presence);
294			}
295
296			final Element idle = packet.findChild("idle", Namespace.IDLE);
297			if (idle != null) {
298				try {
299					final String since = idle.getAttribute("since");
300					contact.setLastseen(AbstractParser.parseTimestamp(since));
301					contact.flagInactive();
302				} catch (Throwable throwable) {
303					if (contact.setLastseen(AbstractParser.parseTimestamp(packet))) {
304						contact.flagActive();
305					}
306				}
307			} else {
308				if (contact.setLastseen(AbstractParser.parseTimestamp(packet))) {
309					contact.flagActive();
310				}
311			}
312
313			PgpEngine pgp = mXmppConnectionService.getPgpEngine();
314			Element x = packet.findChild("x", "jabber:x:signed");
315			if (pgp != null && x != null) {
316				Element status = packet.findChild("status");
317				String msg = status != null ? status.getContent() : "";
318				if (contact.setPgpKeyId(pgp.fetchKeyId(account, msg, x.getContent()))) {
319					mXmppConnectionService.syncRoster(account);
320				}
321			}
322			boolean online = sizeBefore < contact.getPresences().size();
323			mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, online);
324		} else if (type.equals("unavailable")) {
325			if (contact.setLastseen(AbstractParser.parseTimestamp(packet,0L,true))) {
326				contact.flagInactive();
327			}
328			if (from.isBareJid()) {
329				contact.clearPresences();
330			} else {
331				contact.removePresence(from.getResource());
332			}
333			if (contact.getShownStatus() == Presence.Status.OFFLINE) {
334				contact.flagInactive();
335			}
336			mXmppConnectionService.onContactStatusChanged.onContactStatusChanged(contact, false);
337		} else if (type.equals("subscribe")) {
338			if (contact.setPresenceName(packet.findChildContent("nick", Namespace.NICK))) {
339				mXmppConnectionService.syncRoster(account);
340				mXmppConnectionService.getAvatarService().clear(contact);
341			}
342			if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
343				mXmppConnectionService.sendPresencePacket(account,
344						mPresenceGenerator.sendPresenceUpdatesTo(contact));
345			} else {
346				contact.setOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
347				final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
348						account, contact.getJid().asBareJid(), false, false);
349				final String statusMessage = packet.findChildContent("status");
350				if (statusMessage != null
351						&& !statusMessage.isEmpty()
352						&& conversation.countMessages() == 0) {
353					conversation.add(new Message(
354							conversation,
355							statusMessage,
356							Message.ENCRYPTION_NONE,
357							Message.STATUS_RECEIVED
358					));
359				}
360			}
361		}
362		mXmppConnectionService.updateRosterUi();
363	}
364
365	@Override
366	public void onPresencePacketReceived(Account account, PresencePacket packet) {
367		if (packet.hasChild("x", Namespace.MUC_USER)) {
368			this.parseConferencePresence(packet, account);
369		} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
370			this.parseConferencePresence(packet, account);
371		} else if ("error".equals(packet.getAttribute("type")) && mXmppConnectionService.isMuc(account, packet.getFrom())) {
372			this.parseConferencePresence(packet, account);
373		} else {
374			this.parseContactPresence(packet, account);
375		}
376	}
377}