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