1package eu.siacs.conversations.parser;
2
3import android.util.Log;
4
5import net.java.otr4j.session.Session;
6import net.java.otr4j.session.SessionStatus;
7
8import eu.siacs.conversations.Config;
9import eu.siacs.conversations.entities.Account;
10import eu.siacs.conversations.entities.Contact;
11import eu.siacs.conversations.entities.Conversation;
12import eu.siacs.conversations.entities.Message;
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.jid.Jid;
18import eu.siacs.conversations.xmpp.pep.Avatar;
19import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
20
21public class MessageParser extends AbstractParser implements
22 OnMessagePacketReceived {
23 public MessageParser(XmppConnectionService service) {
24 super(service);
25 }
26
27 private Message parseChat(MessagePacket packet, Account account) {
28 final Jid jid = packet.getFrom();
29 if (jid == null) {
30 return null;
31 }
32 Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, jid.toBareJid(), false);
33 updateLastseen(packet, account, true);
34 String pgpBody = getPgpBody(packet);
35 Message finishedMessage;
36 if (pgpBody != null) {
37 finishedMessage = new Message(conversation,
38 pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECEIVED);
39 } else {
40 finishedMessage = new Message(conversation,
41 packet.getBody(), Message.ENCRYPTION_NONE,
42 Message.STATUS_RECEIVED);
43 }
44 finishedMessage.setRemoteMsgId(packet.getId());
45 finishedMessage.markable = isMarkable(packet);
46 if (conversation.getMode() == Conversation.MODE_MULTI
47 && !jid.isBareJid()) {
48 finishedMessage.setType(Message.TYPE_PRIVATE);
49 finishedMessage.setTrueCounterpart(conversation.getMucOptions()
50 .getTrueCounterpart(jid.getResourcepart()));
51 if (conversation.hasDuplicateMessage(finishedMessage)) {
52 return null;
53 }
54
55 }
56 finishedMessage.setCounterpart(jid);
57 finishedMessage.setTime(getTimestamp(packet));
58 return finishedMessage;
59 }
60
61 private Message parseOtrChat(MessagePacket packet, Account account) {
62 boolean properlyAddressed = (!packet.getTo().isBareJid())
63 || (account.countPresences() == 1);
64 final Jid from = packet.getFrom();
65 if (from == null) {
66 return null;
67 }
68 Conversation conversation = mXmppConnectionService
69 .findOrCreateConversation(account, from.toBareJid(), false);
70 String presence;
71 if (from.isBareJid()) {
72 presence = "";
73 } else {
74 presence = from.getResourcepart();
75 }
76 updateLastseen(packet, account, true);
77 String body = packet.getBody();
78 if (body.matches("^\\?OTRv\\d*\\?")) {
79 conversation.endOtrIfNeeded();
80 }
81 if (!conversation.hasValidOtrSession()) {
82 if (properlyAddressed) {
83 conversation.startOtrSession(presence,false);
84 } else {
85 return null;
86 }
87 } else {
88 String foreignPresence = conversation.getOtrSession()
89 .getSessionID().getUserID();
90 if (!foreignPresence.equals(presence)) {
91 conversation.endOtrIfNeeded();
92 if (properlyAddressed) {
93 conversation.startOtrSession(presence, false);
94 } else {
95 return null;
96 }
97 }
98 }
99 try {
100 Session otrSession = conversation.getOtrSession();
101 SessionStatus before = otrSession.getSessionStatus();
102 body = otrSession.transformReceiving(body);
103 SessionStatus after = otrSession.getSessionStatus();
104 if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
105 mXmppConnectionService.onOtrSessionEstablished(conversation);
106 } else if ((before != after) && (after == SessionStatus.FINISHED)) {
107 conversation.resetOtrSession();
108 mXmppConnectionService.updateConversationUi();
109 }
110 if ((body == null) || (body.isEmpty())) {
111 return null;
112 }
113 if (body.startsWith(CryptoHelper.FILETRANSFER)) {
114 String key = body.substring(CryptoHelper.FILETRANSFER.length());
115 conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
116 return null;
117 }
118 Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR,
119 Message.STATUS_RECEIVED);
120 finishedMessage.setTime(getTimestamp(packet));
121 finishedMessage.setRemoteMsgId(packet.getId());
122 finishedMessage.markable = isMarkable(packet);
123 finishedMessage.setCounterpart(from);
124 return finishedMessage;
125 } catch (Exception e) {
126 conversation.resetOtrSession();
127 return null;
128 }
129 }
130
131 private Message parseGroupchat(MessagePacket packet, Account account) {
132 int status;
133 final Jid from = packet.getFrom();
134 if (from == null) {
135 return null;
136 }
137 if (mXmppConnectionService.find(account.pendingConferenceLeaves,
138 account, from.toBareJid()) != null) {
139 return null;
140 }
141 Conversation conversation = mXmppConnectionService
142 .findOrCreateConversation(account, from.toBareJid(), true);
143 if (packet.hasChild("subject")) {
144 conversation.getMucOptions().setSubject(
145 packet.findChild("subject").getContent());
146 mXmppConnectionService.updateConversationUi();
147 return null;
148 }
149 if (from.isBareJid()) {
150 return null;
151 }
152 if (from.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
153 if (mXmppConnectionService.markMessage(conversation,
154 packet.getId(), Message.STATUS_SEND)) {
155 return null;
156 } else if (packet.getId() == null) {
157 Message message = conversation.findSentMessageWithBody(packet.getBody());
158 if (message != null) {
159 mXmppConnectionService.markMessage(message,Message.STATUS_SEND_RECEIVED);
160 return null;
161 } else {
162 status = Message.STATUS_SEND;
163 }
164 } else {
165 status = Message.STATUS_SEND;
166 }
167 } else {
168 status = Message.STATUS_RECEIVED;
169 }
170 String pgpBody = getPgpBody(packet);
171 Message finishedMessage;
172 if (pgpBody == null) {
173 finishedMessage = new Message(conversation,
174 packet.getBody(), Message.ENCRYPTION_NONE, status);
175 } else {
176 finishedMessage = new Message(conversation, pgpBody,
177 Message.ENCRYPTION_PGP, status);
178 }
179 finishedMessage.setRemoteMsgId(packet.getId());
180 finishedMessage.markable = isMarkable(packet);
181 finishedMessage.setCounterpart(from);
182 if (status == Message.STATUS_RECEIVED) {
183 finishedMessage.setTrueCounterpart(conversation.getMucOptions()
184 .getTrueCounterpart(from.getResourcepart()));
185 }
186 if (packet.hasChild("delay")
187 && conversation.hasDuplicateMessage(finishedMessage)) {
188 return null;
189 }
190 finishedMessage.setTime(getTimestamp(packet));
191 return finishedMessage;
192 }
193
194 private Message parseCarbonMessage(final MessagePacket packet, final Account account) {
195 int status;
196 final Jid fullJid;
197 Element forwarded;
198 if (packet.hasChild("received", "urn:xmpp:carbons:2")) {
199 forwarded = packet.findChild("received", "urn:xmpp:carbons:2")
200 .findChild("forwarded", "urn:xmpp:forward:0");
201 status = Message.STATUS_RECEIVED;
202 } else if (packet.hasChild("sent", "urn:xmpp:carbons:2")) {
203 forwarded = packet.findChild("sent", "urn:xmpp:carbons:2")
204 .findChild("forwarded", "urn:xmpp:forward:0");
205 status = Message.STATUS_SEND;
206 } else {
207 return null;
208 }
209 if (forwarded == null) {
210 return null;
211 }
212 Element message = forwarded.findChild("message");
213 if (message == null) {
214 return null;
215 }
216 if (!message.hasChild("body")) {
217 if (status == Message.STATUS_RECEIVED
218 && message.getAttribute("from") != null) {
219 parseNonMessage(message, account);
220 } else if (status == Message.STATUS_SEND
221 && message.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
222 final Jid to = message.getAttributeAsJid("to");
223 if (to != null) {
224 final Conversation conversation = mXmppConnectionService.find(
225 mXmppConnectionService.getConversations(), account,
226 to.toBareJid());
227 if (conversation != null) {
228 mXmppConnectionService.markRead(conversation, false);
229 }
230 }
231 }
232 return null;
233 }
234 if (status == Message.STATUS_RECEIVED) {
235 fullJid = message.getAttributeAsJid("from");
236 if (fullJid == null) {
237 return null;
238 } else {
239 updateLastseen(message, account, true);
240 }
241 } else {
242 fullJid = message.getAttributeAsJid("to");
243 if (fullJid == null) {
244 return null;
245 }
246 }
247 Conversation conversation = mXmppConnectionService
248 .findOrCreateConversation(account, fullJid.toBareJid(), false);
249 String pgpBody = getPgpBody(message);
250 Message finishedMessage;
251 if (pgpBody != null) {
252 finishedMessage = new Message(conversation, pgpBody,
253 Message.ENCRYPTION_PGP, status);
254 } else {
255 String body = message.findChild("body").getContent();
256 finishedMessage = new Message(conversation, body,
257 Message.ENCRYPTION_NONE, status);
258 }
259 finishedMessage.setTime(getTimestamp(message));
260 finishedMessage.setRemoteMsgId(message.getAttribute("id"));
261 finishedMessage.markable = isMarkable(message);
262 finishedMessage.setCounterpart(fullJid);
263 if (conversation.getMode() == Conversation.MODE_MULTI
264 && !fullJid.isBareJid()) {
265 finishedMessage.setType(Message.TYPE_PRIVATE);
266 finishedMessage.setTrueCounterpart(conversation.getMucOptions()
267 .getTrueCounterpart(fullJid.getResourcepart()));
268 if (conversation.hasDuplicateMessage(finishedMessage)) {
269 return null;
270 }
271 }
272 return finishedMessage;
273 }
274
275 private Message parseMamMessage(MessagePacket packet, final Account account) {
276 final Element result = packet.findChild("result","urn:xmpp:mam:0");
277 if (result == null ) {
278 return null;
279 }
280 final Element forwarded = result.findChild("forwarded","urn:xmpp:forward:0");
281 if (forwarded == null) {
282 return null;
283 }
284 final Element message = forwarded.findChild("message");
285 if (message == null) {
286 return null;
287 }
288 final Element body = message.findChild("body");
289 if (body == null || message.hasChild("private","urn:xmpp:carbons:2") || message.hasChild("no-copy","urn:xmpp:hints")) {
290 return null;
291 }
292 int encryption;
293 String content = getPgpBody(message);
294 if (content != null) {
295 encryption = Message.ENCRYPTION_PGP;
296 } else {
297 encryption = Message.ENCRYPTION_NONE;
298 content = body.getContent();
299 }
300 if (content == null) {
301 return null;
302 }
303 final long timestamp = getTimestamp(forwarded);
304 final Jid to = message.getAttributeAsJid("to");
305 final Jid from = message.getAttributeAsJid("from");
306 Jid counterpart;
307 int status;
308 Conversation conversation;
309 if (from!=null && to != null && from.toBareJid().equals(account.getJid().toBareJid())) {
310 status = Message.STATUS_SEND;
311 conversation = this.mXmppConnectionService.findOrCreateConversation(account,to.toBareJid(),false);
312 counterpart = to;
313 } else if (from !=null && to != null) {
314 status = Message.STATUS_RECEIVED;
315 conversation = this.mXmppConnectionService.findOrCreateConversation(account,from.toBareJid(),false);
316 counterpart = from;
317 } else {
318 return null;
319 }
320 Message finishedMessage = new Message(conversation,content,encryption,status);
321 finishedMessage.setTime(timestamp);
322 finishedMessage.setCounterpart(counterpart);
323 finishedMessage.setRemoteMsgId(message.getAttribute("id"));
324 finishedMessage.setServerMsgId(result.getAttribute("id"));
325 if (conversation.hasDuplicateMessage(finishedMessage)) {
326 Log.d(Config.LOGTAG, "received mam message " + content+ " (duplicate)");
327 return null;
328 } else {
329 Log.d(Config.LOGTAG, "received mam message " + content);
330 }
331 return finishedMessage;
332 }
333
334 private void parseError(final MessagePacket packet, final Account account) {
335 final Jid from = packet.getFrom();
336 mXmppConnectionService.markMessage(account, from.toBareJid(),
337 packet.getId(), Message.STATUS_SEND_FAILED);
338 }
339
340 private void parseNonMessage(Element packet, Account account) {
341 final Jid from = packet.getAttributeAsJid("from");
342 if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
343 Element event = packet.findChild("event",
344 "http://jabber.org/protocol/pubsub#event");
345 parseEvent(event, from, account);
346 } else if (from != null
347 && packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
348 String id = packet
349 .findChild("displayed", "urn:xmpp:chat-markers:0")
350 .getAttribute("id");
351 updateLastseen(packet, account, true);
352 mXmppConnectionService.markMessage(account, from.toBareJid(),
353 id, Message.STATUS_SEND_DISPLAYED);
354 } else if (from != null
355 && packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
356 String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
357 .getAttribute("id");
358 updateLastseen(packet, account, false);
359 mXmppConnectionService.markMessage(account, from.toBareJid(),
360 id, Message.STATUS_SEND_RECEIVED);
361 } else if (from != null
362 && packet.hasChild("received", "urn:xmpp:receipts")) {
363 String id = packet.findChild("received", "urn:xmpp:receipts")
364 .getAttribute("id");
365 updateLastseen(packet, account, false);
366 mXmppConnectionService.markMessage(account, from.toBareJid(),
367 id, Message.STATUS_SEND_RECEIVED);
368 } else if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
369 Element x = packet.findChild("x",
370 "http://jabber.org/protocol/muc#user");
371 if (x.hasChild("invite")) {
372 Conversation conversation = mXmppConnectionService
373 .findOrCreateConversation(account,
374 packet.getAttributeAsJid("from"), true);
375 if (!conversation.getMucOptions().online()) {
376 if (x.hasChild("password")) {
377 Element password = x.findChild("password");
378 conversation.getMucOptions().setPassword(
379 password.getContent());
380 mXmppConnectionService.databaseBackend
381 .updateConversation(conversation);
382 }
383 mXmppConnectionService.joinMuc(conversation);
384 mXmppConnectionService.updateConversationUi();
385 }
386 }
387 } else if (packet.hasChild("x", "jabber:x:conference")) {
388 Element x = packet.findChild("x", "jabber:x:conference");
389 Jid jid = x.getAttributeAsJid("jid");
390 String password = x.getAttribute("password");
391 if (jid != null) {
392 Conversation conversation = mXmppConnectionService
393 .findOrCreateConversation(account, jid, true);
394 if (!conversation.getMucOptions().online()) {
395 if (password != null) {
396 conversation.getMucOptions().setPassword(password);
397 mXmppConnectionService.databaseBackend
398 .updateConversation(conversation);
399 }
400 mXmppConnectionService.joinMuc(conversation);
401 mXmppConnectionService.updateConversationUi();
402 }
403 }
404 }
405 }
406
407 private void parseEvent(final Element event, final Jid from, final Account account) {
408 Element items = event.findChild("items");
409 if (items == null) {
410 return;
411 }
412 String node = items.getAttribute("node");
413 if (node == null) {
414 return;
415 }
416 if (node.equals("urn:xmpp:avatar:metadata")) {
417 Avatar avatar = Avatar.parseMetadata(items);
418 if (avatar != null) {
419 avatar.owner = from;
420 if (mXmppConnectionService.getFileBackend().isAvatarCached(
421 avatar)) {
422 if (account.getJid().toBareJid().equals(from)) {
423 if (account.setAvatar(avatar.getFilename())) {
424 mXmppConnectionService.databaseBackend
425 .updateAccount(account);
426 }
427 mXmppConnectionService.getAvatarService().clear(
428 account);
429 mXmppConnectionService.updateConversationUi();
430 mXmppConnectionService.updateAccountUi();
431 } else {
432 Contact contact = account.getRoster().getContact(
433 from);
434 contact.setAvatar(avatar.getFilename());
435 mXmppConnectionService.getAvatarService().clear(
436 contact);
437 mXmppConnectionService.updateConversationUi();
438 mXmppConnectionService.updateRosterUi();
439 }
440 } else {
441 mXmppConnectionService.fetchAvatar(account, avatar);
442 }
443 }
444 } else if (node.equals("http://jabber.org/protocol/nick")) {
445 Element item = items.findChild("item");
446 if (item != null) {
447 Element nick = item.findChild("nick",
448 "http://jabber.org/protocol/nick");
449 if (nick != null) {
450 if (from != null) {
451 Contact contact = account.getRoster().getContact(
452 from);
453 contact.setPresenceName(nick.getContent());
454 mXmppConnectionService.getAvatarService().clear(account);
455 mXmppConnectionService.updateConversationUi();
456 mXmppConnectionService.updateAccountUi();
457 }
458 }
459 }
460 }
461 }
462
463 private String getPgpBody(Element message) {
464 Element child = message.findChild("x", "jabber:x:encrypted");
465 if (child == null) {
466 return null;
467 } else {
468 return child.getContent();
469 }
470 }
471
472 private boolean isMarkable(Element message) {
473 return message.hasChild("markable", "urn:xmpp:chat-markers:0");
474 }
475
476 @Override
477 public void onMessagePacketReceived(Account account, MessagePacket packet) {
478 Message message = null;
479 this.parseNick(packet, account);
480
481 if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
482 if ((packet.getBody() != null)
483 && (packet.getBody().startsWith("?OTR"))) {
484 message = this.parseOtrChat(packet, account);
485 if (message != null) {
486 message.markUnread();
487 }
488 } else if (packet.hasChild("body")
489 && !(packet.hasChild("x",
490 "http://jabber.org/protocol/muc#user"))) {
491 message = this.parseChat(packet, account);
492 if (message != null) {
493 message.markUnread();
494 }
495 } else if (packet.hasChild("received", "urn:xmpp:carbons:2")
496 || (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
497 message = this.parseCarbonMessage(packet, account);
498 if (message != null) {
499 if (message.getStatus() == Message.STATUS_SEND) {
500 account.activateGracePeriod();
501 mXmppConnectionService.markRead(
502 message.getConversation(), false);
503 } else {
504 message.markUnread();
505 }
506 }
507 } else if (packet.hasChild("result","urn:xmpp:mam:0")) {
508 message = parseMamMessage(packet, account);
509 if (message != null) {
510 Conversation conversation = message.getConversation();
511 conversation.add(message);
512 mXmppConnectionService.databaseBackend.createMessage(message);
513 }
514 return;
515 } else if (packet.hasChild("fin","urn:xmpp:mam:0")) {
516 Element fin = packet.findChild("fin","urn:xmpp:mam:0");
517 mXmppConnectionService.getMessageArchiveService().processFin(fin);
518 } else {
519 parseNonMessage(packet, account);
520 }
521 } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
522 message = this.parseGroupchat(packet, account);
523 if (message != null) {
524 if (message.getStatus() == Message.STATUS_RECEIVED) {
525 message.markUnread();
526 } else {
527 mXmppConnectionService.markRead(message.getConversation(),
528 false);
529 account.activateGracePeriod();
530 }
531 }
532 } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
533 this.parseError(packet, account);
534 return;
535 } else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
536 this.parseHeadline(packet, account);
537 return;
538 }
539 if ((message == null) || (message.getBody() == null)) {
540 return;
541 }
542 if ((mXmppConnectionService.confirmMessages())
543 && ((packet.getId() != null))) {
544 if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
545 MessagePacket receipt = mXmppConnectionService
546 .getMessageGenerator().received(account, packet,
547 "urn:xmpp:chat-markers:0");
548 mXmppConnectionService.sendMessagePacket(account, receipt);
549 }
550 if (packet.hasChild("request", "urn:xmpp:receipts")) {
551 MessagePacket receipt = mXmppConnectionService
552 .getMessageGenerator().received(account, packet,
553 "urn:xmpp:receipts");
554 mXmppConnectionService.sendMessagePacket(account, receipt);
555 }
556 }
557 Conversation conversation = message.getConversation();
558 conversation.add(message);
559 if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().advancedStreamFeaturesLoaded()) {
560 if (conversation.setLastMessageTransmitted(System.currentTimeMillis())) {
561 mXmppConnectionService.updateConversation(conversation);
562 }
563 }
564
565 if (message.getStatus() == Message.STATUS_RECEIVED
566 && conversation.getOtrSession() != null
567 && !conversation.getOtrSession().getSessionID().getUserID()
568 .equals(message.getCounterpart().getResourcepart())) {
569 conversation.endOtrIfNeeded();
570 }
571
572 if (packet.getType() != MessagePacket.TYPE_ERROR) {
573 if (message.getEncryption() == Message.ENCRYPTION_NONE
574 || mXmppConnectionService.saveEncryptedMessages()) {
575 mXmppConnectionService.databaseBackend.createMessage(message);
576 }
577 }
578 if (message.trusted() && message.bodyContainsDownloadable()) {
579 this.mXmppConnectionService.getHttpConnectionManager()
580 .createNewConnection(message);
581 } else if (!message.isRead()) {
582 mXmppConnectionService.getNotificationService().push(message);
583 }
584 mXmppConnectionService.updateConversationUi();
585 }
586
587 private void parseHeadline(MessagePacket packet, Account account) {
588 if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
589 Element event = packet.findChild("event",
590 "http://jabber.org/protocol/pubsub#event");
591 parseEvent(event, packet.getFrom(), account);
592 }
593 }
594
595 private void parseNick(MessagePacket packet, Account account) {
596 Element nick = packet.findChild("nick",
597 "http://jabber.org/protocol/nick");
598 if (nick != null) {
599 if (packet.getFrom() != null) {
600 Contact contact = account.getRoster().getContact(
601 packet.getFrom());
602 contact.setPresenceName(nick.getContent());
603 }
604 }
605 }
606}