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