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							mXmppConnectionService.getAvatarService().clear(
352									account);
353						} else {
354							Contact contact = account.getRoster().getContact(
355									from);
356							contact.setAvatar(avatar.getFilename());
357							mXmppConnectionService.getAvatarService().clear(
358									contact);
359						}
360					} else {
361						mXmppConnectionService.fetchAvatar(account, avatar);
362					}
363				}
364			} else if (node.equals("http://jabber.org/protocol/nick")) {
365				Element item = items.findChild("item");
366				if (item != null) {
367					Element nick = item.findChild("nick",
368							"http://jabber.org/protocol/nick");
369					if (nick != null) {
370						if (from != null) {
371							Contact contact = account.getRoster().getContact(
372									from);
373							contact.setPresenceName(nick.getContent());
374						}
375					}
376				}
377			}
378		}
379	}
380
381	private String getPgpBody(Element message) {
382		Element child = message.findChild("x", "jabber:x:encrypted");
383		if (child == null) {
384			return null;
385		} else {
386			return child.getContent();
387		}
388	}
389
390	private boolean isMarkable(Element message) {
391		return message.hasChild("markable", "urn:xmpp:chat-markers:0");
392	}
393
394	@Override
395	public void onMessagePacketReceived(Account account, MessagePacket packet) {
396		Message message = null;
397		boolean notify = mXmppConnectionService.getPreferences().getBoolean(
398				"show_notification", true);
399		boolean alwaysNotifyInConference = notify
400				&& mXmppConnectionService.getPreferences().getBoolean(
401						"always_notify_in_conference", false);
402
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						notify = false;
426						mXmppConnectionService.markRead(
427								message.getConversation(), false);
428					} else {
429						message.markUnread();
430					}
431				}
432			} else {
433				parseNonMessage(packet, account);
434			}
435		} else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
436			message = this.parseGroupchat(packet, account);
437			if (message != null) {
438				if (message.getStatus() == Message.STATUS_RECEIVED) {
439					message.markUnread();
440					notify = alwaysNotifyInConference
441							|| NotificationService
442									.wasHighlightedOrPrivate(message);
443				} else {
444					mXmppConnectionService.markRead(message.getConversation(),
445							false);
446					account.activateGracePeriod();
447					notify = false;
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		if (packet.getType() != MessagePacket.TYPE_ERROR) {
478			if (message.getEncryption() == Message.ENCRYPTION_NONE
479					|| mXmppConnectionService.saveEncryptedMessages()) {
480				mXmppConnectionService.databaseBackend.createMessage(message);
481			}
482		}
483		if (message.bodyContainsDownloadable()) {
484			this.mXmppConnectionService.getHttpConnectionManager()
485					.createNewConnection(message);
486		}
487		notify = notify && !conversation.isMuted();
488		if (notify) {
489			mXmppConnectionService.getNotificationService().push(message);
490		}
491		mXmppConnectionService.updateConversationUi();
492	}
493
494	private void parseHeadline(MessagePacket packet, Account account) {
495		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
496			Element event = packet.findChild("event",
497					"http://jabber.org/protocol/pubsub#event");
498			parseEvent(event, packet.getFrom(), account);
499		}
500	}
501
502	private void parseNick(MessagePacket packet, Account account) {
503		Element nick = packet.findChild("nick",
504				"http://jabber.org/protocol/nick");
505		if (nick != null) {
506			if (packet.getFrom() != null) {
507				Contact contact = account.getRoster().getContact(
508						packet.getFrom());
509				contact.setPresenceName(nick.getContent());
510			}
511		}
512	}
513}