remove all gtk3 conditionals
[claws.git] / src / mbox.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2015 Hiroyuki Yamamoto and the Claws Mail 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 3 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, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #include "claws-features.h"
23 #endif
24
25
26 #include <stdio.h>
27
28 #ifdef USE_PTHREAD
29 #include <pthread.h>
30 #endif
31
32 #include "defs.h"
33 #include <glib.h>
34 #include <glib/gi18n.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <fcntl.h>
39 #include <sys/file.h>
40 #include <ctype.h>
41 #include <time.h>
42 #include <errno.h>
43
44 #ifdef G_OS_WIN32
45 #  include <w32lib.h>
46 #endif
47
48 #include "mbox.h"
49 #include "procmsg.h"
50 #include "folder.h"
51 #include "prefs_common.h"
52 #include "prefs_account.h"
53 #include "account.h"
54 #include "utils.h"
55 #include "filtering.h"
56 #include "alertpanel.h"
57 #include "statusbar.h"
58 #include "file-utils.h"
59
60 #define MESSAGEBUFSIZE  8192
61
62 #define FPUTS_TO_TMP_ABORT_IF_FAIL(s) \
63 { \
64         lines++; \
65         if (claws_fputs(s, tmp_fp) == EOF) { \
66                 g_warning("can't write to temporary file"); \
67                 claws_fclose(tmp_fp); \
68                 claws_fclose(mbox_fp); \
69                 claws_unlink(tmp_file); \
70                 g_free(tmp_file); \
71                 return -1; \
72         } \
73 }
74
75 gint proc_mbox(FolderItem *dest, const gchar *mbox, gboolean apply_filter,
76                PrefsAccount *account)
77 /* return values: -1 error, >=0 number of msgs added */
78 {
79         FILE *mbox_fp;
80         gchar buf[MESSAGEBUFSIZE];
81         gchar *tmp_file;
82         gint msgs = 0;
83         gint lines;
84         MsgInfo *msginfo;
85         gboolean more;
86         GSList *to_filter = NULL, *filtered = NULL, *unfiltered = NULL, *cur, *to_add = NULL;
87         gboolean printed = FALSE;
88         FolderItem *dropfolder;
89
90         cm_return_val_if_fail(dest != NULL, -1);
91         cm_return_val_if_fail(mbox != NULL, -1);
92
93         debug_print("Getting messages from %s into %s...\n", mbox, dest->path);
94
95         if ((mbox_fp = claws_fopen(mbox, "rb")) == NULL) {
96                 FILE_OP_ERROR(mbox, "claws_fopen");
97                 alertpanel_error(_("Could not open mbox file:\n%s\n"), mbox);
98                 return -1;
99         }
100
101         /* ignore empty lines on the head */
102         do {
103                 if (claws_fgets(buf, sizeof(buf), mbox_fp) == NULL) {
104                         g_warning("can't read mbox file.");
105                         claws_fclose(mbox_fp);
106                         return -1;
107                 }
108         } while (buf[0] == '\n' || buf[0] == '\r');
109
110         if (strncmp(buf, "From ", 5) != 0) {
111                 g_warning("invalid mbox format: %s", mbox);
112                 claws_fclose(mbox_fp);
113                 return -1;
114         }
115
116         tmp_file = get_tmp_file();
117
118         folder_item_update_freeze();
119
120         if (apply_filter)
121                 dropfolder = folder_get_default_processing(account->account_id);
122         else
123                 dropfolder = dest;
124         
125         do {
126                 FILE *tmp_fp;
127                 gint empty_lines;
128                 gint msgnum;
129                 
130                 if (msgs > 0 && msgs%500 == 0) {
131                         if (printed)
132                                 statusbar_pop_all();
133                         statusbar_print_all(
134                                         ngettext("Importing from mbox... (%d mail imported)",
135                                                 "Importing from mbox... (%d mails imported)", msgs), msgs);
136                         printed=TRUE;
137                         GTK_EVENTS_FLUSH();
138                 }
139         
140                 if ((tmp_fp = claws_fopen(tmp_file, "wb")) == NULL) {
141                         FILE_OP_ERROR(tmp_file, "claws_fopen");
142                         g_warning("can't open temporary file");
143                         claws_fclose(mbox_fp);
144                         g_free(tmp_file);
145                         return -1;
146                 }
147                 if (change_file_mode_rw(tmp_fp, tmp_file) < 0) {
148                         FILE_OP_ERROR(tmp_file, "chmod");
149                 }
150
151                 empty_lines = 0;
152                 lines = 0;
153
154                 /* process all lines from mboxrc file */
155                 while (claws_fgets(buf, sizeof(buf), mbox_fp) != NULL) {
156                         int offset;
157
158                         /* eat empty lines */
159                         if (buf[0] == '\n' || buf[0] == '\r') {
160                                 empty_lines++;
161                                 continue;
162                         }
163
164                         /* From separator or quoted From */
165                         offset = 0;
166                         /* detect leading '>' char(s) */
167                         while (buf[offset] == '>') {
168                                 offset++;
169                         }
170                         if (!strncmp(buf+offset, "From ", 5)) {
171                                 /* From separator: */
172                                 if (offset == 0) {
173                                         /* expect next mbox item */
174                                         break;
175                                 }
176
177                                 /* quoted From: */
178                                 /* flush any eaten empty line */
179                                 if (empty_lines > 0) {
180                                         while (empty_lines-- > 0) {
181                                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
182                                 }
183                                         empty_lines = 0;
184                                 }
185                                 /* store the unquoted line */
186                                 FPUTS_TO_TMP_ABORT_IF_FAIL(buf + 1);
187                                 continue;
188                         }
189
190                         /* other line */
191                         /* flush any eaten empty line */
192                         if (empty_lines > 0) {                  
193                                 while (empty_lines-- > 0) {
194                                         FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
195                         }
196                                 empty_lines = 0;
197                         }
198                         /* store the line itself */
199                                         FPUTS_TO_TMP_ABORT_IF_FAIL(buf);
200                 }
201                 /* end of mbox item or end of mbox */
202
203                 /* flush any eaten empty line (but the last one) */
204                 if (empty_lines > 0) {
205                         while (--empty_lines > 0) {
206                                 FPUTS_TO_TMP_ABORT_IF_FAIL("\n");
207                         }
208                 }
209
210                 /* more emails to expect? */
211                 more = !claws_feof(mbox_fp);
212
213                 /* warn if email part is empty (it's the minimum check 
214                    we can do */
215                 if (lines == 0) {
216                         g_warning("malformed mbox: %s: message %d is empty", mbox, msgs);
217                         claws_fclose(tmp_fp);
218                         claws_fclose(mbox_fp);
219                         claws_unlink(tmp_file);
220                         return -1;
221                 }
222
223                 if (claws_safe_fclose(tmp_fp) == EOF) {
224                         FILE_OP_ERROR(tmp_file, "claws_fclose");
225                         g_warning("can't write to temporary file");
226                         claws_fclose(mbox_fp);
227                         claws_unlink(tmp_file);
228                         g_free(tmp_file);
229                         return -1;
230                 }
231
232                 if (apply_filter) {
233                         if ((msgnum = folder_item_add_msg(dropfolder, tmp_file, NULL, TRUE)) < 0) {
234                                 claws_fclose(mbox_fp);
235                                 claws_unlink(tmp_file);
236                                 g_free(tmp_file);
237                                 return -1;
238                         }
239                         msginfo = folder_item_get_msginfo(dropfolder, msgnum);
240                         to_filter = g_slist_prepend(to_filter, msginfo);
241                 } else {
242                         MsgFileInfo *finfo = g_new0(MsgFileInfo, 1);
243                         finfo->file = tmp_file;
244                         
245                         to_add = g_slist_prepend(to_add, finfo);
246                         tmp_file = get_tmp_file();
247                         
248                         /* flush every 500 */
249                         if (msgs > 0 && msgs % 500 == 0) {
250                                 folder_item_add_msgs(dropfolder, to_add, TRUE);
251                                 procmsg_message_file_list_free(to_add);
252                                 to_add = NULL;
253                         }
254                 }
255                 msgs++;
256         } while (more);
257
258         if (printed)
259                 statusbar_pop_all();
260
261         if (apply_filter) {
262
263                 folder_item_set_batch(dropfolder, FALSE);
264                 procmsg_msglist_filter(to_filter, account, 
265                                 &filtered, &unfiltered, TRUE);
266                 folder_item_set_batch(dropfolder, TRUE);
267
268                 filtering_move_and_copy_msgs(to_filter);
269                 for (cur = filtered; cur; cur = g_slist_next(cur)) {
270                         MsgInfo *info = (MsgInfo *)cur->data;
271                         procmsg_msginfo_free(&info);
272                 }
273
274                 unfiltered = g_slist_reverse(unfiltered);
275                 if (unfiltered) {
276                         folder_item_move_msgs(dest, unfiltered);
277                         for (cur = unfiltered; cur; cur = g_slist_next(cur)) {
278                                 MsgInfo *info = (MsgInfo *)cur->data;
279                                 procmsg_msginfo_free(&info);
280                         }
281                 }
282
283                 g_slist_free(unfiltered);
284                 g_slist_free(filtered);
285                 g_slist_free(to_filter);
286         } else if (to_add) {
287                 folder_item_add_msgs(dropfolder, to_add, TRUE);
288                 procmsg_message_file_list_free(to_add);
289                 to_add = NULL;
290         }
291
292         folder_item_update_thaw();
293         
294         g_free(tmp_file);
295         claws_fclose(mbox_fp);
296         debug_print("%d messages found.\n", msgs);
297
298         return msgs;
299 }
300
301 gint lock_mbox(const gchar *base, LockType type)
302 {
303 #ifdef G_OS_UNIX
304         gint retval = 0;
305
306         if (type == LOCK_FILE) {
307                 gchar *lockfile, *locklink;
308                 gint retry = 0;
309                 FILE *lockfp;
310
311                 lockfile = g_strdup_printf("%s.%d", base, getpid());
312                 if ((lockfp = claws_fopen(lockfile, "wb")) == NULL) {
313                         FILE_OP_ERROR(lockfile, "claws_fopen");
314                         g_warning("can't create lock file '%s', use 'flock' instead of 'file' if possible.", lockfile);
315                         g_free(lockfile);
316                         return -1;
317                 }
318
319                 if (fprintf(lockfp, "%d\n", getpid()) < 0) {
320                         FILE_OP_ERROR(lockfile, "fprintf");
321                         g_free(lockfile);
322                         claws_fclose(lockfp);
323                         return -1;
324                 }
325
326                 if (claws_safe_fclose(lockfp) == EOF) {
327                         FILE_OP_ERROR(lockfile, "claws_fclose");
328                         g_free(lockfile);
329                         return -1;
330                 }
331
332                 locklink = g_strconcat(base, ".lock", NULL);
333                 while (link(lockfile, locklink) < 0) {
334                         FILE_OP_ERROR(lockfile, "link");
335                         if (retry >= 5) {
336                                 g_warning("can't create '%s'", lockfile);
337                                 claws_unlink(lockfile);
338                                 g_free(lockfile);
339                                 return -1;
340                         }
341                         if (retry == 0)
342                                 g_warning("mailbox is owned by another"
343                                           " process, waiting...");
344                         retry++;
345                         sleep(5);
346                 }
347                 claws_unlink(lockfile);
348                 g_free(lockfile);
349         } else if (type == LOCK_FLOCK) {
350                 gint lockfd;
351                 gboolean fcntled = FALSE;
352 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
353                 struct flock fl;
354                 fl.l_type = F_WRLCK;
355                 fl.l_whence = SEEK_SET;
356                 fl.l_start = 0;
357                 fl.l_len = 0;
358 #endif
359
360 #if HAVE_FLOCK
361                 if ((lockfd = g_open(base, O_RDWR, 0)) < 0) {
362 #else
363                 if ((lockfd = g_open(base, O_RDWR, 0)) < 0) {
364 #endif
365                         FILE_OP_ERROR(base, "open");
366                         return -1;
367                 }
368                 
369 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
370                 if (fcntl(lockfd, F_SETLK, &fl) == -1) {
371                         g_warning("can't fnctl %s (%s)", base, g_strerror(errno));
372                         close(lockfd);
373                         return -1;
374                 } else {
375                         fcntled = TRUE;
376                 }
377 #endif
378
379 #if HAVE_FLOCK
380                 if (flock(lockfd, LOCK_EX|LOCK_NB) < 0 && !fcntled) {
381                         perror("flock");
382 #else
383 #if HAVE_LOCKF
384                 if (lockf(lockfd, F_TLOCK, 0) < 0 && !fcntled) {
385                         perror("lockf");
386 #else
387                 {
388 #endif
389 #endif /* HAVE_FLOCK */
390                         g_warning("can't lock %s", base);
391                         if (close(lockfd) < 0)
392                                 perror("close");
393                         return -1;
394                 }
395                 retval = lockfd;
396         } else {
397                 g_warning("invalid lock type");
398                 return -1;
399         }
400
401         return retval;
402 #else
403         return -1;
404 #endif /* G_OS_UNIX */
405 }
406
407 gint unlock_mbox(const gchar *base, gint fd, LockType type)
408 {
409         if (type == LOCK_FILE) {
410                 gchar *lockfile;
411
412                 lockfile = g_strconcat(base, ".lock", NULL);
413                 if (claws_unlink(lockfile) < 0) {
414                         FILE_OP_ERROR(lockfile, "unlink");
415                         g_free(lockfile);
416                         return -1;
417                 }
418                 g_free(lockfile);
419
420                 return 0;
421         } else if (type == LOCK_FLOCK) {
422 #if HAVE_FCNTL_H && !defined(G_OS_WIN32)
423                 gboolean fcntled = FALSE;
424                 struct flock fl;
425                 fl.l_type = F_UNLCK;
426                 fl.l_whence = SEEK_SET;
427                 fl.l_start = 0;
428                 fl.l_len = 0;
429
430                 if (fcntl(fd, F_SETLK, &fl) == -1) {
431                         g_warning("can't fnctl %s", base);
432                 } else {
433                         fcntled = TRUE;
434                 }
435 #endif
436 #if HAVE_FLOCK
437                 if (flock(fd, LOCK_UN) < 0 && !fcntled) {
438                         perror("flock");
439 #else
440 #if HAVE_LOCKF
441                 if (lockf(fd, F_ULOCK, 0) < 0 && !fcntled) {
442                         perror("lockf");
443 #else
444                 {
445 #endif
446 #endif /* HAVE_FLOCK */
447                         g_warning("can't unlock %s", base);
448                         if (close(fd) < 0)
449                                 perror("close");
450                         return -1;
451                 }
452
453                 if (close(fd) < 0) {
454                         perror("close");
455                         return -1;
456                 }
457
458                 return 0;
459         }
460
461         g_warning("invalid lock type");
462         return -1;
463 }
464
465 gint copy_mbox(gint srcfd, const gchar *dest)
466 {
467         FILE *dest_fp;
468         ssize_t n_read;
469         gchar buf[BUFSIZ];
470         gboolean err = FALSE;
471         int save_errno = 0;
472
473         if (srcfd < 0) {
474                 return -1;
475         }
476
477         if ((dest_fp = claws_fopen(dest, "wb")) == NULL) {
478                 FILE_OP_ERROR(dest, "claws_fopen");
479                 return -1;
480         }
481
482         if (change_file_mode_rw(dest_fp, dest) < 0) {
483                 FILE_OP_ERROR(dest, "chmod");
484                 g_warning("can't change file mode");
485         }
486
487         while ((n_read = read(srcfd, buf, sizeof(buf))) > 0) {
488                 if (claws_fwrite(buf, 1, n_read, dest_fp) < n_read) {
489                         g_warning("writing to %s failed.", dest);
490                         claws_fclose(dest_fp);
491                         claws_unlink(dest);
492                         return -1;
493                 }
494         }
495
496         if (save_errno != 0) {
497                 g_warning("error %d reading mbox: %s", save_errno,
498                                 g_strerror(save_errno));
499                 err = TRUE;
500         }
501
502         if (claws_safe_fclose(dest_fp) == EOF) {
503                 FILE_OP_ERROR(dest, "claws_fclose");
504                 err = TRUE;
505         }
506
507         if (err) {
508                 claws_unlink(dest);
509                 return -1;
510         }
511
512         return 0;
513 }
514
515 void empty_mbox(const gchar *mbox)
516 {
517         FILE *fp;
518
519         if ((fp = claws_fopen(mbox, "wb")) == NULL) {
520                 FILE_OP_ERROR(mbox, "claws_fopen");
521                 g_warning("can't truncate mailbox to zero.");
522                 return;
523         }
524         claws_safe_fclose(fp);
525 }
526
527 gint export_list_to_mbox(GSList *mlist, const gchar *mbox)
528 /* return values: -2 skipped, -1 error, 0 OK */
529 {
530         GSList *cur;
531         MsgInfo *msginfo;
532         FILE *msg_fp;
533         FILE *mbox_fp;
534         gchar buf[BUFFSIZE];
535         int err = 0;
536
537         gint msgs = 1, total = g_slist_length(mlist);
538         if (g_file_test(mbox, G_FILE_TEST_EXISTS) == TRUE) {
539                 if (alertpanel_full(_("Overwrite mbox file"),
540                                         _("This file already exists. Do you want to overwrite it?"),
541                                         GTK_STOCK_CANCEL, _("Overwrite"), NULL,
542                                         ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING)
543                                 != G_ALERTALTERNATE) {
544                         return -2;
545                 }
546         }
547
548         if ((mbox_fp = claws_fopen(mbox, "wb")) == NULL) {
549                 FILE_OP_ERROR(mbox, "claws_fopen");
550                 alertpanel_error(_("Could not create mbox file:\n%s\n"), mbox);
551                 return -1;
552         }
553
554         statusbar_print_all(_("Exporting to mbox..."));
555         for (cur = mlist; cur != NULL; cur = cur->next) {
556                 int len;
557                 gchar buft[BUFFSIZE];
558                 msginfo = (MsgInfo *)cur->data;
559
560                 msg_fp = procmsg_open_message(msginfo);
561                 if (!msg_fp) {
562                         continue;
563                 }
564
565                 strncpy2(buf,
566                          msginfo->from ? msginfo->from :
567                          cur_account && cur_account->address ?
568                          cur_account->address : "unknown",
569                          sizeof(buf));
570                 extract_address(buf);
571
572                 if (fprintf(mbox_fp, "From %s %s",
573                         buf, ctime_r(&msginfo->date_t, buft)) < 0) {
574                         err = -1;
575                         claws_fclose(msg_fp);
576                         goto out;
577                 }
578
579                 buf[0] = '\0';
580                 
581                 /* write email to mboxrc */
582                 while (claws_fgets(buf, sizeof(buf), msg_fp) != NULL) {
583                         /* quote any From, >From, >>From, etc., according to mbox format specs */
584                         int offset;
585
586                         offset = 0;
587                         /* detect leading '>' char(s) */
588                         while (buf[offset] == '>') {
589                                 offset++;
590                         }
591                         if (!strncmp(buf+offset, "From ", 5)) {
592                                 if (claws_fputc('>', mbox_fp) == EOF) {
593                                         err = -1;
594                                         claws_fclose(msg_fp);
595                                         goto out;
596                                 }
597                         }
598                         if (claws_fputs(buf, mbox_fp) == EOF) {
599                                 err = -1;
600                                 claws_fclose(msg_fp);
601                                 goto out;
602                         }
603                 }
604
605                 /* force last line to end w/ a newline */
606                 len = strlen(buf);
607                 if (len > 0) {
608                         len--;
609                         if ((buf[len] != '\n') && (buf[len] != '\r')) {
610                                 if (claws_fputc('\n', mbox_fp) == EOF) {
611                                         err = -1;
612                                         claws_fclose(msg_fp);
613                                         goto out;
614                                 }
615                         }
616                 }
617
618                 /* add a trailing empty line */
619                 if (claws_fputc('\n', mbox_fp) == EOF) {
620                         err = -1;
621                         claws_fclose(msg_fp);
622                         goto out;
623                 }
624
625                 claws_safe_fclose(msg_fp);
626                 statusbar_progress_all(msgs++,total, 500);
627                 if (msgs%500 == 0)
628                         GTK_EVENTS_FLUSH();
629         }
630
631 out:
632         statusbar_progress_all(0,0,0);
633         statusbar_pop_all();
634
635         claws_safe_fclose(mbox_fp);
636
637         return err;
638 }
639
640 /* read all messages in SRC, and store them into one MBOX file. */
641 /* return values: -2 skipped, -1 error, 0 OK */
642 gint export_to_mbox(FolderItem *src, const gchar *mbox)
643 {
644         GSList *mlist;
645         gint ret;
646         
647         cm_return_val_if_fail(src != NULL, -1);
648         cm_return_val_if_fail(src->folder != NULL, -1);
649         cm_return_val_if_fail(mbox != NULL, -1);
650
651         debug_print("Exporting messages from %s into %s...\n",
652                     src->path, mbox);
653
654         mlist = folder_item_get_msg_list(src);
655
656         folder_item_update_freeze();
657         ret = export_list_to_mbox(mlist, mbox);
658         folder_item_update_thaw();
659
660         procmsg_msg_list_free(mlist);
661
662         return ret;
663 }