MessageParser.java

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