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