MessageParser.java

  1package eu.siacs.conversations.parser;
  2
  3import android.util.Log;
  4
  5import net.java.otr4j.session.Session;
  6import net.java.otr4j.session.SessionStatus;
  7
  8import eu.siacs.conversations.Config;
  9import eu.siacs.conversations.entities.Account;
 10import eu.siacs.conversations.entities.Contact;
 11import eu.siacs.conversations.entities.Conversation;
 12import eu.siacs.conversations.entities.Message;
 13import eu.siacs.conversations.services.XmppConnectionService;
 14import eu.siacs.conversations.utils.CryptoHelper;
 15import eu.siacs.conversations.xml.Element;
 16import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
 17import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 18import eu.siacs.conversations.xmpp.jid.Jid;
 19import eu.siacs.conversations.xmpp.pep.Avatar;
 20import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
 21
 22public class MessageParser extends AbstractParser implements
 23		OnMessagePacketReceived {
 24	public MessageParser(XmppConnectionService service) {
 25		super(service);
 26	}
 27
 28	private Message parseChat(MessagePacket packet, Account account) {
 29        final Jid jid = packet.getFrom();
 30		if (jid == null) {
 31			return null;
 32		}
 33		Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid.toBareJid(), false);
 34		updateLastseen(packet, account, true);
 35		String pgpBody = getPgpBody(packet);
 36		Message finishedMessage;
 37		if (pgpBody != null) {
 38			finishedMessage = new Message(conversation,
 39					pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECEIVED);
 40		} else {
 41			finishedMessage = new Message(conversation,
 42					packet.getBody(), Message.ENCRYPTION_NONE,
 43					Message.STATUS_RECEIVED);
 44		}
 45		finishedMessage.setRemoteMsgId(packet.getId());
 46		finishedMessage.markable = isMarkable(packet);
 47		if (conversation.getMode() == Conversation.MODE_MULTI
 48				&& !jid.isBareJid()) {
 49			finishedMessage.setType(Message.TYPE_PRIVATE);
 50			finishedMessage.setTrueCounterpart(conversation.getMucOptions()
 51					.getTrueCounterpart(jid.getResourcepart()));
 52			if (conversation.hasDuplicateMessage(finishedMessage)) {
 53				return null;
 54			}
 55
 56		}
 57		finishedMessage.setCounterpart(jid);
 58		finishedMessage.setTime(getTimestamp(packet));
 59		return finishedMessage;
 60	}
 61
 62	private Message parseOtrChat(MessagePacket packet, Account account) {
 63		boolean properlyAddressed = (!packet.getTo().isBareJid())
 64				|| (account.countPresences() == 1);
 65        final Jid from = packet.getFrom();
 66		if (from == null) {
 67			return null;
 68		}
 69		Conversation conversation = mXmppConnectionService
 70				.findOrCreateConversation(account, from.toBareJid(), false);
 71		String presence;
 72		if (from.isBareJid()) {
 73            presence = "";
 74		} else {
 75			presence = from.getResourcepart();
 76		}
 77		updateLastseen(packet, account, true);
 78		String body = packet.getBody();
 79		if (body.matches("^\\?OTRv\\d*\\?")) {
 80			conversation.endOtrIfNeeded();
 81		}
 82		if (!conversation.hasValidOtrSession()) {
 83			if (properlyAddressed) {
 84				conversation.startOtrSession(presence,false);
 85			} else {
 86				return null;
 87			}
 88		} else {
 89			String foreignPresence = conversation.getOtrSession()
 90					.getSessionID().getUserID();
 91			if (!foreignPresence.equals(presence)) {
 92				conversation.endOtrIfNeeded();
 93				if (properlyAddressed) {
 94					conversation.startOtrSession(presence, false);
 95				} else {
 96					return null;
 97				}
 98			}
 99		}
100		try {
101			Session otrSession = conversation.getOtrSession();
102			SessionStatus before = otrSession.getSessionStatus();
103			body = otrSession.transformReceiving(body);
104			SessionStatus after = otrSession.getSessionStatus();
105			if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
106				mXmppConnectionService.onOtrSessionEstablished(conversation);
107			} else if ((before != after) && (after == SessionStatus.FINISHED)) {
108				conversation.resetOtrSession();
109				mXmppConnectionService.updateConversationUi();
110			}
111			if ((body == null) || (body.isEmpty())) {
112				return null;
113			}
114			if (body.startsWith(CryptoHelper.FILETRANSFER)) {
115				String key = body.substring(CryptoHelper.FILETRANSFER.length());
116				conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
117				return null;
118			}
119			Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR,
120					Message.STATUS_RECEIVED);
121			finishedMessage.setTime(getTimestamp(packet));
122			finishedMessage.setRemoteMsgId(packet.getId());
123			finishedMessage.markable = isMarkable(packet);
124			finishedMessage.setCounterpart(from);
125			return finishedMessage;
126		} catch (Exception e) {
127			String receivedId = packet.getId();
128			if (receivedId != null) {
129				mXmppConnectionService.replyWithNotAcceptable(account, packet);
130			}
131			conversation.resetOtrSession();
132			return null;
133		}
134	}
135
136	private Message parseGroupchat(MessagePacket packet, Account account) {
137		int status;
138        final Jid from = packet.getFrom();
139		if (from == null) {
140			return null;
141		}
142		if (mXmppConnectionService.find(account.pendingConferenceLeaves,
143				account, from.toBareJid()) != null) {
144			return null;
145		}
146		Conversation conversation = mXmppConnectionService
147				.findOrCreateConversation(account, from.toBareJid(), true);
148		if (packet.hasChild("subject")) {
149			conversation.getMucOptions().setSubject(
150					packet.findChild("subject").getContent());
151			mXmppConnectionService.updateConversationUi();
152			return null;
153		}
154		if (from.isBareJid()) {
155			return null;
156		}
157		if (from.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
158			if (mXmppConnectionService.markMessage(conversation,
159					packet.getId(), Message.STATUS_SEND)) {
160				return null;
161			} else if (packet.getId() == null) {
162				Message message = conversation.findSentMessageWithBody(packet.getBody());
163				if (message != null) {
164					mXmppConnectionService.markMessage(message,Message.STATUS_SEND_RECEIVED);
165					return null;
166				} else {
167					status = Message.STATUS_SEND;
168				}
169			} else {
170				status = Message.STATUS_SEND;
171			}
172		} else {
173			status = Message.STATUS_RECEIVED;
174		}
175		String pgpBody = getPgpBody(packet);
176		Message finishedMessage;
177		if (pgpBody == null) {
178			finishedMessage = new Message(conversation,
179					packet.getBody(), Message.ENCRYPTION_NONE, status);
180		} else {
181			finishedMessage = new Message(conversation, pgpBody,
182					Message.ENCRYPTION_PGP, status);
183		}
184		finishedMessage.setRemoteMsgId(packet.getId());
185		finishedMessage.markable = isMarkable(packet);
186		finishedMessage.setCounterpart(from);
187		if (status == Message.STATUS_RECEIVED) {
188			finishedMessage.setTrueCounterpart(conversation.getMucOptions()
189					.getTrueCounterpart(from.getResourcepart()));
190		}
191		if (packet.hasChild("delay")
192				&& conversation.hasDuplicateMessage(finishedMessage)) {
193			return null;
194		}
195		finishedMessage.setTime(getTimestamp(packet));
196		return finishedMessage;
197	}
198
199	private Message parseCarbonMessage(final MessagePacket packet, final Account account) {
200		int status;
201		final Jid fullJid;
202		Element forwarded;
203		if (packet.hasChild("received", "urn:xmpp:carbons:2")) {
204			forwarded = packet.findChild("received", "urn:xmpp:carbons:2")
205					.findChild("forwarded", "urn:xmpp:forward:0");
206			status = Message.STATUS_RECEIVED;
207		} else if (packet.hasChild("sent", "urn:xmpp:carbons:2")) {
208			forwarded = packet.findChild("sent", "urn:xmpp:carbons:2")
209					.findChild("forwarded", "urn:xmpp:forward:0");
210			status = Message.STATUS_SEND;
211		} else {
212			return null;
213		}
214		if (forwarded == null) {
215			return null;
216		}
217		Element message = forwarded.findChild("message");
218		if (message == null) {
219			return null;
220		}
221		if (!message.hasChild("body")) {
222			if (status == Message.STATUS_RECEIVED
223					&& message.getAttribute("from") != null) {
224				parseNonMessage(message, account);
225			} else if (status == Message.STATUS_SEND
226					&& message.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
227				final Jid to = message.getAttributeAsJid("to");
228				if (to != null) {
229					final Conversation conversation = mXmppConnectionService.find(
230							mXmppConnectionService.getConversations(), account,
231							to.toBareJid());
232					if (conversation != null) {
233						mXmppConnectionService.markRead(conversation, false);
234					}
235				}
236			}
237			return null;
238		}
239		if (status == Message.STATUS_RECEIVED) {
240			fullJid = message.getAttributeAsJid("from");
241			if (fullJid == null) {
242				return null;
243			} else {
244				updateLastseen(message, account, true);
245			}
246		} else {
247			fullJid = message.getAttributeAsJid("to");
248			if (fullJid == null) {
249				return null;
250			}
251		}
252		Conversation conversation = mXmppConnectionService
253				.findOrCreateConversation(account, fullJid.toBareJid(), false);
254		String pgpBody = getPgpBody(message);
255		Message finishedMessage;
256		if (pgpBody != null) {
257			finishedMessage = new Message(conversation, pgpBody,
258					Message.ENCRYPTION_PGP, status);
259		} else {
260			String body = message.findChild("body").getContent();
261			finishedMessage = new Message(conversation, body,
262					Message.ENCRYPTION_NONE, status);
263		}
264		finishedMessage.setTime(getTimestamp(message));
265		finishedMessage.setRemoteMsgId(message.getAttribute("id"));
266		finishedMessage.markable = isMarkable(message);
267		finishedMessage.setCounterpart(fullJid);
268		if (conversation.getMode() == Conversation.MODE_MULTI
269				&& !fullJid.isBareJid()) {
270			finishedMessage.setType(Message.TYPE_PRIVATE);
271			finishedMessage.setTrueCounterpart(conversation.getMucOptions()
272					.getTrueCounterpart(fullJid.getResourcepart()));
273			if (conversation.hasDuplicateMessage(finishedMessage)) {
274				return null;
275			}
276		}
277		return finishedMessage;
278	}
279
280	private void parseError(final MessagePacket packet, final Account account) {
281		final Jid from = packet.getFrom();
282		mXmppConnectionService.markMessage(account, from.toBareJid(),
283				packet.getId(), Message.STATUS_SEND_FAILED);
284	}
285
286	private void parseNonMessage(Element packet, Account account) {
287		final Jid from = packet.getAttributeAsJid("from");
288		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
289			Element event = packet.findChild("event",
290					"http://jabber.org/protocol/pubsub#event");
291			parseEvent(event, from, account);
292		} else if (from != null
293				&& packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
294			String id = packet
295					.findChild("displayed", "urn:xmpp:chat-markers:0")
296					.getAttribute("id");
297			updateLastseen(packet, account, true);
298			mXmppConnectionService.markMessage(account, from.toBareJid(),
299					id, Message.STATUS_SEND_DISPLAYED);
300		} else if (from != null
301				&& packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
302			String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
303					.getAttribute("id");
304			updateLastseen(packet, account, false);
305			mXmppConnectionService.markMessage(account, from.toBareJid(),
306					id, Message.STATUS_SEND_RECEIVED);
307		} else if (from != null
308				&& packet.hasChild("received", "urn:xmpp:receipts")) {
309			String id = packet.findChild("received", "urn:xmpp:receipts")
310					.getAttribute("id");
311			updateLastseen(packet, account, false);
312			mXmppConnectionService.markMessage(account, from.toBareJid(),
313					id, Message.STATUS_SEND_RECEIVED);
314		} else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
315			Element x = packet.findChild("x",
316					"http://jabber.org/protocol/muc#user");
317			if (x.hasChild("invite")) {
318				Conversation conversation = mXmppConnectionService
319						.findOrCreateConversation(account,
320								packet.getAttributeAsJid("from"), true);
321				if (!conversation.getMucOptions().online()) {
322					if (x.hasChild("password")) {
323						Element password = x.findChild("password");
324						conversation.getMucOptions().setPassword(
325								password.getContent());
326						mXmppConnectionService.databaseBackend
327								.updateConversation(conversation);
328					}
329					mXmppConnectionService.joinMuc(conversation);
330					mXmppConnectionService.updateConversationUi();
331				}
332			}
333		} else if (packet.hasChild("x", "jabber:x:conference")) {
334			Element x = packet.findChild("x", "jabber:x:conference");
335            Jid jid = x.getAttributeAsJid("jid");
336            String password = x.getAttribute("password");
337			if (jid != null) {
338				Conversation conversation = mXmppConnectionService
339						.findOrCreateConversation(account, jid, true);
340				if (!conversation.getMucOptions().online()) {
341					if (password != null) {
342						conversation.getMucOptions().setPassword(password);
343						mXmppConnectionService.databaseBackend
344								.updateConversation(conversation);
345					}
346					mXmppConnectionService.joinMuc(conversation);
347					mXmppConnectionService.updateConversationUi();
348				}
349			}
350		}
351	}
352
353	private void parseEvent(final Element event, final Jid from, final Account account) {
354		Element items = event.findChild("items");
355		if (items == null) {
356			return;
357		}
358		String node = items.getAttribute("node");
359		if (node == null) {
360			return;
361		}
362		if (node.equals("urn:xmpp:avatar:metadata")) {
363			Avatar avatar = Avatar.parseMetadata(items);
364			if (avatar != null) {
365				avatar.owner = from;
366				if (mXmppConnectionService.getFileBackend().isAvatarCached(
367						avatar)) {
368					if (account.getJid().toBareJid().equals(from)) {
369						if (account.setAvatar(avatar.getFilename())) {
370							mXmppConnectionService.databaseBackend
371									.updateAccount(account);
372						}
373						mXmppConnectionService.getAvatarService().clear(
374								account);
375						mXmppConnectionService.updateConversationUi();
376						mXmppConnectionService.updateAccountUi();
377					} else {
378						Contact contact = account.getRoster().getContact(
379								from);
380						contact.setAvatar(avatar.getFilename());
381						mXmppConnectionService.getAvatarService().clear(
382								contact);
383						mXmppConnectionService.updateConversationUi();
384						mXmppConnectionService.updateRosterUi();
385					}
386				} else {
387					mXmppConnectionService.fetchAvatar(account, avatar);
388				}
389			}
390		} else if (node.equals("http://jabber.org/protocol/nick")) {
391			Element item = items.findChild("item");
392			if (item != null) {
393				Element nick = item.findChild("nick",
394						"http://jabber.org/protocol/nick");
395				if (nick != null) {
396					if (from != null) {
397						Contact contact = account.getRoster().getContact(
398								from);
399						contact.setPresenceName(nick.getContent());
400						mXmppConnectionService.getAvatarService().clear(account);
401						mXmppConnectionService.updateConversationUi();
402						mXmppConnectionService.updateAccountUi();
403					}
404				}
405			}
406		}
407	}
408
409	private String getPgpBody(Element message) {
410		Element child = message.findChild("x", "jabber:x:encrypted");
411		if (child == null) {
412			return null;
413		} else {
414			return child.getContent();
415		}
416	}
417
418	private boolean isMarkable(Element message) {
419		return message.hasChild("markable", "urn:xmpp:chat-markers:0");
420	}
421
422	@Override
423	public void onMessagePacketReceived(Account account, MessagePacket packet) {
424		Message message = null;
425		this.parseNick(packet, account);
426
427		if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
428			if ((packet.getBody() != null)
429					&& (packet.getBody().startsWith("?OTR"))) {
430				message = this.parseOtrChat(packet, account);
431				if (message != null) {
432					message.markUnread();
433				}
434			} else if (packet.hasChild("body")
435					&& !(packet.hasChild("x",
436					"http://jabber.org/protocol/muc#user"))) {
437				message = this.parseChat(packet, account);
438				if (message != null) {
439					message.markUnread();
440				}
441			} else if (packet.hasChild("received", "urn:xmpp:carbons:2")
442					|| (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
443				message = this.parseCarbonMessage(packet, account);
444				if (message != null) {
445					if (message.getStatus() == Message.STATUS_SEND) {
446						account.activateGracePeriod();
447						mXmppConnectionService.markRead(
448								message.getConversation(), false);
449					} else {
450						message.markUnread();
451					}
452				}
453			} else {
454				parseNonMessage(packet, account);
455			}
456		} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
457			message = this.parseGroupchat(packet, account);
458			if (message != null) {
459				if (message.getStatus() == Message.STATUS_RECEIVED) {
460					message.markUnread();
461				} else {
462					mXmppConnectionService.markRead(message.getConversation(),
463							false);
464					account.activateGracePeriod();
465				}
466			}
467		} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
468			this.parseError(packet, account);
469			return;
470		} else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
471			this.parseHeadline(packet, account);
472			return;
473		}
474		if ((message == null) || (message.getBody() == null)) {
475			return;
476		}
477		if ((mXmppConnectionService.confirmMessages())
478				&& ((packet.getId() != null))) {
479			if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
480				MessagePacket receipt = mXmppConnectionService
481						.getMessageGenerator().received(account, packet,
482								"urn:xmpp:chat-markers:0");
483				mXmppConnectionService.sendMessagePacket(account, receipt);
484			}
485			if (packet.hasChild("request", "urn:xmpp:receipts")) {
486				MessagePacket receipt = mXmppConnectionService
487						.getMessageGenerator().received(account, packet,
488								"urn:xmpp:receipts");
489				mXmppConnectionService.sendMessagePacket(account, receipt);
490			}
491		}
492		Conversation conversation = message.getConversation();
493		conversation.add(message);
494
495		if (message.getStatus() == Message.STATUS_RECEIVED
496				&& conversation.getOtrSession() != null
497				&& !conversation.getOtrSession().getSessionID().getUserID()
498				.equals(message.getCounterpart().getResourcepart())) {
499			Log.d(Config.LOGTAG, "ending because of reasons");
500			conversation.endOtrIfNeeded();
501		}
502
503		if (packet.getType() != MessagePacket.TYPE_ERROR) {
504			if (message.getEncryption() == Message.ENCRYPTION_NONE
505					|| mXmppConnectionService.saveEncryptedMessages()) {
506				mXmppConnectionService.databaseBackend.createMessage(message);
507			}
508		}
509		if (message.trusted() && message.bodyContainsDownloadable()) {
510			this.mXmppConnectionService.getHttpConnectionManager()
511					.createNewConnection(message);
512		} else {
513			mXmppConnectionService.getNotificationService().push(message);
514		}
515		mXmppConnectionService.updateConversationUi();
516	}
517
518	private void parseHeadline(MessagePacket packet, Account account) {
519		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
520			Element event = packet.findChild("event",
521					"http://jabber.org/protocol/pubsub#event");
522			parseEvent(event, packet.getFrom(), account);
523		}
524	}
525
526	private void parseNick(MessagePacket packet, Account account) {
527		Element nick = packet.findChild("nick",
528				"http://jabber.org/protocol/nick");
529		if (nick != null) {
530			if (packet.getFrom() != null) {
531				Contact contact = account.getRoster().getContact(
532						packet.getFrom());
533				contact.setPresenceName(nick.getContent());
534			}
535		}
536	}
537}