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 synchronized (this.conversation.messages) {
324 if (this.mNextMessage == null) {
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 return this.mNextMessage;
335 }
336 }
337
338 public Message prev() {
339 synchronized (this.conversation.messages) {
340 if (this.mPreviousMessage == null) {
341
342 int index = this.conversation.messages.indexOf(this);
343 if (index <= 0 || index > this.conversation.messages.size()) {
344 this.mPreviousMessage = null;
345 } else {
346 this.mPreviousMessage = this.conversation.messages
347 .get(index - 1);
348 }
349 }
350 return this.mPreviousMessage;
351 }
352 }
353
354 public boolean mergeable(final Message message) {
355 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());
356 }
357
358 public String getMergedBody() {
359 Message next = this.next();
360 if (this.mergeable(next)) {
361 return body.trim() + '\n' + next.getMergedBody();
362 }
363 return body.trim();
364 }
365
366 public int getMergedStatus() {
367 return getStatus();
368 }
369
370 public long getMergedTimeSent() {
371 Message next = this.next();
372 if (this.mergeable(next)) {
373 return next.getMergedTimeSent();
374 } else {
375 return getTimeSent();
376 }
377 }
378
379 public boolean wasMergedIntoPrevious() {
380 Message prev = this.prev();
381 return prev != null && prev.mergeable(this);
382 }
383
384 public boolean trusted() {
385 Contact contact = this.getContact();
386 return (status > STATUS_RECEIVED || (contact != null && contact.trusted()));
387 }
388
389 public boolean bodyContainsDownloadable() {
390 try {
391 URL url = new URL(this.getBody());
392 if (!url.getProtocol().equalsIgnoreCase("http")
393 && !url.getProtocol().equalsIgnoreCase("https")) {
394 return false;
395 }
396 if (url.getPath() == null) {
397 return false;
398 }
399 String[] pathParts = url.getPath().split("/");
400 String filename;
401 if (pathParts.length > 0) {
402 filename = pathParts[pathParts.length - 1];
403 } else {
404 return false;
405 }
406 String[] extensionParts = filename.split("\\.");
407 if (extensionParts.length == 2
408 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
409 extensionParts[extensionParts.length - 1])) {
410 return true;
411 } else if (extensionParts.length == 3
412 && Arrays
413 .asList(Downloadable.VALID_CRYPTO_EXTENSIONS)
414 .contains(extensionParts[extensionParts.length - 1])
415 && Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
416 extensionParts[extensionParts.length - 2])) {
417 return true;
418 } else {
419 return false;
420 }
421 } catch (MalformedURLException e) {
422 return false;
423 }
424 }
425
426 public ImageParams getImageParams() {
427 ImageParams params = getLegacyImageParams();
428 if (params != null) {
429 return params;
430 }
431 params = new ImageParams();
432 if (this.downloadable != null) {
433 params.size = this.downloadable.getFileSize();
434 }
435 if (body == null) {
436 return params;
437 }
438 String parts[] = body.split("\\|");
439 if (parts.length == 1) {
440 try {
441 params.size = Long.parseLong(parts[0]);
442 } catch (NumberFormatException e) {
443 params.origin = parts[0];
444 try {
445 params.url = new URL(parts[0]);
446 } catch (MalformedURLException e1) {
447 params.url = null;
448 }
449 }
450 } else if (parts.length == 3) {
451 try {
452 params.size = Long.parseLong(parts[0]);
453 } catch (NumberFormatException e) {
454 params.size = 0;
455 }
456 try {
457 params.width = Integer.parseInt(parts[1]);
458 } catch (NumberFormatException e) {
459 params.width = 0;
460 }
461 try {
462 params.height = Integer.parseInt(parts[2]);
463 } catch (NumberFormatException e) {
464 params.height = 0;
465 }
466 } else if (parts.length == 4) {
467 params.origin = parts[0];
468 try {
469 params.url = new URL(parts[0]);
470 } catch (MalformedURLException e1) {
471 params.url = null;
472 }
473 try {
474 params.size = Long.parseLong(parts[1]);
475 } catch (NumberFormatException e) {
476 params.size = 0;
477 }
478 try {
479 params.width = Integer.parseInt(parts[2]);
480 } catch (NumberFormatException e) {
481 params.width = 0;
482 }
483 try {
484 params.height = Integer.parseInt(parts[3]);
485 } catch (NumberFormatException e) {
486 params.height = 0;
487 }
488 }
489 return params;
490 }
491
492 public ImageParams getLegacyImageParams() {
493 ImageParams params = new ImageParams();
494 if (body == null) {
495 return params;
496 }
497 String parts[] = body.split(",");
498 if (parts.length == 3) {
499 try {
500 params.size = Long.parseLong(parts[0]);
501 } catch (NumberFormatException e) {
502 return null;
503 }
504 try {
505 params.width = Integer.parseInt(parts[1]);
506 } catch (NumberFormatException e) {
507 return null;
508 }
509 try {
510 params.height = Integer.parseInt(parts[2]);
511 } catch (NumberFormatException e) {
512 return null;
513 }
514 return params;
515 } else {
516 return null;
517 }
518 }
519
520 public void untie() {
521 this.mNextMessage = null;
522 this.mPreviousMessage = null;
523 }
524
525 public class ImageParams {
526 public URL url;
527 public long size = 0;
528 public int width = 0;
529 public int height = 0;
530 public String origin;
531 }
532}