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.entities.Account;
8import eu.siacs.conversations.entities.Contact;
9import eu.siacs.conversations.entities.Conversation;
10import eu.siacs.conversations.entities.Message;
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
21 private long lastCarbonMessageReceived = -XmppConnectionService.CARBON_GRACE_PERIOD;
22
23 public MessageParser(XmppConnectionService service) {
24 super(service);
25 }
26
27 private Message parseChat(MessagePacket packet, Account account) {
28 String[] fromParts = packet.getFrom().split("/");
29 Conversation conversation = mXmppConnectionService
30 .findOrCreateConversation(account, fromParts[0], false);
31 conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
32 updateLastseen(packet, account, true);
33 String pgpBody = getPgpBody(packet);
34 Message finishedMessage;
35 if (pgpBody != null) {
36 finishedMessage = new Message(conversation, packet.getFrom(),
37 pgpBody, Message.ENCRYPTION_PGP, Message.STATUS_RECIEVED);
38 } else {
39 finishedMessage = new Message(conversation, packet.getFrom(),
40 packet.getBody(), Message.ENCRYPTION_NONE,
41 Message.STATUS_RECIEVED);
42 }
43 finishedMessage.setTime(getTimestamp(packet));
44 return finishedMessage;
45 }
46
47 private Message parseOtrChat(MessagePacket packet, Account account) {
48 boolean properlyAddressed = (packet.getTo().split("/").length == 2)
49 || (account.countPresences() == 1);
50 String[] fromParts = packet.getFrom().split("/");
51 Conversation conversation = mXmppConnectionService
52 .findOrCreateConversation(account, fromParts[0], false);
53 updateLastseen(packet, account, true);
54 String body = packet.getBody();
55 if (!conversation.hasValidOtrSession()) {
56 if (properlyAddressed) {
57 conversation.startOtrSession(
58 mXmppConnectionService.getApplicationContext(),
59 fromParts[1], false);
60 } else {
61 return null;
62 }
63 } else {
64 String foreignPresence = conversation.getOtrSession()
65 .getSessionID().getUserID();
66 if (!foreignPresence.equals(fromParts[1])) {
67 conversation.endOtrIfNeeded();
68 if (properlyAddressed) {
69 conversation.startOtrSession(
70 mXmppConnectionService.getApplicationContext(),
71 fromParts[1], false);
72 } else {
73 return null;
74 }
75 }
76 }
77 try {
78 Session otrSession = conversation.getOtrSession();
79 SessionStatus before = otrSession.getSessionStatus();
80 body = otrSession.transformReceiving(body);
81 SessionStatus after = otrSession.getSessionStatus();
82 if ((before != after) && (after == SessionStatus.ENCRYPTED)) {
83 mXmppConnectionService.onOtrSessionEstablished(conversation);
84 } else if ((before != after) && (after == SessionStatus.FINISHED)) {
85 conversation.resetOtrSession();
86 mXmppConnectionService.updateConversationUi();
87 }
88 if ((body == null) || (body.isEmpty())) {
89 return null;
90 }
91 if (body.startsWith(CryptoHelper.FILETRANSFER)) {
92 String key = body.substring(CryptoHelper.FILETRANSFER.length());
93 conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
94 return null;
95 }
96 conversation
97 .setLatestMarkableMessageId(getMarkableMessageId(packet));
98 Message finishedMessage = new Message(conversation,
99 packet.getFrom(), body, Message.ENCRYPTION_OTR,
100 Message.STATUS_RECIEVED);
101 finishedMessage.setTime(getTimestamp(packet));
102 return finishedMessage;
103 } catch (Exception e) {
104 String receivedId = packet.getId();
105 if (receivedId != null) {
106 mXmppConnectionService.replyWithNotAcceptable(account, packet);
107 }
108 conversation.endOtrIfNeeded();
109 return null;
110 }
111 }
112
113 private Message parseGroupchat(MessagePacket packet, Account account) {
114 int status;
115 String[] fromParts = packet.getFrom().split("/");
116 if (mXmppConnectionService.find(account.pendingConferenceLeaves,account,fromParts[0]) != null) {
117 return null;
118 }
119 Conversation conversation = mXmppConnectionService
120 .findOrCreateConversation(account, fromParts[0], true);
121 if (packet.hasChild("subject")) {
122 conversation.getMucOptions().setSubject(
123 packet.findChild("subject").getContent());
124 mXmppConnectionService.updateConversationUi();
125 return null;
126 }
127 if ((fromParts.length == 1)) {
128 return null;
129 }
130 String counterPart = fromParts[1];
131 if (counterPart.equals(conversation.getMucOptions().getActualNick())) {
132 if (mXmppConnectionService.markMessage(conversation,
133 packet.getId(), Message.STATUS_SEND)) {
134 return null;
135 } else {
136 status = Message.STATUS_SEND;
137 }
138 } else {
139 status = Message.STATUS_RECIEVED;
140 }
141 String pgpBody = getPgpBody(packet);
142 conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
143 Message finishedMessage;
144 if (pgpBody == null) {
145 finishedMessage = new Message(conversation, counterPart,
146 packet.getBody(), Message.ENCRYPTION_NONE, status);
147 } else {
148 finishedMessage = new Message(conversation, counterPart, pgpBody,
149 Message.ENCRYPTION_PGP, status);
150 }
151 finishedMessage.setTime(getTimestamp(packet));
152 if (status == Message.STATUS_RECIEVED) {
153 finishedMessage.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterPart));
154 }
155 return finishedMessage;
156 }
157
158 private Message parseCarbonMessage(MessagePacket packet, Account account) {
159 int status;
160 String fullJid;
161 Element forwarded;
162 if (packet.hasChild("received")) {
163 forwarded = packet.findChild("received").findChild("forwarded");
164 status = Message.STATUS_RECIEVED;
165 } else if (packet.hasChild("sent")) {
166 forwarded = packet.findChild("sent").findChild("forwarded");
167 status = Message.STATUS_SEND;
168 } else {
169 return null;
170 }
171 if (forwarded == null) {
172 return null;
173 }
174 Element message = forwarded.findChild("message");
175 if ((message == null) || (!message.hasChild("body"))) {
176 if (status == Message.STATUS_RECIEVED) {
177 parseNormal(message, account);
178 }
179 return null;
180 }
181 if (status == Message.STATUS_RECIEVED) {
182 fullJid = message.getAttribute("from");
183 if (fullJid == null ) {
184 return null;
185 } else {
186 updateLastseen(message, account, true);
187 }
188 } else {
189 fullJid = message.getAttribute("to");
190 if (fullJid == null) {
191 return null;
192 }
193 }
194 String[] parts = fullJid.split("/");
195 Conversation conversation = mXmppConnectionService
196 .findOrCreateConversation(account, parts[0], false);
197 conversation.setLatestMarkableMessageId(getMarkableMessageId(packet));
198 String pgpBody = getPgpBody(message);
199 Message finishedMessage;
200 if (pgpBody != null) {
201 finishedMessage = new Message(conversation, fullJid, pgpBody,
202 Message.ENCRYPTION_PGP, status);
203 } else {
204 String body = message.findChild("body").getContent();
205 finishedMessage = new Message(conversation, fullJid, body,
206 Message.ENCRYPTION_NONE, status);
207 }
208 finishedMessage.setTime(getTimestamp(message));
209 return finishedMessage;
210 }
211
212 private void parseError(MessagePacket packet, Account account) {
213 String[] fromParts = packet.getFrom().split("/");
214 mXmppConnectionService.markMessage(account, fromParts[0],
215 packet.getId(), Message.STATUS_SEND_FAILED);
216 }
217
218 private void parseNormal(Element packet, Account account) {
219 if (packet.hasChild("event","http://jabber.org/protocol/pubsub#event")) {
220 Element event = packet.findChild("event","http://jabber.org/protocol/pubsub#event");
221 parseEvent(event,packet.getAttribute("from"),account);
222 }
223 if (packet.hasChild("displayed", "urn:xmpp:chat-markers:0")) {
224 String id = packet
225 .findChild("displayed", "urn:xmpp:chat-markers:0")
226 .getAttribute("id");
227 String[] fromParts = packet.getAttribute("from").split("/");
228 updateLastseen(packet, account, true);
229 mXmppConnectionService.markMessage(account, fromParts[0], id,
230 Message.STATUS_SEND_DISPLAYED);
231 } else if (packet.hasChild("received", "urn:xmpp:chat-markers:0")) {
232 String id = packet.findChild("received", "urn:xmpp:chat-markers:0")
233 .getAttribute("id");
234 String[] fromParts = packet.getAttribute("from").split("/");
235 updateLastseen(packet, account, false);
236 mXmppConnectionService.markMessage(account, fromParts[0], id,
237 Message.STATUS_SEND_RECEIVED);
238 } else if (packet.hasChild("x","http://jabber.org/protocol/muc#user")) {
239 Element x = packet.findChild("x","http://jabber.org/protocol/muc#user");
240 if (x.hasChild("invite")) {
241 Conversation conversation = mXmppConnectionService
242 .findOrCreateConversation(account,
243 packet.getAttribute("from"), true);
244 if (!conversation.getMucOptions().online()) {
245 mXmppConnectionService.joinMuc(conversation);
246 mXmppConnectionService.updateConversationUi();
247 }
248 }
249
250 } else if (packet.hasChild("x", "jabber:x:conference")) {
251 Element x = packet.findChild("x", "jabber:x:conference");
252 String jid = x.getAttribute("jid");
253 if (jid!=null) {
254 Conversation conversation = mXmppConnectionService
255 .findOrCreateConversation(account,jid, true);
256 if (!conversation.getMucOptions().online()) {
257 mXmppConnectionService.joinMuc(conversation);
258 mXmppConnectionService.updateConversationUi();
259 }
260 }
261 }
262 }
263
264 private void parseEvent(Element event, String from, Account account) {
265 Element items = event.findChild("items");
266 String node = items.getAttribute("node");
267 if (node!=null) {
268 if (node.equals("urn:xmpp:avatar:metadata")) {
269 Avatar avatar = Avatar.parseMetadata(items);
270 avatar.owner = from;
271 if (mXmppConnectionService.getFileBackend().isAvatarCached(avatar)) {
272 if (account.getJid().equals(from)) {
273 account.setAvatar(avatar.getFilename());
274 } else {
275 Contact contact = account.getRoster().getContact(from);
276 contact.setAvatar(avatar.getFilename());
277 }
278 } else {
279 mXmppConnectionService.fetchAvatar(account, avatar);
280 }
281 } else {
282 Log.d("xmppService",account.getJid()+": "+node+" from "+from);
283 }
284 } else {
285 Log.d("xmppService",event.toString());
286 }
287 }
288
289 private String getPgpBody(Element message) {
290 Element child = message.findChild("x", "jabber:x:encrypted");
291 if (child == null) {
292 return null;
293 } else {
294 return child.getContent();
295 }
296 }
297
298 private String getMarkableMessageId(Element message) {
299 if (message.hasChild("markable", "urn:xmpp:chat-markers:0")) {
300 return message.getAttribute("id");
301 } else {
302 return null;
303 }
304 }
305
306 @Override
307 public void onMessagePacketReceived(Account account, MessagePacket packet) {
308 Message message = null;
309 boolean notify = true;
310 if (mXmppConnectionService.getPreferences().getBoolean(
311 "notification_grace_period_after_carbon_received", true)) {
312 notify = (SystemClock.elapsedRealtime() - lastCarbonMessageReceived) > XmppConnectionService.CARBON_GRACE_PERIOD;
313 }
314
315 if ((packet.getType() == MessagePacket.TYPE_CHAT)) {
316 if ((packet.getBody() != null)
317 && (packet.getBody().startsWith("?OTR"))) {
318 message = this.parseOtrChat(packet, account);
319 if (message != null) {
320 message.markUnread();
321 }
322 } else if (packet.hasChild("body")) {
323 message = this.parseChat(packet, account);
324 message.markUnread();
325 } else if (packet.hasChild("received") || (packet.hasChild("sent"))) {
326 message = this.parseCarbonMessage(packet, account);
327 if (message != null) {
328 if (message.getStatus() == Message.STATUS_SEND) {
329 lastCarbonMessageReceived = SystemClock
330 .elapsedRealtime();
331 notify = false;
332 message.getConversation().markRead();
333 } else {
334 message.markUnread();
335 }
336 }
337 } else {
338 parseNormal(packet, account);
339 }
340
341 } else if (packet.getType() == MessagePacket.TYPE_GROUPCHAT) {
342 message = this.parseGroupchat(packet, account);
343 if (message != null) {
344 if (message.getStatus() == Message.STATUS_RECIEVED) {
345 message.markUnread();
346 } else {
347 message.getConversation().markRead();
348 lastCarbonMessageReceived = SystemClock
349 .elapsedRealtime();
350 notify = false;
351 }
352 }
353 } else if (packet.getType() == MessagePacket.TYPE_ERROR) {
354 this.parseError(packet, account);
355 return;
356 } else if (packet.getType() == MessagePacket.TYPE_NORMAL) {
357 this.parseNormal(packet, account);
358 return;
359 } else if (packet.getType() == MessagePacket.TYPE_HEADLINE) {
360 this.parseHeadline(packet, account);
361 return;
362 }
363 if ((message == null) || (message.getBody() == null)) {
364 return;
365 }
366 if ((mXmppConnectionService.confirmMessages())
367 && ((packet.getId() != null))) {
368 if (packet.hasChild("markable", "urn:xmpp:chat-markers:0")) {
369 MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:chat-markers:0");
370 mXmppConnectionService.sendMessagePacket(account, receipt);
371 }
372 if (packet.hasChild("request", "urn:xmpp:receipts")) {
373 MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, packet, "urn:xmpp:receipts");
374 mXmppConnectionService.sendMessagePacket(account, receipt);
375 }
376 }
377 Conversation conversation = message.getConversation();
378 conversation.getMessages().add(message);
379 if (packet.getType() != MessagePacket.TYPE_ERROR) {
380 mXmppConnectionService.databaseBackend.createMessage(message);
381 }
382 mXmppConnectionService.notifyUi(conversation, notify);
383 }
384
385 private void parseHeadline(MessagePacket packet, Account account) {
386 if (packet.hasChild("event","http://jabber.org/protocol/pubsub#event")) {
387 Element event = packet.findChild("event","http://jabber.org/protocol/pubsub#event");
388 parseEvent(event,packet.getFrom(),account);
389 }
390 }
391}