1package eu.siacs.conversations.entities;
2
3import java.net.MalformedURLException;
4import java.net.URL;
5import java.util.Arrays;
6
7import eu.siacs.conversations.Config;
8import eu.siacs.conversations.xmpp.jid.InvalidJidException;
9import eu.siacs.conversations.xmpp.jid.Jid;
10
11import android.content.ContentValues;
12import android.database.Cursor;
13
14public class Message extends AbstractEntity {
15
16 public static final String TABLENAME = "messages";
17
18 public static final int STATUS_RECEIVED = 0;
19 public static final int STATUS_UNSEND = 1;
20 public static final int STATUS_SEND = 2;
21 public static final int STATUS_SEND_FAILED = 3;
22 public static final int STATUS_WAITING = 5;
23 public static final int STATUS_OFFERED = 6;
24 public static final int STATUS_SEND_RECEIVED = 7;
25 public static final int STATUS_SEND_DISPLAYED = 8;
26
27 public static final int ENCRYPTION_NONE = 0;
28 public static final int ENCRYPTION_PGP = 1;
29 public static final int ENCRYPTION_OTR = 2;
30 public static final int ENCRYPTION_DECRYPTED = 3;
31 public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
32
33 public static final int TYPE_TEXT = 0;
34 public static final int TYPE_IMAGE = 1;
35 public static final int TYPE_AUDIO = 2;
36 public static final int TYPE_STATUS = 3;
37 public static final int TYPE_PRIVATE = 4;
38
39 public static String CONVERSATION = "conversationUuid";
40 public static String COUNTERPART = "counterpart";
41 public static String TRUE_COUNTERPART = "trueCounterpart";
42 public static String BODY = "body";
43 public static String TIME_SENT = "timeSent";
44 public static String ENCRYPTION = "encryption";
45 public static String STATUS = "status";
46 public static String TYPE = "type";
47 public static String REMOTE_MSG_ID = "remoteMsgId";
48
49 protected String conversationUuid;
50 protected Jid counterpart;
51 protected String trueCounterpart;
52 protected String body;
53 protected String encryptedBody;
54 protected long timeSent;
55 protected int encryption;
56 protected int status;
57 protected int type;
58 protected boolean read = true;
59 protected String remoteMsgId = null;
60
61 protected Conversation conversation = null;
62 protected Downloadable downloadable = null;
63 public boolean markable = false;
64
65 private Message mNextMessage = null;
66 private Message mPreviousMessage = null;
67
68 private Message() {
69
70 }
71
72 public Message(Conversation conversation, String body, int encryption) {
73 this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
74 conversation.getContactJid(), null, body, System
75 .currentTimeMillis(), encryption,
76 Message.STATUS_UNSEND, TYPE_TEXT, null);
77 this.conversation = conversation;
78 }
79
80 public Message(final Conversation conversation, final Jid counterpart, final String body,
81 final int encryption, final int status) {
82 this(java.util.UUID.randomUUID().toString(), conversation.getUuid(),
83 counterpart, null, body, System.currentTimeMillis(),
84 encryption, status, TYPE_TEXT, null);
85 this.conversation = conversation;
86 }
87
88 public Message(final String uuid, final String conversationUUid, final Jid counterpart,
89 final String trueCounterpart, final String body, final long timeSent,
90 final int encryption, final int status, final int type, final String remoteMsgId) {
91 this.uuid = uuid;
92 this.conversationUuid = conversationUUid;
93 this.counterpart = counterpart;
94 this.trueCounterpart = trueCounterpart;
95 this.body = body;
96 this.timeSent = timeSent;
97 this.encryption = encryption;
98 this.status = status;
99 this.type = type;
100 this.remoteMsgId = remoteMsgId;
101 }
102
103 @Override
104 public ContentValues getContentValues() {
105 ContentValues values = new ContentValues();
106 values.put(UUID, uuid);
107 values.put(CONVERSATION, conversationUuid);
108 values.put(COUNTERPART, counterpart.toString());
109 values.put(TRUE_COUNTERPART, trueCounterpart);
110 values.put(BODY, body);
111 values.put(TIME_SENT, timeSent);
112 values.put(ENCRYPTION, encryption);
113 values.put(STATUS, status);
114 values.put(TYPE, type);
115 values.put(REMOTE_MSG_ID, remoteMsgId);
116 return values;
117 }
118
119 public String getConversationUuid() {
120 return conversationUuid;
121 }
122
123 public Conversation getConversation() {
124 return this.conversation;
125 }
126
127 public Jid getCounterpart() {
128 return counterpart;
129 }
130
131 public Contact getContact() {
132 if (this.conversation.getMode() == Conversation.MODE_SINGLE) {
133 return this.conversation.getContact();
134 } else {
135 if (this.trueCounterpart == null) {
136 return null;
137 } else {
138 return this.conversation.getAccount().getRoster()
139 .getContactFromRoster(this.trueCounterpart);
140 }
141 }
142 }
143
144 public String getBody() {
145 return body;
146 }
147
148 public long getTimeSent() {
149 return timeSent;
150 }
151
152 public int getEncryption() {
153 return encryption;
154 }
155
156 public int getStatus() {
157 return status;
158 }
159
160 public String getRemoteMsgId() {
161 return this.remoteMsgId;
162 }
163
164 public void setRemoteMsgId(String id) {
165 this.remoteMsgId = id;
166 }
167
168 public static Message fromCursor(Cursor cursor) {
169 Jid jid;
170 try {
171 jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(COUNTERPART)));
172 } catch (InvalidJidException e) {
173 jid = null;
174 }
175 return new Message(cursor.getString(cursor.getColumnIndex(UUID)),
176 cursor.getString(cursor.getColumnIndex(CONVERSATION)),
177 jid,
178 cursor.getString(cursor.getColumnIndex(TRUE_COUNTERPART)),
179 cursor.getString(cursor.getColumnIndex(BODY)),
180 cursor.getLong(cursor.getColumnIndex(TIME_SENT)),
181 cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
182 cursor.getInt(cursor.getColumnIndex(STATUS)),
183 cursor.getInt(cursor.getColumnIndex(TYPE)),
184 cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)));
185 }
186
187 public void setConversation(Conversation conv) {
188 this.conversation = conv;
189 }
190
191 public void setStatus(int status) {
192 this.status = status;
193 }
194
195 public boolean isRead() {
196 return this.read;
197 }
198
199 public void markRead() {
200 this.read = true;
201 }
202
203 public void markUnread() {
204 this.read = false;
205 }
206
207 public void setTime(long time) {
208 this.timeSent = time;
209 }
210
211 public void setEncryption(int encryption) {
212 this.encryption = encryption;
213 }
214
215 public void setBody(String body) {
216 this.body = body;
217 }
218
219 public String getEncryptedBody() {
220 return this.encryptedBody;
221 }
222
223 public void setEncryptedBody(String body) {
224 this.encryptedBody = body;
225 }
226
227 public void setType(int type) {
228 this.type = type;
229 }
230
231 public int getType() {
232 return this.type;
233 }
234
235 public void setPresence(String presence) {
236 if (presence == null) {
237 this.counterpart = this.counterpart.toBareJid();
238 } else {
239 try {
240 this.counterpart = Jid.fromString(this.counterpart.toBareJid() + "/" + presence);
241 } catch (final InvalidJidException ignored) {
242 // TODO: Handle this?
243 }
244 }
245 }
246
247 public void setTrueCounterpart(String trueCounterpart) {
248 this.trueCounterpart = trueCounterpart;
249 }
250
251 public Jid getPresence() {
252 return counterpart;
253 }
254
255 public void setDownloadable(Downloadable downloadable) {
256 this.downloadable = downloadable;
257 }
258
259 public Downloadable getDownloadable() {
260 return this.downloadable;
261 }
262
263 public static Message createStatusMessage(Conversation conversation) {
264 Message message = new Message();
265 message.setType(Message.TYPE_STATUS);
266 message.setConversation(conversation);
267 return message;
268 }
269
270 public void setCounterpart(final Jid counterpart) {
271 this.counterpart = counterpart;
272 }
273
274 public boolean equals(Message message) {
275 if ((this.remoteMsgId != null) && (this.body != null)
276 && (this.counterpart != null)) {
277 return this.remoteMsgId.equals(message.getRemoteMsgId())
278 && this.body.equals(message.getBody())
279 && this.counterpart.equals(message.getCounterpart());
280 } else {
281 return false;
282 }
283 }
284
285 public Message next() {
286 if (this.mNextMessage == null) {
287 synchronized (this.conversation.messages) {
288 int index = this.conversation.messages.indexOf(this);
289 if (index < 0
290 || index >= this.conversation.getMessages().size() - 1) {
291 this.mNextMessage = null;
292 } else {
293 this.mNextMessage = this.conversation.messages
294 .get(index + 1);
295 }
296 }
297 }
298 return this.mNextMessage;
299 }
300
301 public Message prev() {
302 if (this.mPreviousMessage == null) {
303 synchronized (this.conversation.messages) {
304 int index = this.conversation.messages.indexOf(this);
305 if (index <= 0 || index > this.conversation.messages.size()) {
306 this.mPreviousMessage = null;
307 } else {
308 this.mPreviousMessage = this.conversation.messages
309 .get(index - 1);
310 }
311 }
312 }
313 return this.mPreviousMessage;
314 }
315
316 public boolean mergeable(Message message) {
317 if (message == null) {
318 return false;
319 }
320 return (message.getType() == Message.TYPE_TEXT
321 && this.getDownloadable() == null
322 && message.getDownloadable() == null
323 && message.getEncryption() != Message.ENCRYPTION_PGP
324 && this.getType() == message.getType()
325 && this.getEncryption() == message.getEncryption()
326 && this.getCounterpart().equals(message.getCounterpart())
327 && (message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) && ((this
328 .getStatus() == message.getStatus() || ((this.getStatus() == Message.STATUS_SEND || this
329 .getStatus() == Message.STATUS_SEND_RECEIVED) && (message
330 .getStatus() == Message.STATUS_UNSEND
331 || message.getStatus() == Message.STATUS_SEND || message
332 .getStatus() == Message.STATUS_SEND_DISPLAYED))))
333 && !message.bodyContainsDownloadable()
334 && !this.bodyContainsDownloadable());
335 }
336
337 public String getMergedBody() {
338 Message next = this.next();
339 if (this.mergeable(next)) {
340 return body.trim() + '\n' + next.getMergedBody();
341 }
342 return body.trim();
343 }
344
345 public int getMergedStatus() {
346 Message next = this.next();
347 if (this.mergeable(next)) {
348 return next.getMergedStatus();
349 } else {
350 return getStatus();
351 }
352 }
353
354 public long getMergedTimeSent() {
355 Message next = this.next();
356 if (this.mergeable(next)) {
357 return next.getMergedTimeSent();
358 } else {
359 return getTimeSent();
360 }
361 }
362
363 public boolean wasMergedIntoPrevious() {
364 Message prev = this.prev();
365 return prev != null && prev.mergeable(this);
366 }
367
368 public boolean trusted() {
369 Contact contact = this.getContact();
370 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
371 }
372
373 public boolean bodyContainsDownloadable() {
374 try {
375 URL url = new URL(this.getBody());
376 if (!url.getProtocol().equalsIgnoreCase("http")
377 && !url.getProtocol().equalsIgnoreCase("https")) {
378 return false;
379 }
380 if (url.getPath() == null) {
381 return false;
382 }
383 String[] pathParts = url.getPath().split("/");
384 String filename;
385 if (pathParts.length > 0) {
386 filename = pathParts[pathParts.length - 1];
387 } else {
388 return false;
389 }
390 String[] extensionParts = filename.split("\\.");
391 if (extensionParts.length == 2
392 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
393 extensionParts[extensionParts.length - 1])) {
394 return true;
395 } else if (extensionParts.length == 3
396 && Arrays
397 .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
398 .contains(extensionParts[extensionParts.length - 1])
399 && Arrays.asList(Downloadable.VALID_EXTENSIONS).contains(
400 extensionParts[extensionParts.length - 2])) {
401 return true;
402 } else {
403 return false;
404 }
405 } catch (MalformedURLException e) {
406 return false;
407 }
408 }
409
410 public ImageParams getImageParams() {
411 ImageParams params = getLegacyImageParams();
412 if (params!=null) {
413 return params;
414 }
415 params = new ImageParams();
416 if (this.downloadable != null) {
417 params.size = this.downloadable.getFileSize();
418 }
419 if (body == null) {
420 return params;
421 }
422 String parts[] = body.split("\\|");
423 if (parts.length == 1) {
424 try {
425 params.size = Long.parseLong(parts[0]);
426 } catch (NumberFormatException e) {
427 params.origin = parts[0];
428 try {
429 params.url = new URL(parts[0]);
430 } catch (MalformedURLException e1) {
431 params.url = null;
432 }
433 }
434 } else if (parts.length == 3) {
435 try {
436 params.size = Long.parseLong(parts[0]);
437 } catch (NumberFormatException e) {
438 params.size = 0;
439 }
440 try {
441 params.width = Integer.parseInt(parts[1]);
442 } catch (NumberFormatException e) {
443 params.width = 0;
444 }
445 try {
446 params.height = Integer.parseInt(parts[2]);
447 } catch (NumberFormatException e) {
448 params.height = 0;
449 }
450 } else if (parts.length == 4) {
451 params.origin = parts[0];
452 try {
453 params.url = new URL(parts[0]);
454 } catch (MalformedURLException e1) {
455 params.url = null;
456 }
457 try {
458 params.size = Long.parseLong(parts[1]);
459 } catch (NumberFormatException e) {
460 params.size = 0;
461 }
462 try {
463 params.width = Integer.parseInt(parts[2]);
464 } catch (NumberFormatException e) {
465 params.width = 0;
466 }
467 try {
468 params.height = Integer.parseInt(parts[3]);
469 } catch (NumberFormatException e) {
470 params.height = 0;
471 }
472 }
473 return params;
474 }
475
476 public ImageParams getLegacyImageParams() {
477 ImageParams params = new ImageParams();
478 if (body == null) {
479 return params;
480 }
481 String parts[] = body.split(",");
482 if (parts.length == 3) {
483 try {
484 params.size = Long.parseLong(parts[0]);
485 } catch (NumberFormatException e) {
486 return null;
487 }
488 try {
489 params.width = Integer.parseInt(parts[1]);
490 } catch (NumberFormatException e) {
491 return null;
492 }
493 try {
494 params.height = Integer.parseInt(parts[2]);
495 } catch (NumberFormatException e) {
496 return null;
497 }
498 return params;
499 } else {
500 return null;
501 }
502 }
503
504 public class ImageParams {
505 public URL url;
506 public long size = 0;
507 public int width = 0;
508 public int height = 0;
509 public String origin;
510 }
511}