1 /* $Id$ */
2 /* Copyright (c) 2011-2012 Pierre Pronchery <khorben@defora.org> */
3 /* This file is part of DeforaOS Desktop Mailer */
4 /* This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */
15
16
17
18 #include <ctype.h>
19 #include <stdarg.h>
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <time.h>
24 #include <System.h>
25 #include "mailer.h"
26 #include "message.h"
27
28
29 /* Message */
30 /* private */
31 /* types */
32 typedef struct _MessageHeader
33 {
34 char * header;
35 char * value;
36 } MessageHeader;
37
38 struct _MailerMessage
39 {
40 GtkTreeStore * store;
41 GtkTreeRowReference * row;
42
43 int flags;
44
45 MessageHeader * headers;
46 size_t headers_cnt;
47
48 char * body;
49 size_t body_cnt;
50
51 GtkTextBuffer * text;
52
53 char ** attachments;
54 size_t attachments_cnt;
55
56 AccountMessage * data;
57 };
58
59
60 /* prototypes */
61 /* accessors */
62 static gboolean _message_set(Message * message, ...);
63 static int _message_set_date(Message * message, char const * date);
64 static int _message_set_from(Message * message, char const * from);
65 static int _message_set_status(Message * message, char const * status);
66 static int _message_set_to(Message * message, char const * to);
67
68 /* useful */
69 /* message headers */
70 static int _message_header_set(MessageHeader * mh, char const * header,
71 char const * value);
72
73
74 /* constants */
75 static struct
76 {
77 char const * header;
78 MailerHeaderColumn column;
79 int (*callback)(Message * message, char const * value);
80 } _message_columns[] =
81 {
82 { "Date", 0, _message_set_date },
83 { "From", 0, _message_set_from },
84 { "Status", 0, _message_set_status },
85 { "Subject", MHC_SUBJECT, NULL },
86 { "To", 0, _message_set_to },
87 { NULL, 0, NULL }
88 };
89
90
91 /* public */
92 /* functions */
93 /* message_new */
message_new(AccountMessage * message,GtkTreeStore * store,GtkTreeIter * iter)94 Message * message_new(AccountMessage * message, GtkTreeStore * store,
95 GtkTreeIter * iter)
96 {
97 Message * ret;
98 GtkTreePath * path;
99
100 #ifdef DEBUG
101 fprintf(stderr, "DEBUG: %s(%p, %p, %p)\n", __func__, (void *)message,
102 (void *)store, (void *)iter);
103 #endif
104 if((ret = object_new(sizeof(*ret))) == NULL)
105 return NULL;
106 if((ret->store = store) != NULL)
107 {
108 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), iter);
109 ret->row = gtk_tree_row_reference_new(GTK_TREE_MODEL(store),
110 path);
111 gtk_tree_path_free(path);
112 gtk_tree_store_set(store, iter, MHC_MESSAGE, ret, -1);
113 }
114 else
115 ret->row = NULL;
116 ret->flags = 0;
117 ret->headers = NULL;
118 ret->headers_cnt = 0;
119 ret->body = NULL;
120 ret->body_cnt = 0;
121 ret->text = gtk_text_buffer_new(NULL);
122 ret->attachments = NULL;
123 ret->attachments_cnt = 0;
124 ret->data = message;
125 _message_set_date(ret, NULL);
126 _message_set_status(ret, NULL);
127 return ret;
128 }
129
130
131 /* message_new_open */
message_new_open(Mailer * mailer,char const * filename)132 Message * message_new_open(Mailer * mailer, char const * filename)
133 {
134 Message * message;
135 Config * config;
136 Account * account;
137
138 if((message = message_new(NULL, NULL, NULL)) == NULL)
139 return NULL;
140 if((config = config_new()) == NULL
141 || config_set(config, "title", "mbox", filename) != 0)
142 {
143 if(config != NULL)
144 config_delete(config);
145 message_delete(message);
146 return NULL;
147 }
148 if((account = account_new(mailer, "mbox", "title", NULL)) == NULL
149 || account_init(account) != 0
150 || account_config_load(account, config) != 0
151 || account_start(account) != 0)
152 {
153 if(account != NULL)
154 account_delete(account);
155 config_delete(config);
156 message_delete(message);
157 return NULL;
158 }
159 /* FIXME really implement; possibly:
160 * - set different helpers for account;
161 * - delete the account once the message loaded;
162 * - implement and use the Transport class instead. */
163 config_delete(config);
164 account_delete(account);
165 return message;
166 }
167
168
169 /* message_delete */
message_delete(Message * message)170 void message_delete(Message * message)
171 {
172 if(message->row != NULL)
173 gtk_tree_row_reference_free(message->row);
174 g_object_unref(message->text);
175 free(message->body);
176 free(message->headers);
177 object_delete(message);
178 }
179
180
181 /* accessors */
182 /* message_get_body */
message_get_body(Message * message)183 GtkTextBuffer * message_get_body(Message * message)
184 {
185 return message->text;
186 }
187
188
189 /* message_get_data */
message_get_data(Message * message)190 AccountMessage * message_get_data(Message * message)
191 {
192 return message->data;
193 }
194
195
196 /* message_get_flags */
message_get_flags(Message * message)197 int message_get_flags(Message * message)
198 {
199 return message->flags;
200 }
201
202
203 /* message_get_header */
message_get_header(Message * message,char const * header)204 char const * message_get_header(Message * message, char const * header)
205 {
206 size_t i;
207
208 for(i = 0; i < message->headers_cnt; i++)
209 if(strcmp(message->headers[i].header, header) == 0)
210 return message->headers[i].value;
211 return NULL;
212 }
213
214
215 /* message_get_iter */
message_get_iter(Message * message,GtkTreeIter * iter)216 gboolean message_get_iter(Message * message, GtkTreeIter * iter)
217 {
218 GtkTreePath * path;
219
220 if(message->row == NULL)
221 return FALSE;
222 if((path = gtk_tree_row_reference_get_path(message->row)) == NULL)
223 return FALSE;
224 return gtk_tree_model_get_iter(GTK_TREE_MODEL(message->store), iter,
225 path);
226 }
227
228
229 /* message_get_store */
message_get_store(Message * message)230 GtkTreeStore * message_get_store(Message * message)
231 {
232 return message->store;
233 }
234
235
236 /* message_set_body */
message_set_body(Message * message,char const * buf,size_t cnt,gboolean append)237 int message_set_body(Message * message, char const * buf, size_t cnt,
238 gboolean append)
239 {
240 char * p;
241 GtkTextIter iter;
242
243 if(buf == NULL)
244 buf = "";
245 if(append != TRUE)
246 {
247 /* empty the message body */
248 free(message->body);
249 message->body = NULL;
250 message->body_cnt = 0;
251 gtk_text_buffer_set_text(message->text, "", 0);
252 }
253 if((p = realloc(message->body, (message->body_cnt + cnt) * sizeof(*p)))
254 == NULL)
255 return -1;
256 message->body = p;
257 memcpy(&message->body[message->body_cnt], buf, cnt);
258 message->body_cnt += cnt;
259 /* FIXME:
260 * - check encoding
261 * - parse MIME, etc... */
262 gtk_text_buffer_get_end_iter(message->text, &iter);
263 gtk_text_buffer_insert(message->text, &iter, buf, cnt);
264 return 0;
265 }
266
267
268 /* message_set_flag */
message_set_flag(Message * message,MailerMessageFlag flag)269 void message_set_flag(Message * message, MailerMessageFlag flag)
270 {
271 message->flags |= flag;
272 }
273
274
275 /* message_set_flags */
message_set_flags(Message * message,int flags)276 void message_set_flags(Message * message, int flags)
277 {
278 message->flags = flags;
279 }
280
281
282 /* message_set_header */
message_set_header(Message * message,char const * header)283 int message_set_header(Message * message, char const * header)
284 {
285 int ret;
286 size_t i;
287 char * p;
288
289 #ifdef DEBUG
290 fprintf(stderr, "DEBUG: %s(%p, \"%s\")\n", __func__, (void*)message,
291 header);
292 #endif
293 if(header == NULL)
294 return -1;
295 for(i = 0; header[i] != '\0' && header[i] != ':'; i++);
296 /* FIXME white-space is optional */
297 if(header[i] == '\0' || header[i + 1] != ' ')
298 /* XXX unstructured headers are not supported */
299 return -1;
300 if((p = malloc(i + 1)) == NULL)
301 return -1;
302 snprintf(p, i + 1, "%s", header);
303 ret = message_set_header_value(message, p, &header[i + 2]);
304 free(p);
305 return ret;
306 }
307
308
309 /* message_set_header_value */
message_set_header_value(Message * message,char const * header,char const * value)310 int message_set_header_value(Message * message, char const * header,
311 char const * value)
312 {
313 size_t i;
314 MessageHeader * p;
315 MailerHeaderColumn column;
316
317 #ifdef DEBUG
318 fprintf(stderr, "DEBUG: %s(%p, \"%s\", \"%s\")\n", __func__,
319 (void *)message, header, value);
320 #endif
321 /* FIXME remove the header when value == NULL */
322 for(i = 0; i < message->headers_cnt; i++)
323 if(strcmp(message->headers[i].header, header) == 0)
324 break;
325 if(i == message->headers_cnt)
326 {
327 /* the header was not found */
328 if(value == NULL)
329 return 0;
330 /* append the header */
331 if((p = realloc(message->headers, sizeof(*p)
332 * (message->headers_cnt + 1)))
333 == NULL)
334 return -1;
335 message->headers = p;
336 p = &message->headers[message->headers_cnt];
337 memset(p, 0, sizeof(*p));
338 if(_message_header_set(p, header, value) != 0)
339 return -1;
340 message->headers_cnt++;
341 }
342 else if(_message_header_set(&message->headers[i], NULL, value) != 0)
343 return -1;
344 /* FIXME parse/convert input */
345 for(i = 0; _message_columns[i].header != NULL; i++)
346 {
347 if(strcmp(_message_columns[i].header, header) != 0)
348 continue;
349 if((column = _message_columns[i].column) != 0)
350 _message_set(message, column, value, -1);
351 if(_message_columns[i].callback == NULL)
352 return 0;
353 return _message_columns[i].callback(message, value);
354 }
355 return 0;
356 }
357
358
359 /* message_set_read */
message_set_read(Message * message,gboolean read)360 void message_set_read(Message * message, gboolean read)
361 {
362 char const * status;
363 char * p;
364 size_t i;
365
366 if((status = message_get_header(message, "Status")) == NULL)
367 {
368 message_set_header(message, read ? "Status: RO" : "Status: O");
369 return;
370 }
371 if(!read)
372 {
373 if((p = strdup(status)) == NULL)
374 return; /* XXX report error */
375 for(i = 0; p[i] != '\0' && p[i] != 'R'; i++);
376 if(p[i] == 'R')
377 for(; p[i] != '\0'; i++)
378 p[i] = p[i + 1];
379 message_set_header_value(message, "Status", p);
380 free(p);
381 }
382 else if(strchr(status, 'R') == NULL)
383 {
384 i = strlen(status);
385 if((p = malloc(i + 2)) == NULL)
386 return; /* XXX report error */
387 snprintf(p, i + 2, "%c%s", 'R', status);
388 message_set_header_value(message, "Status", p);
389 free(p);
390 }
391 }
392
393
394 /* useful */
395 /* message_save
396 * XXX may not save the message exactly like the original */
397 static int _save_from(MailerMessage * message, FILE * fp);
398 static int _save_headers(MailerMessage * message, FILE * fp);
399 static int _save_body(MailerMessage * message, FILE * fp);
400
message_save(MailerMessage * message,char const * filename)401 int message_save(MailerMessage * message, char const * filename)
402 {
403 FILE * fp;
404
405 if((fp = fopen(filename, "w")) == NULL)
406 return -1;
407 if(_save_from(message, fp) != 0 || _save_headers(message, fp) != 0
408 || _save_body(message, fp) != 0)
409 {
410 fclose(fp);
411 return -1;
412 }
413 if(fclose(fp) != 0)
414 return -1;
415 return 0;
416 }
417
_save_from(MailerMessage * message,FILE * fp)418 static int _save_from(MailerMessage * message, FILE * fp)
419 {
420 char const * p;
421
422 if((p = message_get_header(message, "From")) == NULL)
423 p = "unknown-sender";
424 if(fputs("From ", fp) != 0 || fputs(p, fp) != 0)
425 return -1;
426 if((p = message_get_header(message, "Date")) != NULL)
427 if(fputs(" ", fp) != 0 || fputs(p, fp) != 0)
428 return -1;
429 if(fputs("\n", fp) != 0)
430 return -1;
431 return 0;
432 }
433
_save_headers(MailerMessage * message,FILE * fp)434 static int _save_headers(MailerMessage * message, FILE * fp)
435 {
436 size_t i;
437
438 /* output the headers */
439 for(i = 0; i < message->headers_cnt; i++)
440 if(fputs(message->headers[i].header, fp) != 0
441 || fputs(": ", fp) != 0
442 || fputs(message->headers[i].value, fp) != 0
443 || fputs("\n", fp) != 0)
444 return -1;
445 if(fputs("\n", fp) != 0)
446 return -1;
447 return 0;
448 }
449
_save_body(MailerMessage * message,FILE * fp)450 static int _save_body(MailerMessage * message, FILE * fp)
451 {
452 GtkTextIter start;
453 GtkTextIter end;
454 gchar * text;
455 int res;
456
457 /* output the body */
458 /* FIXME implement properly */
459 gtk_text_buffer_get_start_iter(message->text, &start);
460 gtk_text_buffer_get_end_iter(message->text, &end);
461 text = gtk_text_buffer_get_text(message->text, &start, &end, TRUE);
462 res = fputs(text, fp);
463 g_free(text);
464 return (res == 0) ? 0 : -1;
465 }
466
467
468 /* private */
469 /* functions */
470 /* accessors */
471 /* message_set */
_message_set(Message * message,...)472 static gboolean _message_set(Message * message, ...)
473 {
474 va_list ap;
475 GtkTreeIter iter;
476
477 if(message_get_iter(message, &iter) != TRUE)
478 return FALSE;
479 va_start(ap, message);
480 gtk_tree_store_set_valist(message->store, &iter, ap);
481 va_end(ap);
482 return TRUE;
483 }
484
485
486 /* message_set_date */
_message_set_date(Message * message,char const * date)487 static int _message_set_date(Message * message, char const * date)
488 {
489 struct tm tm;
490 time_t t;
491 char buf[32];
492
493 t = mailer_helper_get_date(date, &tm);
494 strftime(buf, sizeof(buf), "%d/%m/%Y %H:%M:%S", &tm);
495 _message_set(message, MHC_DATE, t, MHC_DATE_DISPLAY, buf, -1);
496 return 0;
497 }
498
499
500 /* message_set_from */
_message_set_from(Message * message,char const * from)501 static int _message_set_from(Message * message, char const * from)
502 {
503 char * name;
504 char * email;
505
506 if((email = mailer_helper_get_email(from)) == NULL)
507 return -1;
508 name = mailer_helper_get_name(from);
509 _message_set(message, MHC_FROM, (name != NULL) ? name : email,
510 MHC_FROM_EMAIL, email, -1);
511 free(email);
512 free(name);
513 return 0;
514 }
515
516
517 /* message_set_status */
_message_set_status(Message * message,char const * status)518 static int _message_set_status(Message * message, char const * status)
519 {
520 gboolean read;
521 GtkIconTheme * theme;
522 GdkPixbuf * pixbuf;
523
524 read = (status == NULL || strchr(status, 'R') != NULL) ? TRUE : FALSE;
525 theme = gtk_icon_theme_get_default();
526 pixbuf = gtk_icon_theme_load_icon(theme, read ? "mail-read"
527 : "mail-unread", 16, 0, NULL);
528 _message_set(message, MHC_READ, read, MHC_WEIGHT, (read)
529 ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD, MHC_ICON,
530 pixbuf, -1);
531 return 0;
532 }
533
534
535 /* message_set_to */
_message_set_to(Message * message,char const * to)536 static int _message_set_to(Message * message, char const * to)
537 {
538 char * name;
539 char * email;
540
541 if((email = mailer_helper_get_email(to)) == NULL)
542 return -1;
543 name = mailer_helper_get_name(to);
544 _message_set(message, MHC_TO, (name != NULL) ? name : email,
545 MHC_TO_EMAIL, email, -1);
546 free(email);
547 free(name);
548 return 0;
549 }
550
551
552 /* useful */
553 /* message headers */
554 /* message_header_set */
_message_header_set(MessageHeader * mh,char const * header,char const * value)555 static int _message_header_set(MessageHeader * mh, char const * header,
556 char const * value)
557 {
558 int ret = 0;
559 char * h = NULL;
560 char * v = NULL;
561
562 if(header != NULL && (h = strdup(header)) == NULL)
563 ret |= -1;
564 if(value != NULL && (v = strdup(value)) == NULL)
565 ret |= -1;
566 if(ret != 0)
567 return ret;
568 if(h != NULL)
569 {
570 free(mh->header);
571 mh->header = h;
572 }
573 if(v != NULL)
574 {
575 free(mh->value);
576 mh->value = v;
577 }
578 return 0;
579 }
580