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