MessageParser.java

  1package eu.siacs.conversations.parser;
  2
  3import net.java.otr4j.session.Session;
  4import net.java.otr4j.session.SessionStatus;
  5import eu.siacs.conversations.entities.Account;
  6import eu.siacs.conversations.entities.Contact;
  7import eu.siacs.conversations.entities.Conversation;
  8import eu.siacs.conversations.entities.Message;
  9import eu.siacs.conversations.services.NotificationService;
 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
260		return finishedMessage;
261	}
262
263	private void parseError(MessagePacket packet, Account account) {
264		String[] fromParts = packet.getFrom().split("/", 2);
265		mXmppConnectionService.markMessage(account, fromParts[0],
266				packet.getId(), Message.STATUS_SEND_FAILED);
267	}
268
269	private void parseNonMessage(Element packet, Account account) {
270		String from = packet.getAttribute("from");
271		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
272			Element event = packet.findChild("event",
273					"http://jabber.org/protocol/pubsub#event");
274			parseEvent(event, packet.getAttribute("from"), account);
275		} else if (from != null
276				&& packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
277			String id = packet
278					.findChild("displayed", "urn:xmpp:chat-markers:0")
279					.getAttribute("id");
280			updateLastseen(packet, account, true);
281			mXmppConnectionService.markMessage(account, from.split("/", 2)[0],
282					id, Message.STATUS_SEND_DISPLAYED);
283		} else if (from != null
284				&& packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
285			String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
286					.getAttribute("id");
287			updateLastseen(packet, account, false);
288			mXmppConnectionService.markMessage(account, from.split("/", 2)[0],
289					id, Message.STATUS_SEND_RECEIVED);
290		} else if (from != null
291				&& packet.hasChild("received", "urn:xmpp:receipts")) {
292			String id = packet.findChild("received", "urn:xmpp:receipts")
293					.getAttribute("id");
294			updateLastseen(packet, account, false);
295			mXmppConnectionService.markMessage(account, from.split("/", 2)[0],
296					id, Message.STATUS_SEND_RECEIVED);
297		} else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
298			Element x = packet.findChild("x",
299					"http://jabber.org/protocol/muc#user");
300			if (x.hasChild("invite")) {
301				Conversation conversation = mXmppConnectionService
302						.findOrCreateConversation(account,
303								packet.getAttribute("from"), true);
304				if (!conversation.getMucOptions().online()) {
305					if (x.hasChild("password")) {
306						Element password = x.findChild("password");
307						conversation.getMucOptions().setPassword(
308								password.getContent());
309						mXmppConnectionService.databaseBackend
310								.updateConversation(conversation);
311					}
312					mXmppConnectionService.joinMuc(conversation);
313					mXmppConnectionService.updateConversationUi();
314				}
315			}
316		} else if (packet.hasChild("x", "jabber:x:conference")) {
317			Element x = packet.findChild("x", "jabber:x:conference");
318			String jid = x.getAttribute("jid");
319			String password = x.getAttribute("password");
320			if (jid != null) {
321				Conversation conversation = mXmppConnectionService
322						.findOrCreateConversation(account, jid, true);
323				if (!conversation.getMucOptions().online()) {
324					if (password != null) {
325						conversation.getMucOptions().setPassword(password);
326						mXmppConnectionService.databaseBackend
327								.updateConversation(conversation);
328					}
329					mXmppConnectionService.joinMuc(conversation);
330					mXmppConnectionService.updateConversationUi();
331				}
332			}
333		}
334	}
335
336	private void parseEvent(Element event, String from, Account account) {
337		Element items = event.findChild("items");
338		String node = items.getAttribute("node");
339		if (node != null) {
340			if (node.equals("urn:xmpp:avatar:metadata")) {
341				Avatar avatar = Avatar.parseMetadata(items);
342				if (avatar != null) {
343					avatar.owner = from;
344					if (mXmppConnectionService.getFileBackend().isAvatarCached(
345							avatar)) {
346						if (account.getJid().equals(from)) {
347							if (account.setAvatar(avatar.getFilename())) {
348								mXmppConnectionService.databaseBackend
349										.updateAccount(account);
350							}
351						} else {
352							Contact contact = account.getRoster().getContact(
353									from);
354							contact.setAvatar(avatar.getFilename());
355						}
356					} else {
357						mXmppConnectionService.fetchAvatar(account, avatar);
358					}
359				}
360			} else if (node.equals("http://jabber.org/protocol/nick")) {
361				Element item = items.findChild("item");
362				if (item != null) {
363					Element nick = item.findChild("nick",
364							"http://jabber.org/protocol/nick");
365					if (nick != null) {
366						if (from != null) {
367							Contact contact = account.getRoster().getContact(
368									from);
369							contact.setPresenceName(nick.getContent());
370						}
371					}
372				}
373			}
374		}
375	}
376
377	private String getPgpBody(Element message) {
378		Element child = message.findChild("x", "jabber:x:encrypted");
379		if (child == null) {
380			return null;
381		} else {
382			return child.getContent();
383		}
384	}
385
386	private boolean isMarkable(Element message) {
387		return message.hasChild("markable", "urn:xmpp:chat-markers:0");
388	}
389
390	@Override
391	public void onMessagePacketReceived(Account account, MessagePacket packet) {
392		Message message = null;
393		boolean notify = mXmppConnectionService.getPreferences().getBoolean(
394				"show_notification", true);
395		boolean alwaysNotifyInConference = notify
396				&& mXmppConnectionService.getPreferences().getBoolean(
397						"always_notify_in_conference", false);
398
399		this.parseNick(packet, account);
400
401		if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
402			if ((packet.getBody() != null)
403					&& (packet.getBody().startsWith("?OTR"))) {
404				message = this.parseOtrChat(packet, account);
405				if (message != null) {
406					message.markUnread();
407				}
408			} else if (packet.hasChild("body")
409					&& !(packet.hasChild("x",
410							"http://jabber.org/protocol/muc#user"))) {
411				message = this.parseChat(packet, account);
412				if (message != null) {
413					message.markUnread();
414				}
415			} else if (packet.hasChild("received", "urn:xmpp:carbons:2")
416					|| (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
417				message = this.parseCarbonMessage(packet, account);
418				if (message != null) {
419					if (message.getStatus() == Message.STATUS_SEND) {
420						account.activateGracePeriod();
421						notify = false;
422						mXmppConnectionService.markRead(
423								message.getConversation(), false);
424					} else {
425						message.markUnread();
426					}
427				}
428			} else {
429				parseNonMessage(packet, account);
430			}
431		} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
432			message = this.parseGroupchat(packet, account);
433			if (message != null) {
434				if (message.getStatus() == Message.STATUS_RECEIVED) {
435					message.markUnread();
436					notify = alwaysNotifyInConference
437							|| NotificationService
438									.wasHighlightedOrPrivate(message);
439				} else {
440					mXmppConnectionService.markRead(message.getConversation(),
441							false);
442					account.activateGracePeriod();
443					notify = false;
444				}
445			}
446		} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
447			this.parseError(packet, account);
448			return;
449		} else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
450			this.parseHeadline(packet, account);
451			return;
452		}
453		if ((message == null) || (message.getBody() == null)) {
454			return;
455		}
456		if ((mXmppConnectionService.confirmMessages())
457				&& ((packet.getId() != null))) {
458			if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
459				MessagePacket receipt = mXmppConnectionService
460						.getMessageGenerator().received(account, packet,
461								"urn:xmpp:chat-markers:0");
462				mXmppConnectionService.sendMessagePacket(account, receipt);
463			}
464			if (packet.hasChild("request", "urn:xmpp:receipts")) {
465				MessagePacket receipt = mXmppConnectionService
466						.getMessageGenerator().received(account, packet,
467								"urn:xmpp:receipts");
468				mXmppConnectionService.sendMessagePacket(account, receipt);
469			}
470		}
471		Conversation conversation = message.getConversation();
472		conversation.add(message);
473		if (packet.getType() != MessagePacket.TYPE_ERROR) {
474			if (message.getEncryption() == Message.ENCRYPTION_NONE
475					|| mXmppConnectionService.saveEncryptedMessages()) {
476				mXmppConnectionService.databaseBackend.createMessage(message);
477			}
478		}
479		if (message.bodyContainsDownloadable()) {
480			this.mXmppConnectionService.getHttpConnectionManager()
481					.createNewConnection(message);
482		}
483		notify = notify && !conversation.isMuted();
484		if (notify) {
485			mXmppConnectionService.getNotificationService().push(message);
486		}
487		mXmppConnectionService.updateConversationUi();
488	}
489
490	private void parseHeadline(MessagePacket packet, Account account) {
491		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
492			Element event = packet.findChild("event",
493					"http://jabber.org/protocol/pubsub#event");
494			parseEvent(event, packet.getFrom(), account);
495		}
496	}
497
498	private void parseNick(MessagePacket packet, Account account) {
499		Element nick = packet.findChild("nick",
500				"http://jabber.org/protocol/nick");
501		if (nick != null) {
502			if (packet.getFrom() != null) {
503				Contact contact = account.getRoster().getContact(
504						packet.getFrom());
505				contact.setPresenceName(nick.getContent());
506			}
507		}
508	}
509}