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