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							mXmppConnectionService.updateConversationUi();
354							mXmppConnectionService.updateAccountUi();
355						} else {
356							Contact contact = account.getRoster().getContact(
357									from);
358							contact.setAvatar(avatar.getFilename());
359							mXmppConnectionService.getAvatarService().clear(
360									contact);
361							mXmppConnectionService.updateConversationUi();
362							mXmppConnectionService.updateRosterUi();
363						}
364					} else {
365						mXmppConnectionService.fetchAvatar(account, avatar);
366					}
367				}
368			} else if (node.equals("http://jabber.org/protocol/nick")) {
369				Element item = items.findChild("item");
370				if (item != null) {
371					Element nick = item.findChild("nick",
372							"http://jabber.org/protocol/nick");
373					if (nick != null) {
374						if (from != null) {
375							Contact contact = account.getRoster().getContact(
376									from);
377							contact.setPresenceName(nick.getContent());
378						}
379					}
380				}
381			}
382		}
383	}
384
385	private String getPgpBody(Element message) {
386		Element child = message.findChild("x", "jabber:x:encrypted");
387		if (child == null) {
388			return null;
389		} else {
390			return child.getContent();
391		}
392	}
393
394	private boolean isMarkable(Element message) {
395		return message.hasChild("markable", "urn:xmpp:chat-markers:0");
396	}
397
398	@Override
399	public void onMessagePacketReceived(Account account, MessagePacket packet) {
400		Message message = null;
401		boolean notify = mXmppConnectionService.getPreferences().getBoolean(
402				"show_notification", true);
403		boolean alwaysNotifyInConference = notify
404				&& mXmppConnectionService.getPreferences().getBoolean(
405						"always_notify_in_conference", false);
406
407		this.parseNick(packet, account);
408
409		if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
410			if ((packet.getBody() != null)
411					&& (packet.getBody().startsWith("?OTR"))) {
412				message = this.parseOtrChat(packet, account);
413				if (message != null) {
414					message.markUnread();
415				}
416			} else if (packet.hasChild("body")
417					&& !(packet.hasChild("x",
418							"http://jabber.org/protocol/muc#user"))) {
419				message = this.parseChat(packet, account);
420				if (message != null) {
421					message.markUnread();
422				}
423			} else if (packet.hasChild("received", "urn:xmpp:carbons:2")
424					|| (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
425				message = this.parseCarbonMessage(packet, account);
426				if (message != null) {
427					if (message.getStatus() == Message.STATUS_SEND) {
428						account.activateGracePeriod();
429						notify = false;
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					notify = alwaysNotifyInConference
445							|| NotificationService
446									.wasHighlightedOrPrivate(message);
447				} else {
448					mXmppConnectionService.markRead(message.getConversation(),
449							false);
450					account.activateGracePeriod();
451					notify = false;
452				}
453			}
454		} else if (packet.getType() == MessagePacket.TYPE_ERROR) {
455			this.parseError(packet, account);
456			return;
457		} else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
458			this.parseHeadline(packet, account);
459			return;
460		}
461		if ((message == null) || (message.getBody() == null)) {
462			return;
463		}
464		if ((mXmppConnectionService.confirmMessages())
465				&& ((packet.getId() != null))) {
466			if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
467				MessagePacket receipt = mXmppConnectionService
468						.getMessageGenerator().received(account, packet,
469								"urn:xmpp:chat-markers:0");
470				mXmppConnectionService.sendMessagePacket(account, receipt);
471			}
472			if (packet.hasChild("request", "urn:xmpp:receipts")) {
473				MessagePacket receipt = mXmppConnectionService
474						.getMessageGenerator().received(account, packet,
475								"urn:xmpp:receipts");
476				mXmppConnectionService.sendMessagePacket(account, receipt);
477			}
478		}
479		Conversation conversation = message.getConversation();
480		conversation.add(message);
481		if (packet.getType() != MessagePacket.TYPE_ERROR) {
482			if (message.getEncryption() == Message.ENCRYPTION_NONE
483					|| mXmppConnectionService.saveEncryptedMessages()) {
484				mXmppConnectionService.databaseBackend.createMessage(message);
485			}
486		}
487		if (message.bodyContainsDownloadable()) {
488			this.mXmppConnectionService.getHttpConnectionManager()
489					.createNewConnection(message);
490			notify = false;
491		}
492		notify = notify && !conversation.isMuted();
493		if (notify) {
494			mXmppConnectionService.getNotificationService().push(message);
495		}
496		mXmppConnectionService.updateConversationUi();
497	}
498
499	private void parseHeadline(MessagePacket packet, Account account) {
500		if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
501			Element event = packet.findChild("event",
502					"http://jabber.org/protocol/pubsub#event");
503			parseEvent(event, packet.getFrom(), account);
504		}
505	}
506
507	private void parseNick(MessagePacket packet, Account account) {
508		Element nick = packet.findChild("nick",
509				"http://jabber.org/protocol/nick");
510		if (nick != null) {
511			if (packet.getFrom() != null) {
512				Contact contact = account.getRoster().getContact(
513						packet.getFrom());
514				contact.setPresenceName(nick.getContent());
515			}
516		}
517	}
518}