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