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