1 /*
2  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
3  *
4  * This library is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation.
7  *
8  * This library is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11  * for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this library. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authors: Not Zed <notzed@lostzed.mmc.com.au>
17  */
18 
19 #include "evolution-data-server-config.h"
20 
21 #include <ctype.h>
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #ifndef _WIN32
31 #include <sys/uio.h>
32 #else
33 #include <winsock2.h>
34 #endif
35 
36 #include <glib/gstdio.h>
37 #include <glib/gi18n-lib.h>
38 
39 #include "camel-maildir-message-info.h"
40 #include "camel-maildir-store.h"
41 #include "camel-maildir-summary.h"
42 
43 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
44 
45 #define CAMEL_MAILDIR_SUMMARY_VERSION (0x2000)
46 
47 static CamelMessageInfo *
48 		message_info_new_from_headers	(CamelFolderSummary *,
49 						 const CamelNameValueArray *);
50 static gint	maildir_summary_load		(CamelLocalSummary *cls,
51 						 gint forceindex,
52 						 GError **error);
53 static gint	maildir_summary_check		(CamelLocalSummary *cls,
54 						 CamelFolderChangeInfo *changeinfo,
55 						 GCancellable *cancellable,
56 						 GError **error);
57 static gint	maildir_summary_sync		(CamelLocalSummary *cls,
58 						 gboolean expunge,
59 						 CamelFolderChangeInfo *changeinfo,
60 						 GCancellable *cancellable,
61 						 GError **error);
62 static CamelMessageInfo *
63 		maildir_summary_add		(CamelLocalSummary *cls,
64 						 CamelMimeMessage *msg,
65 						 const CamelMessageInfo *info,
66 						 CamelFolderChangeInfo *,
67 						 GError **error);
68 
69 static gchar *	maildir_summary_next_uid_string	(CamelFolderSummary *s);
70 static gint	maildir_summary_decode_x_evolution
71 						(CamelLocalSummary *cls,
72 						 const gchar *xev,
73 						 CamelMessageInfo *mi);
74 static gchar *	maildir_summary_encode_x_evolution
75 						(CamelLocalSummary *cls,
76 						 const CamelMessageInfo *mi);
77 
78 typedef struct _CamelMaildirMessageContentInfo CamelMaildirMessageContentInfo;
79 
80 struct _CamelMaildirSummaryPrivate {
81 	gchar *current_file;
82 	gchar *hostname;
83 	gchar filename_flag_sep;
84 
85 	GHashTable *load_map;
86 	GMutex summary_lock;
87 };
88 
89 struct _CamelMaildirMessageContentInfo {
90 	CamelMessageContentInfo info;
91 };
92 
G_DEFINE_TYPE_WITH_PRIVATE(CamelMaildirSummary,camel_maildir_summary,CAMEL_TYPE_LOCAL_SUMMARY)93 G_DEFINE_TYPE_WITH_PRIVATE (
94 	CamelMaildirSummary,
95 	camel_maildir_summary,
96 	CAMEL_TYPE_LOCAL_SUMMARY)
97 
98 static void
99 maildir_summary_finalize (GObject *object)
100 {
101 	CamelMaildirSummaryPrivate *priv;
102 
103 	priv = CAMEL_MAILDIR_SUMMARY (object)->priv;
104 
105 	g_free (priv->hostname);
106 	g_mutex_clear (&priv->summary_lock);
107 
108 	/* Chain up to parent's finalize() method. */
109 	G_OBJECT_CLASS (camel_maildir_summary_parent_class)->finalize (object);
110 }
111 
112 static void
camel_maildir_summary_class_init(CamelMaildirSummaryClass * class)113 camel_maildir_summary_class_init (CamelMaildirSummaryClass *class)
114 {
115 	GObjectClass *object_class;
116 	CamelFolderSummaryClass *folder_summary_class;
117 	CamelLocalSummaryClass *local_summary_class;
118 
119 	object_class = G_OBJECT_CLASS (class);
120 	object_class->finalize = maildir_summary_finalize;
121 
122 	folder_summary_class = CAMEL_FOLDER_SUMMARY_CLASS (class);
123 	folder_summary_class->message_info_type = CAMEL_TYPE_MAILDIR_MESSAGE_INFO;
124 	folder_summary_class->sort_by = "dreceived";
125 	folder_summary_class->collate = NULL;
126 	folder_summary_class->message_info_new_from_headers = message_info_new_from_headers;
127 	folder_summary_class->next_uid_string = maildir_summary_next_uid_string;
128 
129 	local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (class);
130 	local_summary_class->load = maildir_summary_load;
131 	local_summary_class->check = maildir_summary_check;
132 	local_summary_class->sync = maildir_summary_sync;
133 	local_summary_class->add = maildir_summary_add;
134 	local_summary_class->encode_x_evolution = maildir_summary_encode_x_evolution;
135 	local_summary_class->decode_x_evolution = maildir_summary_decode_x_evolution;
136 }
137 
138 static void
camel_maildir_summary_init(CamelMaildirSummary * maildir_summary)139 camel_maildir_summary_init (CamelMaildirSummary *maildir_summary)
140 {
141 	CamelFolderSummary *folder_summary;
142 	gchar hostname[256];
143 
144 	folder_summary = CAMEL_FOLDER_SUMMARY (maildir_summary);
145 
146 	maildir_summary->priv = camel_maildir_summary_get_instance_private (maildir_summary);
147 
148 	/* set unique file version */
149 	camel_folder_summary_set_version (folder_summary, camel_folder_summary_get_version (folder_summary) + CAMEL_MAILDIR_SUMMARY_VERSION);
150 
151 	if (gethostname (hostname, 256) == 0) {
152 		maildir_summary->priv->hostname = g_strdup (hostname);
153 	} else {
154 		maildir_summary->priv->hostname = g_strdup ("localhost");
155 	}
156 	g_mutex_init (&maildir_summary->priv->summary_lock);
157 }
158 
159 /**
160  * camel_maildir_summary_new:
161  * @folder: parent folder.
162  * @maildirdir: a maildir directory for the new summary
163  * @index: (nullable): an optional #CamelIndex to use, or %NULL
164  *
165  * Create a new CamelMaildirSummary object.
166  *
167  * Returns: (transfer full): A new #CamelMaildirSummary object
168  **/
169 CamelMaildirSummary *
camel_maildir_summary_new(struct _CamelFolder * folder,const gchar * maildirdir,CamelIndex * index,gchar filename_flag_sep)170 camel_maildir_summary_new (struct _CamelFolder *folder,
171 			   const gchar *maildirdir,
172 			   CamelIndex *index,
173 			   gchar filename_flag_sep)
174 {
175 	CamelMaildirSummary *o;
176 
177 	o = g_object_new (CAMEL_TYPE_MAILDIR_SUMMARY, "folder", folder, NULL);
178 
179 	o->priv->filename_flag_sep = filename_flag_sep;
180 
181 	if (folder) {
182 		CamelStore *parent_store;
183 
184 		parent_store = camel_folder_get_parent_store (folder);
185 		camel_db_set_collate (camel_store_get_db (parent_store), "dreceived", NULL, NULL);
186 
187 		if (!filename_flag_sep)
188 			o->priv->filename_flag_sep = camel_maildir_store_get_filename_flag_sep (CAMEL_MAILDIR_STORE (parent_store));
189 	}
190 
191 	if (!o->priv->filename_flag_sep)
192 		o->priv->filename_flag_sep = CAMEL_MAILDIR_FILENAME_FLAG_SEP;
193 
194 	camel_local_summary_construct ((CamelLocalSummary *) o, maildirdir, index);
195 	return o;
196 }
197 
198 /* Flag separator used for this summary in the filename. */
199 gchar
camel_maildir_summary_get_filename_flag_sep(CamelMaildirSummary * maildir_summary)200 camel_maildir_summary_get_filename_flag_sep (CamelMaildirSummary *maildir_summary)
201 {
202 	g_return_val_if_fail (CAMEL_IS_MAILDIR_SUMMARY (maildir_summary), CAMEL_MAILDIR_FILENAME_FLAG_SEP);
203 
204 	return maildir_summary->priv->filename_flag_sep;
205 }
206 
207 /* the 'standard' maildir flags.  should be defined in sorted order. */
208 static struct {
209 	gchar flag;
210 	guint32 flagbit;
211 } flagbits[] = {
212 	{ 'D', CAMEL_MESSAGE_DRAFT },
213 	{ 'F', CAMEL_MESSAGE_FLAGGED },
214 	/*{ 'P', CAMEL_MESSAGE_FORWARDED },*/
215 	{ 'R', CAMEL_MESSAGE_ANSWERED },
216 	{ 'S', CAMEL_MESSAGE_SEEN },
217 	{ 'T', CAMEL_MESSAGE_DELETED },
218 };
219 
220 /* convert the uid + flags into a unique:info maildir format */
221 gchar *
camel_maildir_summary_uid_and_flags_to_name(CamelMaildirSummary * maildir_summary,const gchar * uid,guint32 flags)222 camel_maildir_summary_uid_and_flags_to_name (CamelMaildirSummary *maildir_summary,
223 					     const gchar *uid,
224 					     guint32 flags)
225 {
226 	gchar *p, *buf, filename_flag_sep;
227 	gint i;
228 
229 	g_return_val_if_fail (uid != NULL, NULL);
230 
231 	filename_flag_sep = maildir_summary ? maildir_summary->priv->filename_flag_sep : CAMEL_MAILDIR_FILENAME_FLAG_SEP;
232 
233 	buf = g_alloca (strlen (uid) + 1 /* flag_sep */ + 2 /* "2," */ + G_N_ELEMENTS (flagbits) + 1);
234 	p = buf + sprintf (buf, "%s%c2,", uid, filename_flag_sep);
235 	for (i = 0; i < G_N_ELEMENTS (flagbits); i++) {
236 		if ((flags & flagbits[i].flagbit) != 0)
237 			*p++ = flagbits[i].flag;
238 	}
239 
240 	*p = 0;
241 
242 	return g_strdup (buf);
243 }
244 
245 gchar *
camel_maildir_summary_info_to_name(const CamelMessageInfo * info)246 camel_maildir_summary_info_to_name (const CamelMessageInfo *info)
247 {
248 	CamelFolderSummary *summary;
249 	gchar *res;
250 
251 	g_return_val_if_fail (CAMEL_IS_MESSAGE_INFO (info), NULL);
252 
253 	summary = camel_message_info_ref_summary (info);
254 
255 	res = camel_maildir_summary_uid_and_flags_to_name (CAMEL_MAILDIR_SUMMARY (summary),
256 		camel_message_info_get_uid (info),
257 		camel_message_info_get_flags (info));
258 
259 	g_clear_object (&summary);
260 
261 	return res;
262 }
263 
264 /* returns whether the @info changed */
265 gboolean
camel_maildir_summary_name_to_info(CamelMessageInfo * info,const gchar * name)266 camel_maildir_summary_name_to_info (CamelMessageInfo *info,
267 				    const gchar *name)
268 {
269 	CamelFolderSummary *summary;
270 	gchar *p, c, pattern[4];
271 	guint32 set = 0;	/* what we set */
272 	gint i;
273 
274 	summary = camel_message_info_ref_summary (info);
275 
276 	pattern[0] = camel_maildir_summary_get_filename_flag_sep (CAMEL_MAILDIR_SUMMARY (summary));
277 	pattern[1] = '2';
278 	pattern[2] = ',';
279 	pattern[3] = '\0';
280 
281 	g_clear_object (&summary);
282 
283 	p = strstr (name, pattern);
284 
285 	if (p) {
286 		guint32 flags;
287 
288 		flags = camel_message_info_get_flags (info);
289 
290 		p += 3;
291 		while ((c = *p++)) {
292 			/* we could assume that the flags are in order, but its just as easy not to require */
293 			for (i = 0; i < G_N_ELEMENTS (flagbits); i++) {
294 				if (flagbits[i].flag == c && (flags & flagbits[i].flagbit) == 0) {
295 					set |= flagbits[i].flagbit;
296 				}
297 			}
298 		}
299 
300 		/* changed? */
301 		if ((flags & set) != set) {
302 			return camel_message_info_set_flags (info, set, set);
303 		}
304 	}
305 
306 	return FALSE;
307 }
308 
309 /* for maildir, x-evolution isn't used, so dont try and get anything out of it */
maildir_summary_decode_x_evolution(CamelLocalSummary * cls,const gchar * xev,CamelMessageInfo * mi)310 static gint maildir_summary_decode_x_evolution (CamelLocalSummary *cls, const gchar *xev, CamelMessageInfo *mi)
311 {
312 	return -1;
313 }
314 
maildir_summary_encode_x_evolution(CamelLocalSummary * cls,const CamelMessageInfo * mi)315 static gchar *maildir_summary_encode_x_evolution (CamelLocalSummary *cls, const CamelMessageInfo *mi)
316 {
317 	return NULL;
318 }
319 
320 /* FIXME:
321  * both 'new' and 'add' will try and set the filename, this is not ideal ...
322 */
323 static CamelMessageInfo *
maildir_summary_add(CamelLocalSummary * cls,CamelMimeMessage * msg,const CamelMessageInfo * info,CamelFolderChangeInfo * changes,GError ** error)324 maildir_summary_add (CamelLocalSummary *cls,
325                      CamelMimeMessage *msg,
326                      const CamelMessageInfo *info,
327                      CamelFolderChangeInfo *changes,
328                      GError **error)
329 {
330 	CamelLocalSummaryClass *local_summary_class;
331 	CamelMessageInfo *mi;
332 
333 	/* Chain up to parent's add() method. */
334 	local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
335 	mi = local_summary_class->add (cls, msg, info, changes, error);
336 	if (mi) {
337 		if (info) {
338 			CamelMaildirMessageInfo *mdi = CAMEL_MAILDIR_MESSAGE_INFO (mi);
339 
340 			camel_maildir_message_info_take_filename (mdi, camel_maildir_summary_info_to_name (mi));
341 			d (printf ("Setting filename to %s\n", camel_maildir_message_info_get_filename (mdi)));
342 
343 			/* Inherit the Received date from the passed-in info only if it is set and
344 			   the new message info doesn't have it set or it's set to the default
345 			   value, derived from the message UID. */
346 			if (camel_message_info_get_date_received (info) > 0 &&
347 			    (camel_message_info_get_date_received (mi) <= 0 ||
348 			    (camel_message_info_get_uid (mi) &&
349 			     camel_message_info_get_date_received (mi) == strtoul (camel_message_info_get_uid (mi), NULL, 10))))
350 				camel_message_info_set_date_received (mi, camel_message_info_get_date_received (info));
351 		}
352 	}
353 
354 	return mi;
355 }
356 
357 static CamelMessageInfo *
message_info_new_from_headers(CamelFolderSummary * summary,const CamelNameValueArray * headers)358 message_info_new_from_headers (CamelFolderSummary *summary,
359 			       const CamelNameValueArray *headers)
360 {
361 	CamelMessageInfo *mi, *info;
362 	CamelMaildirSummary *mds = (CamelMaildirSummary *) summary;
363 	const gchar *uid;
364 
365 	mi = ((CamelFolderSummaryClass *) camel_maildir_summary_parent_class)->message_info_new_from_headers (summary, headers);
366 	/* assign the uid and new filename */
367 	if (mi) {
368 		uid = camel_message_info_get_uid (mi);
369 		if (uid == NULL || uid[0] == 0) {
370 			gchar *new_uid = camel_folder_summary_next_uid_string (summary);
371 
372 			camel_message_info_set_uid (mi, new_uid);
373 			g_free (new_uid);
374 		}
375 
376 		/* handle 'duplicates' */
377 		info = (uid && *uid) ? camel_folder_summary_peek_loaded (summary, uid) : NULL;
378 		if (info) {
379 			d (printf ("already seen uid '%s', just summarising instead\n", uid));
380 			g_clear_object (&mi);
381 			mi = info;
382 		}
383 
384 		if (camel_message_info_get_date_received (mi) <= 0) {
385 			/* with maildir we know the real received date, from the filename */
386 			camel_message_info_set_date_received (mi, strtoul (camel_message_info_get_uid (mi), NULL, 10));
387 		}
388 
389 		if (mds->priv->current_file) {
390 #if 0
391 			gchar *p1, *p2, *p3;
392 			gulong uid;
393 #endif
394 			/* if setting from a file, grab the flags from it */
395 			camel_maildir_message_info_take_filename (CAMEL_MAILDIR_MESSAGE_INFO (mi), g_strdup (mds->priv->current_file));
396 			camel_maildir_summary_name_to_info (mi, mds->priv->current_file);
397 
398 #if 0
399 			/* Actually, I dont think all this effort is worth it at all ... */
400 
401 			/* also, see if we can extract the next-id from tne name, and safe-if-fy ourselves against collisions */
402 			/* we check for something.something_number.something */
403 			p1 = strchr (mdi->filename, '.');
404 			if (p1) {
405 				p2 = strchr (p1 + 1, '.');
406 				p3 = strchr (p1 + 1, '_');
407 				if (p2 && p3 && p3 < p2) {
408 					uid = strtoul (p3 + 1, &p1, 10);
409 					if (p1 == p2 && uid > 0)
410 						camel_folder_summary_set_uid (s, uid);
411 				}
412 			}
413 #endif
414 		} else {
415 			/* if creating a file, set its name from the flags we have */
416 			camel_maildir_message_info_take_filename (CAMEL_MAILDIR_MESSAGE_INFO (mi), camel_maildir_summary_info_to_name (mi));
417 			d (printf ("Setting filename to %s\n", camel_maildir_message_info_get_filename (CAMEL_MAILDIR_MESSAGE_INFO (mi))));
418 		}
419 	}
420 
421 	return mi;
422 }
423 
424 static gchar *
maildir_summary_next_uid_string(CamelFolderSummary * s)425 maildir_summary_next_uid_string (CamelFolderSummary *s)
426 {
427 	CamelMaildirSummary *mds = (CamelMaildirSummary *) s;
428 
429 	d (printf ("next uid string called?\n"));
430 
431 	/* if we have a current file, then use that to get the uid */
432 	if (mds->priv->current_file) {
433 		gchar *cln;
434 
435 		cln = strchr (mds->priv->current_file, mds->priv->filename_flag_sep);
436 		if (cln)
437 			return g_strndup (mds->priv->current_file, cln - mds->priv->current_file);
438 		else
439 			return g_strdup (mds->priv->current_file);
440 	} else {
441 		/* the first would probably work, but just to be safe, check for collisions */
442 #if 0
443 		return g_strdup_printf ("%ld.%d_%u.%s", time (0), getpid (), camel_folder_summary_next_uid (s), mds->priv->hostname);
444 #else
445 		CamelLocalSummary *cls = (CamelLocalSummary *) s;
446 		gchar *name = NULL, *uid = NULL;
447 		struct stat st;
448 		gint retry = 0;
449 		guint32 nextuid = camel_folder_summary_next_uid (s);
450 
451 		/* we use time.pid_count.hostname */
452 		do {
453 			if (retry > 0) {
454 				g_free (name);
455 				g_free (uid);
456 				g_usleep (2 * G_USEC_PER_SEC);
457 			}
458 			uid = g_strdup_printf ("%" G_GINT64_FORMAT ".%d_%u.%s", (gint64) time (NULL), getpid (), nextuid, mds->priv->hostname);
459 			name = g_strdup_printf ("%s/tmp/%s", cls->folder_path, uid);
460 			retry++;
461 		} while (g_stat (name, &st) == 0 && retry < 3);
462 
463 		/* I dont know what we're supposed to do if it fails to find a unique name?? */
464 
465 		g_free (name);
466 		return uid;
467 #endif
468 	}
469 }
470 
471 static gint
maildir_summary_load(CamelLocalSummary * cls,gint forceindex,GError ** error)472 maildir_summary_load (CamelLocalSummary *cls,
473                       gint forceindex,
474                       GError **error)
475 {
476 	CamelLocalSummaryClass *local_summary_class;
477 	gchar *cur;
478 	DIR *dir;
479 	struct dirent *d;
480 	CamelMaildirSummary *mds = (CamelMaildirSummary *) cls;
481 	gchar *uid;
482 	CamelMemPool *pool;
483 	gint ret;
484 
485 	cur = g_strdup_printf ("%s/cur", cls->folder_path);
486 
487 	d (printf ("pre-loading uid <> filename map\n"));
488 
489 	dir = opendir (cur);
490 	if (dir == NULL) {
491 		g_set_error (
492 			error, G_IO_ERROR,
493 			g_io_error_from_errno (errno),
494 			_("Cannot open maildir directory path: %s: %s"),
495 			cls->folder_path, g_strerror (errno));
496 		g_free (cur);
497 		return -1;
498 	}
499 
500 	mds->priv->load_map = g_hash_table_new (g_str_hash, g_str_equal);
501 	pool = camel_mempool_new (1024, 512, CAMEL_MEMPOOL_ALIGN_BYTE);
502 
503 	while ((d = readdir (dir))) {
504 		if (d->d_name[0] == '.')
505 			continue;
506 
507 		/* map the filename -> uid */
508 		uid = strchr (d->d_name, mds->priv->filename_flag_sep);
509 		if (uid) {
510 			gint len = uid - d->d_name;
511 			uid = camel_mempool_alloc (pool, len + 1);
512 			memcpy (uid, d->d_name, len);
513 			uid[len] = 0;
514 			g_hash_table_insert (mds->priv->load_map, uid, camel_mempool_strdup (pool, d->d_name));
515 		} else {
516 			uid = camel_mempool_strdup (pool, d->d_name);
517 			g_hash_table_insert (mds->priv->load_map, uid, uid);
518 		}
519 	}
520 	closedir (dir);
521 	g_free (cur);
522 
523 	/* Chain up to parent's load() method. */
524 	local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
525 	ret = local_summary_class->load (cls, forceindex, error);
526 
527 	g_hash_table_destroy (mds->priv->load_map);
528 	mds->priv->load_map = NULL;
529 	camel_mempool_destroy (pool);
530 
531 	return ret;
532 }
533 
534 static gint
camel_maildir_summary_add(CamelLocalSummary * cls,const gchar * name,gint forceindex,GCancellable * cancellable)535 camel_maildir_summary_add (CamelLocalSummary *cls,
536                            const gchar *name,
537                            gint forceindex,
538                            GCancellable *cancellable)
539 {
540 	CamelMessageInfo *info;
541 	CamelFolderSummary *summary;
542 	CamelMaildirSummary *maildirs = (CamelMaildirSummary *) cls;
543 	gchar *filename = g_strdup_printf ("%s/cur/%s", cls->folder_path, name);
544 	gint fd;
545 	CamelMimeParser *mp;
546 
547 	d (printf ("summarising: %s\n", name));
548 
549 	summary = CAMEL_FOLDER_SUMMARY (cls);
550 
551 	fd = open (filename, O_RDONLY | O_LARGEFILE);
552 	if (fd == -1) {
553 		g_warning ("Cannot summarise/index: %s: %s", filename, g_strerror (errno));
554 		g_free (filename);
555 		return -1;
556 	}
557 	mp = camel_mime_parser_new ();
558 	camel_mime_parser_scan_from (mp, FALSE);
559 	camel_mime_parser_init_with_fd (mp, fd);
560 	if (cls->index && (forceindex || !camel_index_has_name (cls->index, name))) {
561 		d (printf ("forcing indexing of message content\n"));
562 		camel_folder_summary_set_index (summary, cls->index);
563 	} else {
564 		camel_folder_summary_set_index (summary, NULL);
565 	}
566 	maildirs->priv->current_file = (gchar *) name;
567 
568 	info = camel_folder_summary_info_new_from_parser (summary, mp);
569 	camel_folder_summary_add (summary, info, FALSE);
570 	g_clear_object (&info);
571 
572 	g_object_unref (mp);
573 	maildirs->priv->current_file = NULL;
574 	camel_folder_summary_set_index (summary, NULL);
575 	g_free (filename);
576 	return 0;
577 }
578 
579 struct _remove_data {
580 	CamelLocalSummary *cls;
581 	CamelFolderChangeInfo *changes;
582 	GList *removed_uids;
583 };
584 
585 static void
remove_summary(const gchar * uid,gpointer value,struct _remove_data * rd)586 remove_summary (const gchar *uid,
587                 gpointer value,
588                 struct _remove_data *rd)
589 {
590 	d (printf ("removing message %s from summary\n", uid));
591 	if (rd->cls->index)
592 		camel_index_delete_name (rd->cls->index, uid);
593 	if (rd->changes)
594 		camel_folder_change_info_remove_uid (rd->changes, uid);
595 	rd->removed_uids = g_list_prepend (rd->removed_uids, (gpointer) uid);
596 }
597 
598 static gint
maildir_summary_check(CamelLocalSummary * cls,CamelFolderChangeInfo * changes,GCancellable * cancellable,GError ** error)599 maildir_summary_check (CamelLocalSummary *cls,
600                        CamelFolderChangeInfo *changes,
601                        GCancellable *cancellable,
602                        GError **error)
603 {
604 	DIR *dir;
605 	struct dirent *d;
606 	gchar *p;
607 	CamelFolderSummary *s = (CamelFolderSummary *) cls;
608 	CamelMaildirSummary *mds;
609 	GHashTable *left;
610 	gint i, count, total;
611 	gint forceindex;
612 	gchar *new, *cur;
613 	gchar *uid;
614 	struct _remove_data rd = { cls, changes, NULL };
615 	GPtrArray *known_uids;
616 
617 	mds = CAMEL_MAILDIR_SUMMARY (s);
618 
619 	g_mutex_lock (&mds->priv->summary_lock);
620 
621 	new = g_strdup_printf ("%s/new", cls->folder_path);
622 	cur = g_strdup_printf ("%s/cur", cls->folder_path);
623 
624 	d (printf ("checking summary ...\n"));
625 
626 	camel_operation_push_message (
627 		cancellable, _("Checking folder consistency"));
628 
629 	/* scan the directory, check for mail files not in the index, or index entries that
630 	 * no longer exist */
631 	dir = opendir (cur);
632 	if (dir == NULL) {
633 		g_set_error (
634 			error, G_IO_ERROR,
635 			g_io_error_from_errno (errno),
636 			_("Cannot open maildir directory path: %s: %s"),
637 			cls->folder_path, g_strerror (errno));
638 		g_free (cur);
639 		g_free (new);
640 		camel_operation_pop_message (cancellable);
641 		g_mutex_unlock (&mds->priv->summary_lock);
642 		return -1;
643 	}
644 
645 	/* keeps track of all uid's that have not been processed */
646 	left = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) camel_pstring_free, NULL);
647 	known_uids = camel_folder_summary_get_array (s);
648 	forceindex = !known_uids || known_uids->len == 0;
649 	for (i = 0; known_uids && i < known_uids->len; i++) {
650 		const gchar *uid = g_ptr_array_index (known_uids, i);
651 		guint32 flags;
652 
653 		flags = camel_folder_summary_get_info_flags ((CamelFolderSummary *) cls, uid);
654 		if (flags != (~0)) {
655 			g_hash_table_insert (left, (gchar *) camel_pstring_strdup (uid), GUINT_TO_POINTER (flags));
656 		}
657 	}
658 
659 	/* joy, use this to pre-count the total, so we can report progress meaningfully */
660 	total = 0;
661 	count = 0;
662 	while (readdir (dir))
663 		total++;
664 	rewinddir (dir);
665 
666 	while ((d = readdir (dir))) {
667 		guint32 stored_flags = 0;
668 		gint pc;
669 
670 		/* Avoid a potential division by zero if the first loop
671 		 * (to calculate total) is executed on an empty
672 		 * directory, then the directory is populated before
673 		 * this loop is executed. */
674 		total = MAX (total, count + 1);
675 		pc = (total > 0) ? count * 100 / total : 0;
676 
677 		camel_operation_progress (cancellable, pc);
678 		count++;
679 
680 		/* FIXME: also run stat to check for regular file */
681 		p = d->d_name;
682 		if (p[0] == '.')
683 			continue;
684 
685 		/* map the filename -> uid */
686 		uid = strchr (d->d_name, mds->priv->filename_flag_sep);
687 		if (uid)
688 			uid = g_strndup (d->d_name, uid - d->d_name);
689 		else
690 			uid = g_strdup (d->d_name);
691 
692 		if (g_hash_table_contains (left, uid)) {
693 			stored_flags = GPOINTER_TO_UINT (g_hash_table_lookup (left, uid));
694 			g_hash_table_remove (left, uid);
695 		}
696 
697 		if (!camel_folder_summary_check_uid ((CamelFolderSummary *) cls, uid)) {
698 			/* must be a message incorporated by another client, this is not a 'recent' uid */
699 			if (camel_maildir_summary_add (cls, d->d_name, forceindex, cancellable) == 0)
700 				if (changes)
701 					camel_folder_change_info_add_uid (changes, uid);
702 		} else {
703 			CamelMaildirMessageInfo *mdi;
704 			CamelMessageInfo *info;
705 			gchar *expected_filename;
706 
707 			if (cls->index && (!camel_index_has_name (cls->index, uid))) {
708 				/* message_info_new will handle duplicates */
709 				camel_maildir_summary_add (cls, d->d_name, forceindex, cancellable);
710 			}
711 
712 			info = camel_folder_summary_peek_loaded ((CamelFolderSummary *) cls, uid);
713 			mdi = info ? CAMEL_MAILDIR_MESSAGE_INFO (info) : NULL;
714 
715 			expected_filename = camel_maildir_summary_uid_and_flags_to_name (mds, uid, stored_flags);
716 			if ((mdi && !camel_maildir_message_info_get_filename (mdi)) ||
717 			    !expected_filename ||
718 			    strcmp (expected_filename, d->d_name) != 0) {
719 				if (!mdi) {
720 					g_clear_object (&info);
721 					info = camel_folder_summary_get ((CamelFolderSummary *) cls, uid);
722 					mdi = info ? CAMEL_MAILDIR_MESSAGE_INFO (info) : NULL;
723 				}
724 
725 				g_warn_if_fail (mdi != NULL);
726 
727 				if (mdi)
728 					camel_maildir_message_info_set_filename (mdi, d->d_name);
729 			}
730 
731 			g_free (expected_filename);
732 			g_clear_object (&info);
733 		}
734 		g_free (uid);
735 	}
736 	closedir (dir);
737 	g_hash_table_foreach (left, (GHFunc) remove_summary, &rd);
738 
739 	if (rd.removed_uids)
740 		camel_folder_summary_remove_uids ((CamelFolderSummary *) cls, rd.removed_uids);
741 	g_list_free (rd.removed_uids);
742 
743 	/* Destroy the hash table only after the removed_uids GList is freed, because it has borrowed the UIDs */
744 	g_hash_table_destroy (left);
745 
746 	camel_operation_pop_message (cancellable);
747 
748 	camel_operation_push_message (
749 		cancellable, _("Checking for new messages"));
750 
751 	/* now, scan new for new messages, and copy them to cur, and so forth */
752 	dir = opendir (new);
753 	if (dir != NULL) {
754 		total = 0;
755 		count = 0;
756 		while (readdir (dir))
757 			total++;
758 		rewinddir (dir);
759 
760 		while ((d = readdir (dir))) {
761 			gchar *name, *newname, *destname, *destfilename;
762 			gchar *src, *dest;
763 			gint pc;
764 
765 			/* Avoid a potential division by zero if the first loop
766 			 * (to calculate total) is executed on an empty
767 			 * directory, then the directory is populated before
768 			 * this loop is executed. */
769 			total = MAX (total, count + 1);
770 			pc = (total > 0) ? count * 100 / total : 0;
771 
772 			camel_operation_progress (cancellable, pc);
773 			count++;
774 
775 			name = d->d_name;
776 			if (name[0] == '.')
777 				continue;
778 
779 			/* already in summary?  shouldn't happen, but just incase ... */
780 			if (camel_folder_summary_check_uid ((CamelFolderSummary *) cls, name)) {
781 				newname = destname = camel_folder_summary_next_uid_string (s);
782 			} else {
783 				gchar *nm;
784 				newname = g_strdup (name);
785 				nm = strrchr (newname, mds->priv->filename_flag_sep);
786 				if (nm)
787 					*nm = '\0';
788 				destname = newname;
789 			}
790 
791 			/* copy this to the destination folder, use 'standard' semantics for maildir info field */
792 			src = g_strdup_printf ("%s/%s", new, name);
793 			destfilename = g_strdup_printf ("%s%c2,", destname, mds->priv->filename_flag_sep);
794 			dest = g_strdup_printf ("%s/%s", cur, destfilename);
795 
796 			/* FIXME: This should probably use link/unlink */
797 
798 			if (g_rename (src, dest) == 0) {
799 				camel_maildir_summary_add (cls, destfilename, forceindex, cancellable);
800 				if (changes) {
801 					camel_folder_change_info_add_uid (changes, destname);
802 					camel_folder_change_info_recent_uid (changes, destname);
803 				}
804 			} else {
805 				/* else?  we should probably care about failures, but wont */
806 				g_warning ("Failed to move new maildir message %s to cur %s", src, dest);
807 			}
808 
809 			/* c strings are painful to work with ... */
810 			g_free (destfilename);
811 			g_free (newname);
812 			g_free (src);
813 			g_free (dest);
814 		}
815 
816 		camel_operation_pop_message (cancellable);
817 		closedir (dir);
818 	}
819 
820 	g_free (new);
821 	g_free (cur);
822 
823 	camel_folder_summary_free_array (known_uids);
824 	g_mutex_unlock (&mds->priv->summary_lock);
825 
826 	return 0;
827 }
828 
829 /* sync the summary with the ondisk files. */
830 static gint
maildir_summary_sync(CamelLocalSummary * cls,gboolean expunge,CamelFolderChangeInfo * changes,GCancellable * cancellable,GError ** error)831 maildir_summary_sync (CamelLocalSummary *cls,
832                       gboolean expunge,
833                       CamelFolderChangeInfo *changes,
834                       GCancellable *cancellable,
835                       GError **error)
836 {
837 	CamelLocalSummaryClass *local_summary_class;
838 	gint i;
839 	CamelMessageInfo *info;
840 	CamelMaildirMessageInfo *mdi;
841 	GList *removed_uids = NULL;
842 	gchar *name;
843 	struct stat st;
844 	GPtrArray *known_uids;
845 
846 	d (printf ("summary_sync(expunge=%s)\n", expunge?"true":"false"));
847 
848 	/* Check consistency on save only if not exiting the application */
849 	if (!camel_application_is_exiting &&
850 	    camel_local_summary_check (cls, changes, cancellable, error) == -1)
851 		return -1;
852 
853 	camel_operation_push_message (cancellable, _("Storing folder"));
854 
855 	known_uids = camel_folder_summary_get_array ((CamelFolderSummary *) cls);
856 	for (i = (known_uids ? known_uids->len : 0) - 1; i >= 0; i--) {
857 		const gchar *uid = g_ptr_array_index (known_uids, i);
858 		guint32 flags = 0;
859 
860 		camel_operation_progress (cancellable, (known_uids->len - i) * 100 / known_uids->len);
861 
862 		/* Message infos with folder-flagged flags are not removed from memory */
863 		info = camel_folder_summary_peek_loaded ((CamelFolderSummary *) cls, uid);
864 		mdi = info ? CAMEL_MAILDIR_MESSAGE_INFO (info) : NULL;
865 		if (!mdi) {
866 			flags = camel_folder_summary_get_info_flags ((CamelFolderSummary *) cls, uid);
867 			if (flags == (~0))
868 				flags = 0;
869 		}
870 
871 		if (expunge && (
872 		    (mdi && (camel_message_info_get_flags (info) & CAMEL_MESSAGE_DELETED) != 0) ||
873 		    (!mdi && (flags & CAMEL_MESSAGE_DELETED) != 0))) {
874 			const gchar *mdi_filename;
875 			gchar *tmp = NULL;
876 
877 			if (mdi) {
878 				mdi_filename = camel_maildir_message_info_get_filename (mdi);
879 			} else {
880 				tmp = camel_maildir_summary_uid_and_flags_to_name (CAMEL_MAILDIR_SUMMARY (cls), uid, flags);
881 				mdi_filename = tmp;
882 			}
883 
884 			name = g_strdup_printf ("%s/cur/%s", cls->folder_path, mdi_filename);
885 
886 			g_free (tmp);
887 
888 			d (printf ("deleting %s\n", name));
889 			if (unlink (name) == 0 || errno == ENOENT) {
890 
891 				/* FIXME: put this in folder_summary::remove()? */
892 				if (cls->index)
893 					camel_index_delete_name (cls->index, uid);
894 
895 				camel_folder_change_info_remove_uid (changes, uid);
896 				removed_uids = g_list_prepend (removed_uids, (gpointer) camel_pstring_strdup (uid));
897 			}
898 			g_free (name);
899 		} else if (mdi && camel_message_info_get_folder_flagged (info)) {
900 			gchar *newname = camel_maildir_summary_info_to_name (info);
901 			gchar *dest;
902 
903 			/* do we care about additional metainfo stored inside the message? */
904 			/* probably should all go in the filename? */
905 
906 			/* have our flags/ i.e. name changed? */
907 			if (strcmp (newname, camel_maildir_message_info_get_filename (mdi))) {
908 				name = g_strdup_printf ("%s/cur/%s", cls->folder_path, camel_maildir_message_info_get_filename (mdi));
909 				dest = g_strdup_printf ("%s/cur/%s", cls->folder_path, newname);
910 				if (g_rename (name, dest) == -1) {
911 					g_warning ("%s: Failed to rename '%s' to '%s': %s", G_STRFUNC, name, dest, g_strerror (errno));
912 				}
913 				if (g_stat (dest, &st) == -1) {
914 					/* we'll assume it didn't work, but dont change anything else */
915 				} else {
916 					/* TODO: If this is made mt-safe, then this code could be a problem, since
917 					 * the estrv is being modified.
918 					 * Sigh, this may mean the maildir name has to be cached another way */
919 					camel_maildir_message_info_set_filename (mdi, newname);
920 				}
921 				g_free (name);
922 				g_free (dest);
923 			}
924 
925 			g_free (newname);
926 
927 			/* strip FOLDER_MESSAGE_FLAGED, etc */
928 			camel_message_info_set_flags (info, 0xffff, camel_message_info_get_flags (info));
929 		}
930 		g_clear_object (&info);
931 	}
932 
933 	if (removed_uids) {
934 		camel_folder_summary_remove_uids (CAMEL_FOLDER_SUMMARY (cls), removed_uids);
935 		g_list_free_full (removed_uids, (GDestroyNotify) camel_pstring_free);
936 	}
937 
938 	camel_folder_summary_free_array (known_uids);
939 	camel_operation_pop_message (cancellable);
940 
941 	/* Chain up to parent's sync() method. */
942 	local_summary_class = CAMEL_LOCAL_SUMMARY_CLASS (camel_maildir_summary_parent_class);
943 	return local_summary_class->sync (cls, expunge, changes, cancellable, error);
944 }
945