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.NotificationService;
11import eu.siacs.conversations.services.XmppConnectionService;
12import eu.siacs.conversations.utils.CryptoHelper;
13import eu.siacs.conversations.xml.Element;
14import eu.siacs.conversations.xmpp.OnMessagePacketReceived;
15import eu.siacs.conversations.xmpp.pep.Avatar;
16import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
17
18public class MessageParser extends AbstractParser implements
19 OnMessagePacketReceived {
20 public MessageParser(XmppConnectionService service) {
21 super(service);
22 }
23
24 private Message parseChat(MessagePacket packet, Account account) {
25 String[] fromParts = packet.getFrom().split("/", 2);
26 Conversation conversation = mXmppConnectionService
27 .findOrCreateConversation(account, fromParts[0], false);
28 updateLastseen(packet, account, true);
29 String pgpBody = getPgpBody(packet);
30 Message finishedMessage;
31 if (pgpBody != null) {
32 finishedMessage = new Message(conversation, packet.getFrom(),
33 pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECEIVED);
34 } else {
35 finishedMessage = new Message(conversation, packet.getFrom(),
36 packet.getBody(), Message.ENCRYPTION_NONE,
37 Message.STATUS_RECEIVED);
38 }
39 finishedMessage.setRemoteMsgId(packet.getId());
40 finishedMessage.markable = isMarkable(packet);
41 if (conversation.getMode() == Conversation.MODE_MULTI
42 && fromParts.length >= 2) {
43 finishedMessage.setType(Message.TYPE_PRIVATE);
44 finishedMessage.setPresence(fromParts[1]);
45 finishedMessage.setTrueCounterpart(conversation.getMucOptions()
46 .getTrueCounterpart(fromParts[1]));
47 if (conversation.hasDuplicateMessage(finishedMessage)) {
48 return null;
49 }
50
51 }
52 finishedMessage.setTime(getTimestamp(packet));
53 return finishedMessage;
54 }
55
56 private Message parseOtrChat(MessagePacket packet, Account account) {
57 boolean properlyAddressed = (packet.getTo().split("/", 2).length == 2)
58 || (account.countPresences() == 1);
59 String[] fromParts = packet.getFrom().split("/", 2);
60 Conversation conversation = mXmppConnectionService
61 .findOrCreateConversation(account, fromParts[0], false);
62 String presence;
63 if (fromParts.length >= 2) {
64 presence = fromParts[1];
65 } else {
66 presence = "";
67 }
68 updateLastseen(packet, account, true);
69 String body = packet.getBody();
70 if (body.matches("^\\?OTRv\\d*\\?")) {
71 conversation.endOtrIfNeeded();
72 }
73 if (!conversation.hasValidOtrSession()) {
74 if (properlyAddressed) {
75 conversation.startOtrSession(mXmppConnectionService, presence,
76 false);
77 } else {
78 return null;
79 }
80 } else {
81 String foreignPresence = conversation.getOtrSession()
82 .getSessionID().getUserID();
83 if (!foreignPresence.equals(presence)) {
84 conversation.endOtrIfNeeded();
85 if (properlyAddressed) {
86 conversation.startOtrSession(mXmppConnectionService,
87 presence, false);
88 } else {
89 return null;
90 }
91 }
92 }
93 try {
94 Session otrSession = conversation.getOtrSession();
95 SessionStatus before = otrSession.getSessionStatus();
96 body = otrSession.transformReceiving(body);
97 SessionStatus after = otrSession.getSessionStatus();
98 if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
99 mXmppConnectionService.onOtrSessionEstablished(conversation);
100 } else if ((before != after) && (after == SessionStatus.FINISHED)) {
101 conversation.resetOtrSession();
102 mXmppConnectionService.updateConversationUi();
103 }
104 if ((body == null) || (body.isEmpty())) {
105 return null;
106 }
107 if (body.startsWith(CryptoHelper.FILETRANSFER)) {
108 String key = body.substring(CryptoHelper.FILETRANSFER.length());
109 conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
110 return null;
111 }
112 Message finishedMessage = new Message(conversation,
113 packet.getFrom(), body, Message.ENCRYPTION_OTR,
114 Message.STATUS_RECEIVED);
115 finishedMessage.setTime(getTimestamp(packet));
116 finishedMessage.setRemoteMsgId(packet.getId());
117 finishedMessage.markable = isMarkable(packet);
118 return finishedMessage;
119 } catch (Exception e) {
120 String receivedId = packet.getId();
121 if (receivedId != null) {
122 mXmppConnectionService.replyWithNotAcceptable(account, packet);
123 }
124 conversation.resetOtrSession();
125 return null;
126 }
127 }
128
129 private Message parseGroupchat(MessagePacket packet, Account account) {
130 int status;
131 String[] fromParts = packet.getFrom().split("/", 2);
132 if (mXmppConnectionService.find(account.pendingConferenceLeaves,
133 account, fromParts[0]) != null) {
134 return null;
135 }
136 Conversation conversation = mXmppConnectionService
137 .findOrCreateConversation(account, fromParts[0], true);
138 if (packet.hasChild("subject")) {
139 conversation.getMucOptions().setSubject(
140 packet.findChild("subject").getContent());
141 mXmppConnectionService.updateConversationUi();
142 return null;
143 }
144 if ((fromParts.length == 1)) {
145 return null;
146 }
147 String counterPart = fromParts[1];
148 if (counterPart.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, counterPart,
162 packet.getBody(), Message.ENCRYPTION_NONE, status);
163 } else {
164 finishedMessage = new Message(conversation, counterPart, 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(counterPart));
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(MessagePacket packet, Account account) {
182 int status;
183 String 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 String to = message.getAttribute("to");
210 if (to != null) {
211 Conversation conversation = mXmppConnectionService.find(
212 mXmppConnectionService.getConversations(), account,
213 to.split("/")[0]);
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.getAttribute("from");
223 if (fullJid == null) {
224 return null;
225 } else {
226 updateLastseen(message, account, true);
227 }
228 } else {
229 fullJid = message.getAttribute("to");
230 if (fullJid == null) {
231 return null;
232 }
233 }
234 String[] parts = fullJid.split("/", 2);
235 Conversation conversation = mXmppConnectionService
236 .findOrCreateConversation(account, parts[0], false);
237 String pgpBody = getPgpBody(message);
238 Message finishedMessage;
239 if (pgpBody != null) {
240 finishedMessage = new Message(conversation, fullJid, pgpBody,
241 Message.ENCRYPTION_PGP, status);
242 } else {
243 String body = message.findChild("body").getContent();
244 finishedMessage = new Message(conversation, fullJid, body,
245 Message.ENCRYPTION_NONE, status);
246 }
247 finishedMessage.setTime(getTimestamp(message));
248 finishedMessage.setRemoteMsgId(message.getAttribute("id"));
249 finishedMessage.markable = isMarkable(message);
250 if (conversation.getMode() == Conversation.MODE_MULTI
251 && parts.length >= 2) {
252 finishedMessage.setType(Message.TYPE_PRIVATE);
253 finishedMessage.setPresence(parts[1]);
254 finishedMessage.setTrueCounterpart(conversation.getMucOptions()
255 .getTrueCounterpart(parts[1]));
256 if (conversation.hasDuplicateMessage(finishedMessage)) {
257 return null;
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 mXmppConnectionService.getAvatarService().clear(account);
379 mXmppConnectionService.updateConversationUi();
380 mXmppConnectionService.updateAccountUi();
381 }
382 }
383 }
384 }
385 }
386 }
387
388 private String getPgpBody(Element message) {
389 Element child = message.findChild("x", "jabber:x:encrypted");
390 if (child == null) {
391 return null;
392 } else {
393 return child.getContent();
394 }
395 }
396
397 private boolean isMarkable(Element message) {
398 return message.hasChild("markable", "urn:xmpp:chat-markers:0");
399 }
400
401 @Override
402 public void onMessagePacketReceived(Account account, MessagePacket packet) {
403 Message message = null;
404 this.parseNick(packet, account);
405
406 if ((packet.getType() == MessagePacket.TYPE_CHAT || packet.getType() == MessagePacket.TYPE_NORMAL)) {
407 if ((packet.getBody() != null)
408 && (packet.getBody().startsWith("?OTR"))) {
409 message = this.parseOtrChat(packet, account);
410 if (message != null) {
411 message.markUnread();
412 }
413 } else if (packet.hasChild("body")
414 && !(packet.hasChild("x",
415 "http://jabber.org/protocol/muc#user"))) {
416 message = this.parseChat(packet, account);
417 if (message != null) {
418 message.markUnread();
419 }
420 } else if (packet.hasChild("received", "urn:xmpp:carbons:2")
421 || (packet.hasChild("sent", "urn:xmpp:carbons:2"))) {
422 message = this.parseCarbonMessage(packet, account);
423 if (message != null) {
424 if (message.getStatus() == Message.STATUS_SEND) {
425 account.activateGracePeriod();
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 } else {
441 mXmppConnectionService.markRead(message.getConversation(),
442 false);
443 account.activateGracePeriod();
444 }
445 }
446 } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
447 this.parseError(packet, account);
448 return;
449 } else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
450 this.parseHeadline(packet, account);
451 return;
452 }
453 if ((message == null) || (message.getBody() == null)) {
454 return;
455 }
456 if ((mXmppConnectionService.confirmMessages())
457 && ((packet.getId() != null))) {
458 if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
459 MessagePacket receipt = mXmppConnectionService
460 .getMessageGenerator().received(account, packet,
461 "urn:xmpp:chat-markers:0");
462 mXmppConnectionService.sendMessagePacket(account, receipt);
463 }
464 if (packet.hasChild("request", "urn:xmpp:receipts")) {
465 MessagePacket receipt = mXmppConnectionService
466 .getMessageGenerator().received(account, packet,
467 "urn:xmpp:receipts");
468 mXmppConnectionService.sendMessagePacket(account, receipt);
469 }
470 }
471 Conversation conversation = message.getConversation();
472 conversation.add(message);
473
474 if (message.getStatus() == Message.STATUS_RECEIVED
475 && conversation.getOtrSession() != null
476 && !conversation.getOtrSession().getSessionID().getUserID()
477 .equals(message.getPresence())) {
478 conversation.endOtrIfNeeded();
479 }
480
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.trusted() && message.bodyContainsDownloadable()) {
488 this.mXmppConnectionService.getHttpConnectionManager()
489 .createNewConnection(message);
490 } else {
491 mXmppConnectionService.getNotificationService().push(message);
492 }
493 mXmppConnectionService.updateConversationUi();
494 }
495
496 private void parseHeadline(MessagePacket packet, Account account) {
497 if (packet.hasChild("event", "http://jabber.org/protocol/pubsub#event")) {
498 Element event = packet.findChild("event",
499 "http://jabber.org/protocol/pubsub#event");
500 parseEvent(event, packet.getFrom(), account);
501 }
502 }
503
504 private void parseNick(MessagePacket packet, Account account) {
505 Element nick = packet.findChild("nick",
506 "http://jabber.org/protocol/nick");
507 if (nick != null) {
508 if (packet.getFrom() != null) {
509 Contact contact = account.getRoster().getContact(
510 packet.getFrom());
511 contact.setPresenceName(nick.getContent());
512 }
513 }
514 }
515}