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