a704db11cf626e6e6611b04ee8c021fa1869bfee
[claws.git] / src / mbox.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <fcntl.h>
32 #include <sys/file.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <errno.h>
36
37 #include "mbox.h"
38 #include "procmsg.h"
39 #include "folder.h"
40 #include "prefs_common.h"
41 #include "prefs_account.h"
42 #include "account.h"
43 #include "utils.h"
44 #include "filtering.h"
45 #include "alertpanel.h"
46 #include "statusbar.h"
47
48 #define MSGBUFSIZE      8192
49
50 #define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \
51 { \
52         lines++; \
53         if (fputs(s, tmp_fp) == EOF) { \
54                 g_warning("can't write to temporary file\n"); \
55                 fclose(tmp_fp); \
56                 fclose(mbox_fp); \
57                 g_unlink(tmp_file); \
58                 g_free(tmp_file); \
59                 return -1; \
60         } \
61 }
62
63 gint proc_mbox(FolderItem *dest, const gchar *mbox, gboolean apply_filter)
64 /* return values: -1 error, >=0 number of msgs added */
65 {
66         FILE *mbox_fp;
67         gchar buf[MSGBUFSIZE];
68         gchar *tmp_file;
69         gint msgs = 0;
70         gint lines;
71         MsgInfo *msginfo;
72         gboolean more;
73         GSList *to_filter = NULL, *filtered = NULL, *unfiltered = NULL, *cur, *to_add = NULL;
74         gboolean printed = FALSE;
75         FolderItem *dropfolder;
76
77         g_return_val_if_fail(dest != NULL, -1);
78         g_return_val_if_fail(mbox != NULL, -1);
79
80         debug_print("Getting messages from %s into %s...\n", mbox, dest->path);
81
82         if ((mbox_fp = g_fopen(mbox, "rb")) == NULL) {
83                 FILE_OP_ERROR(mbox, "fopen");
84                 alertpanel_error(_("Could not open mbox file:\n%s\n"), mbox);
85                 return -1;
86         }
87
88         /* ignore empty lines on the head */
89         do {
90                 if (fgets(buf, sizeof(buf), mbox_fp) == NULL) {
91                         g_warning("can't read mbox file.\n");
92                         fclose(mbox_fp);
93                         return -1;
94                 }
95         } while (buf[0] == '\n' || buf[0] == '\r');
96
97         if (strncmp(buf, "From ", 5) != 0) {
98                 g_warning("invalid mbox format: %s\n", mbox);
99                 fclose(mbox_fp);
100                 return -1;
101         }
102
103         tmp_file = get_tmp_file();
104
105         folder_item_update_freeze();
106
107         if (apply_filter)
108                 dropfolder = folder_get_default_processing();
109         else
110                 dropfolder = dest;
111         
112         do {
113                 FILE *tmp_fp;
114                 gint empty_lines;
115                 gint msgnum;
116                 
117                 if (msgs > 0 && msgs%500 == 0) {
118                         if (printed)
119                                 statusbar_pop_all();
120                         statusbar_print_all(_("Importing from mbox... (%d mails imported)"), msgs);
121                         printed=TRUE;
122                         GTK_EVENTS_FLUSH();
123                 }
124         
125                 if ((tmp_fp = g_fopen(tmp_file, "wb")) == NULL) {
126                         FILE_OP_ERROR(tmp_file, "fopen");
127                         g_warning("can't open temporary file\n");
128                         fclose(mbox_fp);
129                         g_free(tmp_file);
130                         return -1;
131                 }
132                 if (change_file_mode_rw(tmp_fp, tmp_file) < 0) {
133                         FILE_OP_ERROR(tmp_file, "chmod");
134                 }
135
136                 empty_lines = 0;
137                 lines = 0;
138                 more = FALSE;
139
140                 /* process all lines from mboxrc file */
141                 while (fgets(buf, sizeof(buf), mbox_fp) != NULL) {
142                         int offset;
143
144                         /* eof not reached, expect more lines */
145                         more = TRUE;
146
147                         /* eat empty lines */
148                         if (buf[0] == '\n' || buf[0] == '\r') {
149                                 empty_lines++;
150                                 continue;
151                         }
152
153                         /* From separator or quoted From */
154                         offset = 0;
155                         /* detect leading '>' char(s) */
156                         while ((buf[offset] == '>')) {
157                                 offset++;
158                         }
159                         if (!strncmp(buf+offset, "From ", 5)) {
160                                 /* From separator: */
161                                 if (offset == 0) {
162                                         /* expect next mbox item */
163                                         break;
164                                 }
165
166                                 /* quoted From: */
167                                 /* flush any eaten empty line */
168                                 if (empty_lines > 0) {
169                                         while (empty_lines-- > 0) {
170                                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
171                                 }
172                                         empty_lines = 0;
173                                 }
174                                 /* store the unquoted line */
175                                 FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1);
176                                 continue;
177                         }
178
179                         /* other line */
180                         /* flush any eaten empty line */
181                         if (empty_lines > 0) {                  
182                                 while (empty_lines-- > 0) {
183                                         FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
184                         }
185                                 empty_lines = 0;
186                         }
187                         /* store the line itself */
188                                         FPUTS_TO_TMP_ABORT_IF_FAIL(buf);
189                 }
190                 /* end of mbox item or end of mbox */
191
192                 /* flush any eaten empty line (but the last one) */
193                 if (empty_lines > 0) {
194                         while (--empty_lines > 0) {
195                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
196                         }
197                 }
198
199                 /* more emails to expect? */
200                 more = !feof(mbox_fp);
201
202                 /* warn if email part is empty (it's the minimum check 
203                    we can do */
204                 if (lines == 0) {
205                         g_warning("malformed mbox: %s: message %d is empty\n", mbox, msgs);
206                         fclose(tmp_fp);
207                         fclose(mbox_fp);
208                         g_unlink(tmp_file);
209                         return -1;
210                 }
211
212                 if (fclose(tmp_fp) == EOF) {
213                         FILE_OP_ERROR(tmp_file, "fclose");
214                         g_warning("can't write to temporary file\n");
215                         fclose(mbox_fp);
216                         g_unlink(tmp_file);
217                         g_free(tmp_file);
218                         return -1;
219                 }
220
221                 if (apply_filter) {
222                         if ((msgnum = folder_item_add_msg(dropfolder, tmp_file, NULL, TRUE)) < 0) {
223                                 fclose(mbox_fp);
224                                 g_unlink(tmp_file);
225                                 g_free(tmp_file);
226                                 return -1;
227                         }
228                         msginfo = folder_item_get_msginfo(dropfolder, msgnum);
229                         to_filter = g_slist_prepend(to_filter, msginfo);
230                 } else {
231                         MsgFileInfo *finfo = g_new0(MsgFileInfo, 1);
232                         finfo->file = tmp_file;
233                         
234                         to_add = g_slist_prepend(to_add, finfo);
235                         tmp_file = get_tmp_file();
236                         
237                         /* flush every 500 */
238                         if (msgs > 0 && msgs % 500 == 0) {
239                                 folder_item_add_msgs(dropfolder, to_add, TRUE);
240                                 procmsg_message_file_list_free(to_add);
241                                 to_add = NULL;
242                         }
243                 }
244                 msgs++;
245         } while (more);
246
247         if (printed)
248                 statusbar_pop_all();
249
250         if (apply_filter) {
251                 procmsg_msglist_filter(to_filter, NULL, &filtered, &unfiltered, TRUE);
252                 unfiltered = g_slist_reverse(unfiltered);
253                 folder_item_move_msgs(dest, unfiltered);
254                 for (cur = unfiltered; cur; cur = g_slist_next(cur)) {
255                         MsgInfo *info = (MsgInfo *)cur->data;
256                         procmsg_msginfo_free(info);
257                 }
258
259                 filtered = g_slist_reverse(filtered);
260                 filtering_move_and_copy_msgs(filtered);
261                 for (cur = filtered; cur; cur = g_slist_next(cur)) {
262                         MsgInfo *info = (MsgInfo *)cur->data;
263                         procmsg_msginfo_free(info);
264                 }
265
266                 g_slist_free(unfiltered);
267                 g_slist_free(filtered);
268                 g_slist_free(to_filter);
269         } else if (to_add) {
270                 folder_item_add_msgs(dropfolder, to_add, TRUE);
271                 procmsg_message_file_list_free(to_add);
272                 to_add = NULL;
273         }
274
275         folder_item_update_thaw();
276         
277         g_free(tmp_file);
278         fclose(mbox_fp);
279         debug_print("%d messages found.\n", msgs);
280
281         return msgs;
282 }
283
284 gint lock_mbox(const gchar *base, LockType type)
285 {
286 #ifdef G_OS_UNIX
287         gint retval = 0;
288
289         if (type == LOCK_FILE) {
290                 gchar *lockfile, *locklink;
291                 gint retry = 0;
292                 FILE *lockfp;
293
294                 lockfile = g_strdup_printf("%s.%d", base, getpid());
295                 if ((lockfp = g_fopen(lockfile, "wb")) == NULL) {
296                         FILE_OP_ERROR(lockfile, "fopen");
297                         g_warning("can't create lock file %s\n", lockfile);
298                         g_warning("use 'flock' instead of 'file' if possible.\n");
299                         g_free(lockfile);
300                         return -1;
301                 }
302
303                 fprintf(lockfp, "%d\n", getpid());
304                 fclose(lockfp);
305
306                 locklink = g_strconcat(base, ".lock", NULL);
307                 while (link(lockfile, locklink) < 0) {
308                         FILE_OP_ERROR(lockfile, "link");
309                         if (retry >= 5) {
310                                 g_warning("can't create %s\n", lockfile);
311                                 g_unlink(lockfile);
312                                 g_free(lockfile);
313                                 return -1;
314                         }
315                         if (retry == 0)
316                                 g_warning("mailbox is owned by another"
317                                             " process, waiting...\n");
318                         retry++;
319                         sleep(5);
320                 }
321                 g_unlink(lockfile);
322                 g_free(lockfile);
323         } else if (type == LOCK_FLOCK) {
324                 gint lockfd;
325                 gboolean fcntled = FALSE;
326 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
327                 struct flock fl;
328                 fl.l_type = F_WRLCK;
329                 fl.l_whence = SEEK_SET;
330                 fl.l_start = 0;
331                 fl.l_len = 0;
332 #endif
333
334 #if HAVE_FLOCK
335                 if ((lockfd = open(base, O_RDWR)) < 0) {
336 #else
337                 if ((lockfd = open(base, O_RDWR)) < 0) {
338 #endif
339                         FILE_OP_ERROR(base, "open");
340                         return -1;
341                 }
342                 
343 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
344                 if (fcntl(lockfd, F_SETLK, &fl) == -1) {
345                         g_warning("can't fnctl %s (%s)", base, strerror(errno));
346                         return -1;
347                 } else {
348                         fcntled = TRUE;
349                 }
350 #endif
351
352 #if HAVE_FLOCK
353                 if (flock(lockfd, LOCK_EX|LOCK_NB) < 0 && !fcntled) {
354                         perror("flock");
355 #else
356 #if HAVE_LOCKF
357                 if (lockf(lockfd, F_TLOCK, 0) < 0 && !fcntled) {
358                         perror("lockf");
359 #else
360                 {
361 #endif
362 #endif /* HAVE_FLOCK */
363                         g_warning("can't lock %s\n", base);
364                         if (close(lockfd) < 0)
365                                 perror("close");
366                         return -1;
367                 }
368                 retval = lockfd;
369         } else {
370                 g_warning("invalid lock type\n");
371                 return -1;
372         }
373
374         return retval;
375 #else
376         return -1;
377 #endif /* G_OS_UNIX */
378 }
379
380 gint unlock_mbox(const gchar *base, gint fd, LockType type)
381 {
382         if (type == LOCK_FILE) {
383                 gchar *lockfile;
384
385                 lockfile = g_strconcat(base, ".lock", NULL);
386                 if (g_unlink(lockfile) < 0) {
387                         FILE_OP_ERROR(lockfile, "unlink");
388                         g_free(lockfile);
389                         return -1;
390                 }
391                 g_free(lockfile);
392
393                 return 0;
394         } else if (type == LOCK_FLOCK) {
395                 gboolean fcntled = FALSE;
396 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
397                 struct flock fl;
398                 fl.l_type = F_UNLCK;
399                 fl.l_whence = SEEK_SET;
400                 fl.l_start = 0;
401                 fl.l_len = 0;
402
403                 if (fcntl(fd, F_SETLK, &fl) == -1) {
404                         g_warning("can't fnctl %s", base);
405                         return -1;
406                 } else {
407                         fcntled = TRUE;
408                 }
409 #endif
410 #if HAVE_FLOCK
411                 if (flock(fd, LOCK_UN) < 0 && !fcntled) {
412                         perror("flock");
413 #else
414 #if HAVE_LOCKF
415                 if (lockf(fd, F_ULOCK, 0) < 0 && !fcntled) {
416                         perror("lockf");
417 #else
418                 {
419 #endif
420 #endif /* HAVE_FLOCK */
421                         g_warning("can't unlock %s\n", base);
422                         if (close(fd) < 0)
423                                 perror("close");
424                         return -1;
425                 }
426
427                 if (close(fd) < 0) {
428                         perror("close");
429                         return -1;
430                 }
431
432                 return 0;
433         }
434
435         g_warning("invalid lock type\n");
436         return -1;
437 }
438
439 gint copy_mbox(const gchar *src, const gchar *dest)
440 {
441         return copy_file(src, dest, TRUE);
442 }
443
444 void empty_mbox(const gchar *mbox)
445 {
446         FILE *fp;
447
448         if ((fp = g_fopen(mbox, "wb")) == NULL) {
449                 FILE_OP_ERROR(mbox, "fopen");
450                 g_warning("can't truncate mailbox to zero.\n");
451                 return;
452         }
453         fclose(fp);
454 }
455
456 gint export_list_to_mbox(GSList *mlist, const gchar *mbox)
457 /* return values: -2 skipped, -1 error, 0 OK */
458 {
459         GSList *cur;
460         MsgInfo *msginfo;
461         FILE *msg_fp;
462         FILE *mbox_fp;
463         gchar buf[BUFFSIZE];
464         gint msgs = 1, total = g_slist_length(mlist);
465         if (g_file_test(mbox, G_FILE_TEST_EXISTS) == TRUE) {
466                 if (alertpanel_full(_("Overwrite mbox file"),
467                                         _("This file already exists. Do you want to overwrite it?"),
468                                         GTK_STOCK_CANCEL, _("Overwrite"), NULL, FALSE,
469                                         NULL, ALERT_WARNING, G_ALERTDEFAULT)
470                                 != G_ALERTALTERNATE) {
471                         return -2;
472                 }
473         }
474
475         if ((mbox_fp = g_fopen(mbox, "wb")) == NULL) {
476                 FILE_OP_ERROR(mbox, "fopen");
477                 alertpanel_error(_("Could not create mbox file:\n%s\n"), mbox);
478                 return -1;
479         }
480
481         statusbar_print_all(_("Exporting to mbox..."));
482         for (cur = mlist; cur != NULL; cur = cur->next) {
483                 int len;
484                 msginfo = (MsgInfo *)cur->data;
485
486                 msg_fp = procmsg_open_message(msginfo);
487                 if (!msg_fp) {
488                         continue;
489                 }
490
491                 strncpy2(buf,
492                          msginfo->from ? msginfo->from :
493                          cur_account && cur_account->address ?
494                          cur_account->address : "unknown",
495                          sizeof(buf));
496                 extract_address(buf);
497
498                 fprintf(mbox_fp, "From %s %s",
499                         buf, ctime(&msginfo->date_t));
500
501                 buf[0] = '\0';
502                 
503                 /* write email to mboxrc */
504                 while (fgets(buf, sizeof(buf), msg_fp) != NULL) {
505                         /* quote any From, >From, >>From, etc., according to mbox format specs */
506                         int offset;
507
508                         offset = 0;
509                         /* detect leading '>' char(s) */
510                         while ((buf[offset] == '>')) {
511                                 offset++;
512                         }
513                         if (!strncmp(buf+offset, "From ", 5))
514                                 fputc('>', mbox_fp);
515                         fputs(buf, mbox_fp);
516                 }
517
518                 /* force last line to end w/ a newline */
519                 len = strlen(buf);
520                 if (len > 0) {
521                         len--;
522                         if ((buf[len] != '\n') && (buf[len] != '\r'))
523                                 fputc('\n', mbox_fp);
524                 }
525
526                 /* add a trailing empty line */
527                 fputc('\n', mbox_fp);
528
529                 fclose(msg_fp);
530                 statusbar_progress_all(msgs++,total, 500);
531                 if (msgs%500 == 0)
532                         GTK_EVENTS_FLUSH();
533         }
534         statusbar_progress_all(0,0,0);
535         statusbar_pop_all();
536
537         fclose(mbox_fp);
538
539         return 0;
540 }
541
542 /* read all messages in SRC, and store them into one MBOX file. */
543 /* return values: -2 skipped, -1 error, 0 OK */
544 gint export_to_mbox(FolderItem *src, const gchar *mbox)
545 {
546         GSList *mlist;
547         gint ret;
548         
549         g_return_val_if_fail(src != NULL, -1);
550         g_return_val_if_fail(src->folder != NULL, -1);
551         g_return_val_if_fail(mbox != NULL, -1);
552
553         debug_print("Exporting messages from %s into %s...\n",
554                     src->path, mbox);
555
556         mlist = folder_item_get_msg_list(src);
557
558         ret = export_list_to_mbox(mlist, mbox);
559
560         procmsg_msg_list_free(mlist);
561
562         return ret;
563 }