NC_ not working here, remove it
[claws.git] / src / procmsg.c
blob36f5b251402b91366f8a95ffea1ff3a64f324b96
1 /*
2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto
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.
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.
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/>.
19 #include "config.h"
20 #include "defs.h"
22 #include <glib.h>
23 #include <glib/gi18n.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <math.h>
29 #include "main.h"
30 #include "utils.h"
31 #include "procmsg.h"
32 #include "procheader.h"
33 #include "send_message.h"
34 #include "procmime.h"
35 #include "statusbar.h"
36 #include "prefs_filtering.h"
37 #include "filtering.h"
38 #include "folder.h"
39 #include "foldersel.h"
40 #include "prefs_common.h"
41 #include "account.h"
42 #include "alertpanel.h"
43 #include "news.h"
44 #include "hooks.h"
45 #include "msgcache.h"
46 #include "partial_download.h"
47 #include "mainwindow.h"
48 #include "summaryview.h"
49 #include "log.h"
50 #include "tags.h"
51 #include "timing.h"
52 #include "inc.h"
53 #include "privacy.h"
54 #include "file-utils.h"
56 extern SessionStats session_stats;
58 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
59 FolderItem *queue, gint msgnum, gboolean *queued_removed);
60 static void procmsg_update_unread_children (MsgInfo *info,
61 gboolean newly_marked);
62 enum
64 Q_SENDER = 0,
65 Q_SMTPSERVER = 1,
66 Q_RECIPIENTS = 2,
67 Q_NEWSGROUPS = 3,
68 Q_MAIL_ACCOUNT_ID = 4,
69 Q_NEWS_ACCOUNT_ID = 5,
70 Q_SAVE_COPY_FOLDER = 6,
71 Q_REPLY_MESSAGE_ID = 7,
72 Q_FWD_MESSAGE_ID = 8,
73 Q_PRIVACY_SYSTEM = 9,
74 Q_ENCRYPT = 10,
75 Q_ENCRYPT_DATA = 11,
76 Q_CLAWS_HDRS = 12,
77 Q_PRIVACY_SYSTEM_OLD = 13,
78 Q_ENCRYPT_OLD = 14,
79 Q_ENCRYPT_DATA_OLD = 15,
80 Q_CLAWS_HDRS_OLD = 16,
83 void procmsg_msg_list_free(GSList *mlist)
85 GSList *cur;
86 MsgInfo *msginfo;
88 for (cur = mlist; cur != NULL; cur = cur->next) {
89 msginfo = (MsgInfo *)cur->data;
90 procmsg_msginfo_free(&msginfo);
92 g_slist_free(mlist);
95 MsgNumberList *procmsg_get_number_list_for_msgs(MsgInfoList *msglist)
97 GSList *cur = NULL;
98 GSList *nums = NULL;
100 for (cur = msglist; cur; cur = cur->next) {
101 MsgInfo *msg = (MsgInfo *)cur->data;
102 nums = g_slist_prepend(nums, GUINT_TO_POINTER(msg->msgnum));
105 return g_slist_reverse(nums);
108 struct MarkSum {
109 gint *new_msgs;
110 gint *unread_msgs;
111 gint *total_msgs;
112 gint *min;
113 gint *max;
114 gint first;
117 /* CLAWS subject threading:
119 in the first round it inserts subject lines in a
120 hashtable (subject <-> node)
122 the second round finishes the threads by attaching
123 matching subject lines to the one found in the
124 hashtable. will use the oldest node with the same
125 subject that is not more then thread_by_subject_max_age
126 days old (see subject_hashtable_lookup)
129 static void subject_hashtable_insert(GHashTable *hashtable, GNode *node)
131 gchar *subject;
132 MsgInfo *msginfo;
133 GSList *list = NULL;
135 cm_return_if_fail(hashtable != NULL);
136 cm_return_if_fail(node != NULL);
137 msginfo = (MsgInfo *) node->data;
138 cm_return_if_fail(msginfo != NULL);
140 subject = msginfo->subject;
141 if (subject == NULL)
142 return;
144 subject += subject_get_prefix_length(subject);
146 list = g_hash_table_lookup(hashtable, subject);
147 list = g_slist_prepend(list, node);
148 g_hash_table_insert(hashtable, subject, list);
151 static GNode *subject_hashtable_lookup(GHashTable *hashtable, MsgInfo *msginfo)
153 gchar *subject;
154 GSList *list, *cur;
155 GNode *node = NULL, *hashtable_node = NULL;
156 gint prefix_length;
157 MsgInfo *hashtable_msginfo = NULL, *best_msginfo = NULL;
158 gboolean match;
160 cm_return_val_if_fail(hashtable != NULL, NULL);
162 subject = msginfo->subject;
163 if (subject == NULL)
164 return NULL;
165 prefix_length = subject_get_prefix_length(subject);
166 if (prefix_length <= 0)
167 return NULL;
168 subject += prefix_length;
170 list = g_hash_table_lookup(hashtable, subject);
171 if (list == NULL)
172 return NULL;
174 /* check all nodes with the same subject to find the best parent */
175 for (cur = list; cur; cur = cur->next) {
176 hashtable_node = (GNode *)cur->data;
177 hashtable_msginfo = (MsgInfo *) hashtable_node->data;
178 match = FALSE;
180 /* best node should be the oldest in the found nodes */
181 /* parent node must not be older then msginfo */
182 if ((hashtable_msginfo->date_t < msginfo->date_t) &&
183 ((best_msginfo == NULL) ||
184 (best_msginfo->date_t > hashtable_msginfo->date_t)))
185 match = TRUE;
187 /* parent node must not be more then thread_by_subject_max_age
188 days older then msginfo */
189 if (fabs(difftime(msginfo->date_t, hashtable_msginfo->date_t)) >
190 prefs_common.thread_by_subject_max_age * 3600 * 24)
191 match = FALSE;
193 /* can add new tests for all matching
194 nodes found by subject */
196 if (match) {
197 node = hashtable_node;
198 best_msginfo = hashtable_msginfo;
202 return node;
205 static void subject_hashtable_free(gpointer key, gpointer value, gpointer data)
207 g_slist_free(value);
210 /* return the reversed thread tree */
211 GNode *procmsg_get_thread_tree(GSList *mlist)
213 GNode *root, *parent, *node, *next;
214 GHashTable *msgid_table;
215 GHashTable *subject_hashtable = NULL;
216 MsgInfo *msginfo;
217 const gchar *msgid;
218 GSList *reflist;
219 START_TIMING("");
220 root = g_node_new(NULL);
221 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
223 if (prefs_common.thread_by_subject) {
224 subject_hashtable = g_hash_table_new(g_str_hash, g_str_equal);
227 for (; mlist != NULL; mlist = mlist->next) {
228 msginfo = (MsgInfo *)mlist->data;
229 parent = root;
231 if (msginfo->inreplyto) {
232 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
233 if (parent == NULL) {
234 parent = root;
237 node = g_node_insert_data_before
238 (parent, parent == root ? parent->children : NULL,
239 msginfo);
240 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
241 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
243 /* CLAWS: add subject to hashtable (without prefix) */
244 if (prefs_common.thread_by_subject) {
245 subject_hashtable_insert(subject_hashtable, node);
249 /* complete the unfinished threads */
250 for (node = root->children; node != NULL; ) {
251 next = node->next;
252 msginfo = (MsgInfo *)node->data;
253 parent = NULL;
255 if (msginfo->inreplyto)
256 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
258 /* try looking for the indirect parent */
259 if (!parent && msginfo->references) {
260 for (reflist = msginfo->references;
261 reflist != NULL; reflist = reflist->next)
262 if ((parent = g_hash_table_lookup
263 (msgid_table, reflist->data)) != NULL)
264 break;
267 /* node should not be the parent, and node should not
268 be an ancestor of parent (circular reference) */
269 if (parent && parent != node &&
270 !g_node_is_ancestor(node, parent)) {
271 g_node_unlink(node);
272 g_node_insert_before
273 (parent, parent->children, node);
276 node = next;
279 if (prefs_common.thread_by_subject) {
280 START_TIMING("thread by subject");
281 for (node = root->children; node && node != NULL;) {
282 next = node->next;
283 msginfo = (MsgInfo *) node->data;
285 parent = subject_hashtable_lookup(subject_hashtable, msginfo);
287 /* the node may already be threaded by IN-REPLY-TO, so go up
288 * in the tree to
289 find the parent node */
290 if (parent != NULL) {
291 if (g_node_is_ancestor(node, parent))
292 parent = NULL;
293 if (parent == node)
294 parent = NULL;
297 if (parent) {
298 g_node_unlink(node);
299 g_node_append(parent, node);
302 node = next;
304 END_TIMING();
307 if (prefs_common.thread_by_subject)
309 g_hash_table_foreach(subject_hashtable, subject_hashtable_free, NULL);
310 g_hash_table_destroy(subject_hashtable);
313 g_hash_table_destroy(msgid_table);
314 END_TIMING();
315 return root;
318 gint procmsg_move_messages(GSList *mlist)
320 GSList *cur, *movelist = NULL;
321 MsgInfo *msginfo;
322 FolderItem *dest = NULL;
323 gint retval = 0;
324 gboolean finished = TRUE;
325 if (!mlist) return 0;
327 folder_item_update_freeze();
329 next_folder:
330 for (cur = mlist; cur != NULL; cur = cur->next) {
331 msginfo = (MsgInfo *)cur->data;
332 if (!msginfo->to_folder) {
333 continue;
334 } else {
335 finished = FALSE;
337 if (!dest) {
338 dest = msginfo->to_folder;
339 movelist = g_slist_prepend(movelist, msginfo);
340 } else if (dest == msginfo->to_folder) {
341 movelist = g_slist_prepend(movelist, msginfo);
342 } else {
343 continue;
345 procmsg_msginfo_set_to_folder(msginfo, NULL);
347 if (movelist) {
348 movelist = g_slist_reverse(movelist);
349 retval |= folder_item_move_msgs(dest, movelist);
350 g_slist_free(movelist);
351 movelist = NULL;
353 if (finished == FALSE) {
354 finished = TRUE;
355 dest = NULL;
356 goto next_folder;
359 folder_item_update_thaw();
360 return retval;
363 void procmsg_copy_messages(GSList *mlist)
365 GSList *cur, *copylist = NULL;
366 MsgInfo *msginfo;
367 FolderItem *dest = NULL;
368 gboolean finished = TRUE;
369 if (!mlist) return;
371 folder_item_update_freeze();
373 next_folder:
374 for (cur = mlist; cur != NULL; cur = cur->next) {
375 msginfo = (MsgInfo *)cur->data;
376 if (!msginfo->to_folder) {
377 continue;
378 } else {
379 finished = FALSE;
381 if (!dest) {
382 dest = msginfo->to_folder;
383 copylist = g_slist_prepend(copylist, msginfo);
384 } else if (dest == msginfo->to_folder) {
385 copylist = g_slist_prepend(copylist, msginfo);
386 } else {
387 continue;
389 procmsg_msginfo_set_to_folder(msginfo, NULL);
391 if (copylist) {
392 copylist = g_slist_reverse(copylist);
393 folder_item_copy_msgs(dest, copylist);
394 g_slist_free(copylist);
395 copylist = NULL;
397 if (finished == FALSE) {
398 finished = TRUE;
399 dest = NULL;
400 goto next_folder;
403 folder_item_update_thaw();
406 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
408 cm_return_val_if_fail(msginfo != NULL, NULL);
410 return folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
413 gchar *procmsg_get_message_file(MsgInfo *msginfo)
415 gchar *filename = NULL;
417 cm_return_val_if_fail(msginfo != NULL, NULL);
419 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
420 if (!filename)
421 debug_print("can't fetch message %d\n", msginfo->msgnum);
423 return filename;
426 gchar *procmsg_get_message_file_full(MsgInfo *msginfo, gboolean headers, gboolean body)
428 gchar *filename = NULL;
430 cm_return_val_if_fail(msginfo != NULL, NULL);
432 filename = folder_item_fetch_msg_full(msginfo->folder, msginfo->msgnum,
433 headers, body);
434 if (!filename)
435 debug_print("can't fetch message %d\n", msginfo->msgnum);
437 return filename;
440 GSList *procmsg_get_message_file_list(GSList *mlist)
442 GSList *file_list = NULL;
443 MsgInfo *msginfo;
444 MsgFileInfo *fileinfo;
445 gchar *file;
447 while (mlist != NULL) {
448 msginfo = (MsgInfo *)mlist->data;
449 file = procmsg_get_message_file(msginfo);
450 if (!file) {
451 procmsg_message_file_list_free(file_list);
452 return NULL;
454 fileinfo = g_new(MsgFileInfo, 1);
455 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
456 fileinfo->file = file;
457 fileinfo->flags = g_new(MsgFlags, 1);
458 *fileinfo->flags = msginfo->flags;
459 file_list = g_slist_prepend(file_list, fileinfo);
460 mlist = mlist->next;
463 file_list = g_slist_reverse(file_list);
465 return file_list;
468 void procmsg_message_file_list_free(MsgInfoList *file_list)
470 GSList *cur;
471 MsgFileInfo *fileinfo;
473 for (cur = file_list; cur != NULL; cur = cur->next) {
474 fileinfo = (MsgFileInfo *)cur->data;
475 procmsg_msginfo_free(&(fileinfo->msginfo));
476 g_free(fileinfo->file);
477 g_free(fileinfo->flags);
478 g_free(fileinfo);
481 g_slist_free(file_list);
484 FILE *procmsg_open_message(MsgInfo *msginfo, gboolean skip_special_headers)
486 FILE *fp;
487 gchar *file;
489 cm_return_val_if_fail(msginfo != NULL, NULL);
491 file = procmsg_get_message_file_path(msginfo);
492 cm_return_val_if_fail(file != NULL, NULL);
494 if (!is_file_exist(file)) {
495 g_free(file);
496 file = procmsg_get_message_file(msginfo);
497 if (!file)
498 return NULL;
501 if ((fp = claws_fopen(file, "rb")) == NULL) {
502 FILE_OP_ERROR(file, "claws_fopen");
503 g_free(file);
504 return NULL;
507 g_free(file);
509 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags) ||
510 skip_special_headers == TRUE) {
511 gchar buf[BUFFSIZE];
513 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
514 /* new way */
515 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
516 strlen("X-Claws-End-Special-Headers:"))) ||
517 (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
518 strlen("X-Sylpheed-End-Special-Headers:"))))
519 break;
520 /* old way */
521 if (buf[0] == '\r' || buf[0] == '\n') break;
522 /* from other mailers */
523 if (!strncmp(buf, "Date: ", 6)
524 || !strncmp(buf, "To: ", 4)
525 || !strncmp(buf, "From: ", 6)
526 || !strncmp(buf, "Subject: ", 9)) {
527 rewind(fp);
528 break;
533 return fp;
536 gboolean procmsg_msg_exist(MsgInfo *msginfo)
538 gboolean ret;
540 if (!msginfo) return FALSE;
542 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
544 return ret;
547 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
548 PrefsFilterType type)
550 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
551 {"X-ML-Name:", NULL, TRUE},
552 {"X-List:", NULL, TRUE},
553 {"X-Mailing-list:", NULL, TRUE},
554 {"List-Id:", NULL, TRUE},
555 {"X-Sequence:", NULL, TRUE},
556 {"Sender:", NULL, TRUE},
557 {"List-Post:", NULL, TRUE},
558 {NULL, NULL, FALSE}};
559 enum
561 H_X_BEENTHERE = 0,
562 H_X_ML_NAME = 1,
563 H_X_LIST = 2,
564 H_X_MAILING_LIST = 3,
565 H_LIST_ID = 4,
566 H_X_SEQUENCE = 5,
567 H_SENDER = 6,
568 H_LIST_POST = 7
571 FILE *fp;
573 cm_return_if_fail(msginfo != NULL);
574 cm_return_if_fail(header != NULL);
575 cm_return_if_fail(key != NULL);
577 *header = NULL;
578 *key = NULL;
580 switch (type) {
581 case FILTER_BY_NONE:
582 return;
583 case FILTER_BY_AUTO:
584 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL)
585 return;
586 procheader_get_header_fields(fp, hentry);
587 claws_fclose(fp);
589 #define SET_FILTER_KEY(hstr, idx) \
591 *header = g_strdup(hstr); \
592 *key = hentry[idx].body; \
593 hentry[idx].body = NULL; \
596 if (hentry[H_LIST_ID].body != NULL) {
597 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
598 extract_list_id_str(*key);
599 } else if (hentry[H_X_BEENTHERE].body != NULL) {
600 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
601 } else if (hentry[H_X_ML_NAME].body != NULL) {
602 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
603 } else if (hentry[H_X_LIST].body != NULL) {
604 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
605 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
606 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
607 } else if (hentry[H_X_SEQUENCE].body != NULL) {
608 gchar *p;
610 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
611 p = *key;
612 while (*p != '\0') {
613 while (*p != '\0' && !g_ascii_isspace(*p)) p++;
614 while (g_ascii_isspace(*p)) p++;
615 if (g_ascii_isdigit(*p)) {
616 *p = '\0';
617 break;
620 g_strstrip(*key);
621 } else if (hentry[H_SENDER].body != NULL) {
622 SET_FILTER_KEY("header \"Sender\"", H_SENDER);
623 } else if (hentry[H_LIST_POST].body != NULL) {
624 SET_FILTER_KEY("header \"List-Post\"", H_LIST_POST);
625 } else if (msginfo->to) {
626 *header = g_strdup("to");
627 *key = g_strdup(msginfo->to);
628 } else if (msginfo->subject) {
629 *header = g_strdup("subject");
630 *key = g_strdup(msginfo->subject);
633 g_free(hentry[H_X_BEENTHERE].body);
634 hentry[H_X_BEENTHERE].body = NULL;
635 g_free(hentry[H_X_ML_NAME].body);
636 hentry[H_X_ML_NAME].body = NULL;
637 g_free(hentry[H_X_LIST].body);
638 hentry[H_X_LIST].body = NULL;
639 g_free(hentry[H_X_MAILING_LIST].body);
640 hentry[H_X_MAILING_LIST].body = NULL;
641 g_free(hentry[H_LIST_ID].body);
642 hentry[H_LIST_ID].body = NULL;
643 g_free(hentry[H_SENDER].body);
644 hentry[H_SENDER].body = NULL;
645 g_free(hentry[H_LIST_POST].body);
646 hentry[H_LIST_POST].body = NULL;
648 break;
649 case FILTER_BY_FROM:
650 *header = g_strdup("from");
651 *key = g_strdup(msginfo->from);
652 break;
653 case FILTER_BY_TO:
654 *header = g_strdup("to");
655 *key = g_strdup(msginfo->to);
656 break;
657 case FILTER_BY_SUBJECT:
658 *header = g_strdup("subject");
659 *key = g_strdup(msginfo->subject);
660 break;
661 case FILTER_BY_SENDER:
662 if ((fp = procmsg_open_message(msginfo, FALSE)) == NULL)
663 return;
664 procheader_get_header_fields(fp, hentry);
665 fclose(fp);
667 if (hentry[H_SENDER].body != NULL)
668 SET_FILTER_KEY("header \"Sender\"", H_SENDER);
670 g_free(hentry[H_X_BEENTHERE].body);
671 hentry[H_X_BEENTHERE].body = NULL;
672 g_free(hentry[H_X_ML_NAME].body);
673 hentry[H_X_ML_NAME].body = NULL;
674 g_free(hentry[H_X_LIST].body);
675 hentry[H_X_LIST].body = NULL;
676 g_free(hentry[H_X_MAILING_LIST].body);
677 hentry[H_X_MAILING_LIST].body = NULL;
678 g_free(hentry[H_LIST_ID].body);
679 hentry[H_LIST_ID].body = NULL;
680 g_free(hentry[H_SENDER].body);
681 hentry[H_SENDER].body = NULL;
683 #undef SET_FILTER_KEY
684 break;
685 default:
686 break;
690 static void procmsg_empty_trash(FolderItem *trash)
692 GNode *node, *next;
694 if (!trash ||
695 (trash->stype != F_TRASH &&
696 !folder_has_parent_of_type(trash, F_TRASH)))
697 return;
699 if (trash && trash->total_msgs > 0) {
700 GSList *mlist = folder_item_get_msg_list(trash);
701 GSList *cur;
702 for (cur = mlist ; cur != NULL ; cur = cur->next) {
703 MsgInfo * msginfo = (MsgInfo *) cur->data;
704 if (MSG_IS_LOCKED(msginfo->flags)) {
705 procmsg_msginfo_free(&msginfo);
706 continue;
708 if (msginfo->total_size != 0 &&
709 msginfo->size != (off_t)msginfo->total_size)
710 partial_mark_for_delete(msginfo);
712 procmsg_msginfo_free(&msginfo);
714 g_slist_free(mlist);
715 folder_item_remove_all_msg(trash);
718 if (!trash->node || !trash->node->children)
719 return;
721 node = trash->node->children;
722 while (node != NULL) {
723 next = node->next;
724 procmsg_empty_trash(FOLDER_ITEM(node->data));
725 node = next;
729 void procmsg_empty_all_trash(void)
731 FolderItem *trash;
732 GList *cur;
734 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
735 Folder *folder = FOLDER(cur->data);
736 trash = folder->trash;
737 procmsg_empty_trash(trash);
738 if (folder->account && folder->account->set_trash_folder &&
739 folder_find_item_from_identifier(folder->account->trash_folder))
740 procmsg_empty_trash(
741 folder_find_item_from_identifier(folder->account->trash_folder));
745 static PrefsAccount *procmsg_get_account_from_file(const gchar *file)
747 PrefsAccount *mailac = NULL;
748 FILE *fp;
749 int hnum;
750 gchar *buf = NULL;
751 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
752 {"SSV:", NULL, FALSE},
753 {"R:", NULL, FALSE},
754 {"NG:", NULL, FALSE},
755 {"MAID:", NULL, FALSE},
756 {"NAID:", NULL, FALSE},
757 {"SCF:", NULL, FALSE},
758 {"RMID:", NULL, FALSE},
759 {"FMID:", NULL, FALSE},
760 {"X-Claws-Privacy-System:", NULL, FALSE},
761 {"X-Claws-Encrypt:", NULL, FALSE},
762 {"X-Claws-Encrypt-Data:", NULL, FALSE},
763 {"X-Claws-End-Special-Headers", NULL, FALSE},
764 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
765 {"X-Sylpheed-Encrypt:", NULL, FALSE},
766 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
767 {NULL, NULL, FALSE}};
769 cm_return_val_if_fail(file != NULL, NULL);
771 if ((fp = claws_fopen(file, "rb")) == NULL) {
772 FILE_OP_ERROR(file, "claws_fopen");
773 return NULL;
776 while ((hnum = procheader_get_one_field(&buf, fp, qentry)) != -1 && buf != NULL) {
777 gchar *p = buf + strlen(qentry[hnum].name);
779 if (hnum == Q_MAIL_ACCOUNT_ID) {
780 mailac = account_find_from_id(atoi(p));
781 break;
783 g_free(buf);
784 buf = NULL;
786 if (buf)
787 g_free(buf);
788 claws_fclose(fp);
789 return mailac;
792 gchar *procmsg_msginfo_get_avatar(MsgInfo *msginfo, gint type)
794 GSList *mia;
796 if (!msginfo || !msginfo->extradata || !msginfo->extradata->avatars)
797 return NULL;
799 for (mia = msginfo->extradata->avatars; mia; mia = mia->next) {
800 MsgInfoAvatar *avatar = (MsgInfoAvatar *)mia->data;
801 if (avatar->avatar_id == type)
802 return avatar->avatar_src;
805 return NULL;
808 void procmsg_msginfo_add_avatar(MsgInfo *msginfo, gint type, const gchar *data)
810 MsgInfoAvatar *av;
812 if (!msginfo->extradata)
813 msginfo->extradata = g_new0(MsgInfoExtraData, 1);
815 av = g_new0(MsgInfoAvatar, 1);
816 av->avatar_id = type;
817 av->avatar_src = g_strdup(data);
819 msginfo->extradata->avatars = g_slist_append(msginfo->extradata->avatars, av);
822 gchar *procmsg_msginfo_get_identifier(MsgInfo *msginfo)
824 gchar *folder_id;
825 const gchar *msgid;
826 gchar *id;
828 cm_return_val_if_fail(msginfo != NULL, NULL);
829 folder_id = folder_item_get_identifier(msginfo->folder);
830 msgid = msginfo->msgid;
832 id = g_strconcat(folder_id, G_DIR_SEPARATOR_S, msgid, NULL);
834 g_free(folder_id);
836 return id;
839 MsgInfo *procmsg_get_msginfo_from_identifier(const gchar *id)
841 gchar *folder_id = g_strdup(id);
842 gchar *separator = strrchr(folder_id, G_DIR_SEPARATOR);
843 const gchar *msgid;
844 FolderItem *item;
845 MsgInfo *msginfo;
847 if (separator == NULL) {
848 g_free(folder_id);
849 return NULL;
852 *separator = '\0';
853 msgid = separator + 1;
855 item = folder_find_item_from_identifier(folder_id);
857 if (item == NULL) {
858 g_free(folder_id);
859 return NULL;
862 msginfo = folder_item_get_msginfo_by_msgid(item, msgid);
863 g_free(folder_id);
865 return msginfo;
868 static GSList *procmsg_list_sort_by_account(FolderItem *queue, GSList *list)
870 GSList *result = NULL;
871 GSList *orig = NULL;
872 PrefsAccount *last_account = NULL;
873 MsgInfo *msg = NULL;
874 GSList *cur = NULL;
875 gboolean nothing_to_sort = TRUE;
877 if (!list)
878 return NULL;
880 orig = g_slist_copy(list);
882 msg = (MsgInfo *)orig->data;
884 for (cur = orig; cur; cur = cur->next)
885 debug_print("sort before %s\n", ((MsgInfo *)cur->data)->from);
887 debug_print("\n");
889 parse_again:
890 nothing_to_sort = TRUE;
891 cur = orig;
892 while (cur) {
893 gchar *file = NULL;
894 PrefsAccount *ac = NULL;
895 msg = (MsgInfo *)cur->data;
896 file = folder_item_fetch_msg(queue, msg->msgnum);
897 ac = procmsg_get_account_from_file(file);
898 g_free(file);
900 if (last_account == NULL || (ac != NULL && ac == last_account)) {
901 result = g_slist_append(result, msg);
902 orig = g_slist_remove(orig, msg);
903 last_account = ac;
904 nothing_to_sort = FALSE;
905 goto parse_again;
907 cur = cur->next;
910 if (orig && g_slist_length(orig)) {
911 if (!last_account && nothing_to_sort) {
912 /* can't find an account for the rest of the list */
913 cur = orig;
914 while (cur) {
915 result = g_slist_append(result, cur->data);
916 cur = cur->next;
918 } else {
919 last_account = NULL;
920 goto parse_again;
924 g_slist_free(orig);
926 for (cur = result; cur; cur = cur->next)
927 debug_print("sort after %s\n", ((MsgInfo *)cur->data)->from);
929 debug_print("\n");
931 return result;
934 static gboolean procmsg_is_last_for_account(FolderItem *queue, MsgInfo *msginfo, GSList *elem)
936 gchar *file = folder_item_fetch_msg(queue, msginfo->msgnum);
937 PrefsAccount *ac = procmsg_get_account_from_file(file);
938 GSList *cur;
939 g_free(file);
940 for (cur = elem; cur; cur = cur->next) {
941 MsgInfo *cur_msginfo = (MsgInfo *)cur->data;
942 file = folder_item_fetch_msg(queue, cur_msginfo->msgnum);
944 if (cur_msginfo != msginfo && !MSG_IS_LOCKED(cur_msginfo->flags)) {
945 if (procmsg_get_account_from_file(file) == ac) {
946 g_free(file);
947 return FALSE;
951 g_free(file);
953 return TRUE;
956 static gboolean send_queue_lock = FALSE;
958 gboolean procmsg_queue_lock(char **errstr)
960 if (send_queue_lock) {
961 /* Avoid having to translate two similar strings */
962 log_warning(LOG_PROTOCOL, "%s\n", _("Already trying to send."));
963 if (errstr) {
964 if (*errstr) g_free(*errstr);
965 *errstr = g_strdup_printf(_("Already trying to send."));
967 return FALSE;
969 send_queue_lock = TRUE;
970 return TRUE;
972 void procmsg_queue_unlock(void)
974 send_queue_lock = FALSE;
977 *\brief Send messages in queue
979 *\param queue Queue folder to process
980 *\param save_msgs Unused
982 *\return Number of messages sent, negative if an error occurred
983 * positive if no error occurred
985 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs, gchar **errstr)
987 gint sent = 0, err = 0;
988 GSList *list, *elem;
989 GSList *sorted_list = NULL;
990 GNode *node, *next;
992 if (!procmsg_queue_lock(errstr)) {
993 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
994 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
995 return -1;
997 inc_lock();
998 if (!queue)
999 queue = folder_get_default_queue();
1001 if (queue == NULL) {
1002 procmsg_queue_unlock();
1003 inc_unlock();
1004 return -1;
1007 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1008 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1010 folder_item_scan(queue);
1011 list = folder_item_get_msg_list(queue);
1013 /* sort the list per sender account; this helps reusing the same SMTP server */
1014 sorted_list = procmsg_list_sort_by_account(queue, list);
1016 for (elem = sorted_list; elem != NULL; elem = elem->next) {
1017 gchar *file;
1018 MsgInfo *msginfo;
1020 msginfo = (MsgInfo *)(elem->data);
1021 if (!MSG_IS_LOCKED(msginfo->flags) && !MSG_IS_DELETED(msginfo->flags)) {
1022 file = folder_item_fetch_msg(queue, msginfo->msgnum);
1023 if (file) {
1024 gboolean queued_removed = FALSE;
1025 if (procmsg_send_message_queue_full(file,
1026 !procmsg_is_last_for_account(queue, msginfo, elem),
1027 errstr, queue, msginfo->msgnum, &queued_removed) < 0) {
1028 g_warning("sending queued message %d failed",
1029 msginfo->msgnum);
1030 err++;
1031 } else {
1032 sent++;
1033 if (!queued_removed)
1034 folder_item_remove_msg(queue, msginfo->msgnum);
1036 g_free(file);
1039 /* FIXME: supposedly if only one message is locked, and queue
1040 * is being flushed, the following free says something like
1041 * "freeing msg ## in folder (nil)". */
1042 procmsg_msginfo_free(&msginfo);
1045 g_slist_free(sorted_list);
1046 folder_item_scan(queue);
1048 if (queue->node && queue->node->children) {
1049 node = queue->node->children;
1050 while (node != NULL) {
1051 int res = 0;
1052 next = node->next;
1053 send_queue_lock = FALSE;
1054 res = procmsg_send_queue(FOLDER_ITEM(node->data), save_msgs, errstr);
1055 send_queue_lock = TRUE;
1056 if (res < 0)
1057 err = -res;
1058 else
1059 sent += res;
1060 node = next;
1063 procmsg_queue_unlock();
1064 inc_unlock();
1065 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1066 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1068 return (err != 0 ? -err : sent);
1071 gboolean procmsg_is_sending(void)
1073 return send_queue_lock;
1077 *\brief Determine if a queue folder is empty
1079 *\param queue Queue folder to process
1081 *\return TRUE if the queue folder is empty, otherwise return FALSE
1083 gboolean procmsg_queue_is_empty(FolderItem *queue)
1085 GSList *list;
1086 gboolean res = FALSE;
1087 if (!queue)
1088 queue = folder_get_default_queue();
1089 cm_return_val_if_fail(queue != NULL, TRUE);
1091 folder_item_scan(queue);
1092 list = folder_item_get_msg_list(queue);
1093 res = (list == NULL);
1094 procmsg_msg_list_free(list);
1096 if (res == TRUE) {
1097 GNode *node, *next;
1098 if (queue->node && queue->node->children) {
1099 node = queue->node->children;
1100 while (node != NULL) {
1101 next = node->next;
1102 if (!procmsg_queue_is_empty(FOLDER_ITEM(node->data)))
1103 return FALSE;
1104 node = next;
1108 return res;
1111 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
1113 FILE *fp, *outfp;
1114 gchar buf[BUFFSIZE];
1116 if ((fp = claws_fopen(in, "rb")) == NULL) {
1117 FILE_OP_ERROR(in, "claws_fopen");
1118 return -1;
1120 if ((outfp = claws_fopen(out, "wb")) == NULL) {
1121 FILE_OP_ERROR(out, "claws_fopen");
1122 claws_fclose(fp);
1123 return -1;
1125 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
1126 /* new way */
1127 if ((!strncmp(buf, "X-Claws-End-Special-Headers: 1",
1128 strlen("X-Claws-End-Special-Headers:"))) ||
1129 (!strncmp(buf, "X-Sylpheed-End-Special-Headers: 1",
1130 strlen("X-Sylpheed-End-Special-Headers:"))))
1131 break;
1132 /* old way */
1133 if (buf[0] == '\r' || buf[0] == '\n') break;
1134 /* from other mailers */
1135 if (!strncmp(buf, "Date: ", 6)
1136 || !strncmp(buf, "To: ", 4)
1137 || !strncmp(buf, "From: ", 6)
1138 || !strncmp(buf, "Subject: ", 9)) {
1139 rewind(fp);
1140 break;
1143 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
1144 if (claws_fputs(buf, outfp) == EOF) {
1145 FILE_OP_ERROR(out, "claws_fputs");
1146 claws_fclose(outfp);
1147 claws_fclose(fp);
1148 return -1;
1151 claws_safe_fclose(outfp);
1152 claws_fclose(fp);
1153 return 0;
1156 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file)
1158 gint num;
1159 MsgInfo *msginfo, *tmp_msginfo;
1160 MsgFlags flag = {0, 0};
1161 gchar *outbox_path = NULL;
1163 if (!outbox) {
1164 debug_print("using default outbox\n");
1165 outbox = folder_get_default_outbox();
1168 cm_return_val_if_fail(outbox != NULL, -1);
1170 outbox_path = folder_item_get_path(outbox);
1171 debug_print("attempting to save sent message to %s...\n", outbox_path);
1172 g_free(outbox_path);
1174 gchar tmp[MAXPATHLEN + 1];
1176 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
1177 get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
1179 if (procmsg_remove_special_headers(file, tmp) !=0)
1180 return -1;
1182 while (folder_item_scan(outbox) < 0) {
1183 outbox = foldersel_folder_sel(NULL, FOLDER_SEL_SAVE, NULL, FALSE,
1184 _("Select the folder where you want to save the sent message"));
1185 if (outbox == NULL) {
1186 g_warning("not saving sent message");
1187 claws_unlink(tmp);
1188 return -1;
1191 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
1192 g_warning("not saving sent message");
1193 outbox_path = folder_item_get_path(outbox);
1194 alertpanel_warning(_("Could not save sent message to %s."), outbox_path);
1195 claws_unlink(tmp);
1196 g_free(outbox_path);
1197 return -1;
1200 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
1201 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
1202 if (msginfo != NULL) {
1203 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
1204 procmsg_msginfo_free(&msginfo); /* refcnt-- */
1205 /* tmp_msginfo == msginfo */
1206 if (tmp_msginfo && msginfo->extradata &&
1207 (msginfo->extradata->dispositionnotificationto ||
1208 msginfo->extradata->returnreceiptto)) {
1209 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
1211 procmsg_msginfo_free(&tmp_msginfo); /* refcnt-- */
1214 return 0;
1218 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
1220 msginfo->refcnt++;
1222 return msginfo;
1225 MsgInfo *procmsg_msginfo_new(void)
1227 MsgInfo *newmsginfo;
1229 newmsginfo = g_new0(MsgInfo, 1);
1230 newmsginfo->refcnt = 1;
1232 return newmsginfo;
1235 static MsgInfoAvatar *procmsg_msginfoavatar_copy(MsgInfoAvatar *avatar)
1237 MsgInfoAvatar *newavatar;
1239 if (avatar == NULL) return NULL;
1241 newavatar = g_new0(MsgInfoAvatar, 1);
1242 newavatar->avatar_id = avatar->avatar_id;
1243 newavatar->avatar_src = g_strdup(avatar->avatar_src);
1245 return newavatar;
1248 static void procmsg_msginfoavatar_free(MsgInfoAvatar *avatar)
1250 if (avatar != NULL) {
1251 if (avatar->avatar_src != NULL)
1252 g_free(avatar->avatar_src);
1253 g_free(avatar);
1257 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
1259 MsgInfo *newmsginfo;
1260 GSList *refs;
1262 if (msginfo == NULL) return NULL;
1264 newmsginfo = g_new0(MsgInfo, 1);
1266 newmsginfo->refcnt = 1;
1268 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
1269 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
1270 g_strdup(msginfo->mmb) : NULL
1272 MEMBCOPY(msgnum);
1273 MEMBCOPY(size);
1274 MEMBCOPY(mtime);
1275 MEMBCOPY(date_t);
1277 MEMBCOPY(flags);
1279 MEMBDUP(fromname);
1281 MEMBDUP(date);
1282 MEMBDUP(from);
1283 MEMBDUP(to);
1284 MEMBDUP(cc);
1285 MEMBDUP(newsgroups);
1286 MEMBDUP(subject);
1287 MEMBDUP(msgid);
1288 MEMBDUP(inreplyto);
1289 MEMBDUP(xref);
1291 MEMBCOPY(folder);
1292 MEMBCOPY(to_folder);
1294 if (msginfo->extradata) {
1295 newmsginfo->extradata = g_new0(MsgInfoExtraData, 1);
1296 if (msginfo->extradata->avatars) {
1297 newmsginfo->extradata->avatars = g_slist_copy_deep(msginfo->extradata->avatars,
1298 (GCopyFunc) procmsg_msginfoavatar_copy, NULL);
1300 MEMBDUP(extradata->dispositionnotificationto);
1301 MEMBDUP(extradata->returnreceiptto);
1302 MEMBDUP(extradata->partial_recv);
1303 MEMBDUP(extradata->account_server);
1304 MEMBDUP(extradata->account_login);
1305 MEMBDUP(extradata->list_post);
1306 MEMBDUP(extradata->list_subscribe);
1307 MEMBDUP(extradata->list_unsubscribe);
1308 MEMBDUP(extradata->list_help);
1309 MEMBDUP(extradata->list_archive);
1310 MEMBDUP(extradata->list_owner);
1311 MEMBDUP(extradata->resent_from);
1314 refs = msginfo->references;
1315 for (refs = msginfo->references; refs != NULL; refs = refs->next) {
1316 newmsginfo->references = g_slist_prepend
1317 (newmsginfo->references, g_strdup(refs->data));
1319 newmsginfo->references = g_slist_reverse(newmsginfo->references);
1321 MEMBCOPY(score);
1322 MEMBDUP(plaintext_file);
1324 return newmsginfo;
1327 MsgInfo *procmsg_msginfo_get_full_info_from_file(MsgInfo *msginfo, const gchar *file)
1329 MsgInfo *full_msginfo;
1331 if (msginfo == NULL) return NULL;
1333 if (!file || !is_file_exist(file)) {
1334 g_warning("procmsg_msginfo_get_full_info_from_file(): can't get message file");
1335 return NULL;
1338 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
1339 if (!full_msginfo) return NULL;
1341 msginfo->total_size = full_msginfo->total_size;
1342 msginfo->planned_download = full_msginfo->planned_download;
1344 if (full_msginfo->extradata) {
1345 if (!msginfo->extradata)
1346 msginfo->extradata = g_new0(MsgInfoExtraData, 1);
1347 if (!msginfo->extradata->list_post)
1348 msginfo->extradata->list_post = g_strdup(full_msginfo->extradata->list_post);
1349 if (!msginfo->extradata->list_subscribe)
1350 msginfo->extradata->list_subscribe = g_strdup(full_msginfo->extradata->list_subscribe);
1351 if (!msginfo->extradata->list_unsubscribe)
1352 msginfo->extradata->list_unsubscribe = g_strdup(full_msginfo->extradata->list_unsubscribe);
1353 if (!msginfo->extradata->list_help)
1354 msginfo->extradata->list_help = g_strdup(full_msginfo->extradata->list_help);
1355 if (!msginfo->extradata->list_archive)
1356 msginfo->extradata->list_archive= g_strdup(full_msginfo->extradata->list_archive);
1357 if (!msginfo->extradata->list_owner)
1358 msginfo->extradata->list_owner = g_strdup(full_msginfo->extradata->list_owner);
1359 if (!msginfo->extradata->avatars)
1360 msginfo->extradata->avatars = g_slist_copy_deep(full_msginfo->extradata->avatars,
1361 (GCopyFunc) procmsg_msginfoavatar_copy, NULL);
1362 if (!msginfo->extradata->dispositionnotificationto)
1363 msginfo->extradata->dispositionnotificationto =
1364 g_strdup(full_msginfo->extradata->dispositionnotificationto);
1365 if (!msginfo->extradata->returnreceiptto)
1366 msginfo->extradata->returnreceiptto = g_strdup
1367 (full_msginfo->extradata->returnreceiptto);
1368 if (!msginfo->extradata->partial_recv && full_msginfo->extradata->partial_recv)
1369 msginfo->extradata->partial_recv = g_strdup
1370 (full_msginfo->extradata->partial_recv);
1371 if (!msginfo->extradata->account_server && full_msginfo->extradata->account_server)
1372 msginfo->extradata->account_server = g_strdup
1373 (full_msginfo->extradata->account_server);
1374 if (!msginfo->extradata->account_login && full_msginfo->extradata->account_login)
1375 msginfo->extradata->account_login = g_strdup
1376 (full_msginfo->extradata->account_login);
1377 if (!msginfo->extradata->resent_from && full_msginfo->extradata->resent_from)
1378 msginfo->extradata->resent_from = g_strdup
1379 (full_msginfo->extradata->resent_from);
1381 procmsg_msginfo_free(&full_msginfo);
1383 return procmsg_msginfo_new_ref(msginfo);
1386 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
1388 MsgInfo *full_msginfo;
1389 gchar *file;
1391 if (msginfo == NULL) return NULL;
1393 file = procmsg_get_message_file_path(msginfo);
1394 if (!file || !is_file_exist(file)) {
1395 g_free(file);
1396 file = procmsg_get_message_file(msginfo);
1398 if (!file || !is_file_exist(file)) {
1399 g_warning("procmsg_msginfo_get_full_info(): can't get message file");
1400 return NULL;
1403 full_msginfo = procmsg_msginfo_get_full_info_from_file(msginfo, file);
1404 g_free(file);
1405 return full_msginfo;
1408 #define FREENULL(n) { g_free(n); n = NULL; }
1409 void procmsg_msginfo_free(MsgInfo **msginfo_ptr)
1411 MsgInfo *msginfo = *msginfo_ptr;
1413 if (msginfo == NULL) return;
1415 msginfo->refcnt--;
1416 if (msginfo->refcnt > 0)
1417 return;
1419 if (msginfo->to_folder) {
1420 msginfo->to_folder->op_count--;
1421 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1424 FREENULL(msginfo->fromspace);
1426 FREENULL(msginfo->fromname);
1428 FREENULL(msginfo->date);
1429 FREENULL(msginfo->from);
1430 FREENULL(msginfo->to);
1431 FREENULL(msginfo->cc);
1432 FREENULL(msginfo->newsgroups);
1433 FREENULL(msginfo->subject);
1434 FREENULL(msginfo->msgid);
1435 FREENULL(msginfo->inreplyto);
1436 FREENULL(msginfo->xref);
1438 if (msginfo->extradata) {
1439 if (msginfo->extradata->avatars) {
1440 g_slist_foreach(msginfo->extradata->avatars,
1441 (GFunc)procmsg_msginfoavatar_free,
1442 NULL);
1443 g_slist_free(msginfo->extradata->avatars);
1444 msginfo->extradata->avatars = NULL;
1446 FREENULL(msginfo->extradata->returnreceiptto);
1447 FREENULL(msginfo->extradata->dispositionnotificationto);
1448 FREENULL(msginfo->extradata->list_post);
1449 FREENULL(msginfo->extradata->list_subscribe);
1450 FREENULL(msginfo->extradata->list_unsubscribe);
1451 FREENULL(msginfo->extradata->list_help);
1452 FREENULL(msginfo->extradata->list_archive);
1453 FREENULL(msginfo->extradata->list_owner);
1454 FREENULL(msginfo->extradata->partial_recv);
1455 FREENULL(msginfo->extradata->account_server);
1456 FREENULL(msginfo->extradata->account_login);
1457 FREENULL(msginfo->extradata->resent_from);
1458 FREENULL(msginfo->extradata);
1460 slist_free_strings_full(msginfo->references);
1461 msginfo->references = NULL;
1462 g_slist_free(msginfo->tags);
1463 msginfo->tags = NULL;
1465 FREENULL(msginfo->plaintext_file);
1467 g_free(msginfo);
1468 *msginfo_ptr = NULL;
1470 #undef FREENULL
1472 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1474 guint memusage = 0;
1475 GSList *tmp;
1477 memusage += sizeof(MsgInfo);
1478 if (msginfo->fromname)
1479 memusage += strlen(msginfo->fromname);
1480 if (msginfo->date)
1481 memusage += strlen(msginfo->date);
1482 if (msginfo->from)
1483 memusage += strlen(msginfo->from);
1484 if (msginfo->to)
1485 memusage += strlen(msginfo->to);
1486 if (msginfo->cc)
1487 memusage += strlen(msginfo->cc);
1488 if (msginfo->newsgroups)
1489 memusage += strlen(msginfo->newsgroups);
1490 if (msginfo->subject)
1491 memusage += strlen(msginfo->subject);
1492 if (msginfo->msgid)
1493 memusage += strlen(msginfo->msgid);
1494 if (msginfo->inreplyto)
1495 memusage += strlen(msginfo->inreplyto);
1497 for (tmp = msginfo->references; tmp; tmp=tmp->next) {
1498 gchar *r = (gchar *)tmp->data;
1499 memusage += r?strlen(r):0 + sizeof(GSList);
1501 if (msginfo->fromspace)
1502 memusage += strlen(msginfo->fromspace);
1504 for (tmp = msginfo->tags; tmp; tmp=tmp->next) {
1505 memusage += sizeof(GSList);
1507 if (msginfo->extradata) {
1508 memusage += sizeof(MsgInfoExtraData);
1509 if (msginfo->extradata->avatars) {
1510 for (tmp = msginfo->extradata->avatars; tmp; tmp = tmp->next) {
1511 MsgInfoAvatar *avt = (MsgInfoAvatar *)tmp->data;
1512 memusage += (avt->avatar_src)? strlen(avt->avatar_src): 0;
1513 memusage += sizeof(MsgInfoAvatar) + sizeof(GSList);
1516 if (msginfo->extradata->dispositionnotificationto)
1517 memusage += strlen(msginfo->extradata->dispositionnotificationto);
1518 if (msginfo->extradata->returnreceiptto)
1519 memusage += strlen(msginfo->extradata->returnreceiptto);
1521 if (msginfo->extradata->partial_recv)
1522 memusage += strlen(msginfo->extradata->partial_recv);
1523 if (msginfo->extradata->account_server)
1524 memusage += strlen(msginfo->extradata->account_server);
1525 if (msginfo->extradata->account_login)
1526 memusage += strlen(msginfo->extradata->account_login);
1527 if (msginfo->extradata->resent_from)
1528 memusage += strlen(msginfo->extradata->resent_from);
1530 if (msginfo->extradata->list_post)
1531 memusage += strlen(msginfo->extradata->list_post);
1532 if (msginfo->extradata->list_subscribe)
1533 memusage += strlen(msginfo->extradata->list_subscribe);
1534 if (msginfo->extradata->list_unsubscribe)
1535 memusage += strlen(msginfo->extradata->list_unsubscribe);
1536 if (msginfo->extradata->list_help)
1537 memusage += strlen(msginfo->extradata->list_help);
1538 if (msginfo->extradata->list_archive)
1539 memusage += strlen(msginfo->extradata->list_archive);
1540 if (msginfo->extradata->list_owner)
1541 memusage += strlen(msginfo->extradata->list_owner);
1543 return memusage;
1546 static gint procmsg_send_message_queue_full(const gchar *file, gboolean keep_session, gchar **errstr,
1547 FolderItem *queue, gint msgnum, gboolean *queued_removed)
1549 static HeaderEntry qentry[] = {
1550 {"S:", NULL, FALSE}, /* 0 */
1551 {"SSV:", NULL, FALSE},
1552 {"R:", NULL, FALSE},
1553 {"NG:", NULL, FALSE},
1554 {"MAID:", NULL, FALSE},
1555 {"NAID:", NULL, FALSE}, /* 5 */
1556 {"SCF:", NULL, FALSE},
1557 {"RMID:", NULL, FALSE},
1558 {"FMID:", NULL, FALSE},
1559 {"X-Claws-Privacy-System:", NULL, FALSE},
1560 {"X-Claws-Encrypt:", NULL, FALSE}, /* 10 */
1561 {"X-Claws-Encrypt-Data:", NULL, FALSE},
1562 {"X-Claws-End-Special-Headers:", NULL, FALSE},
1563 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1564 {"X-Sylpheed-Encrypt:", NULL, FALSE},
1565 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE}, /* 15 */
1566 {"X-Sylpheed-End-Special-Headers:", NULL, FALSE},
1567 {NULL, NULL, FALSE}};
1568 FILE *fp;
1569 gint filepos;
1570 gint mailval = 0, newsval = 0;
1571 gchar *from = NULL;
1572 gchar *smtpserver = NULL;
1573 GSList *to_list = NULL;
1574 GSList *newsgroup_list = NULL;
1575 gchar *savecopyfolder = NULL;
1576 gchar *replymessageid = NULL;
1577 gchar *fwdmessageid = NULL;
1578 gchar *buf;
1579 gint hnum;
1580 PrefsAccount *mailac = NULL, *newsac = NULL;
1581 gboolean encrypt = FALSE;
1582 FolderItem *outbox;
1584 cm_return_val_if_fail(file != NULL, -1);
1586 if ((fp = claws_fopen(file, "rb")) == NULL) {
1587 FILE_OP_ERROR(file, "claws_fopen");
1588 if (errstr) {
1589 if (*errstr) g_free(*errstr);
1590 *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1592 return -1;
1595 while ((hnum = procheader_get_one_field(&buf, fp, qentry)) != -1 && buf != NULL) {
1596 gchar *p = buf + strlen(qentry[hnum].name);
1598 switch (hnum) {
1599 case Q_SENDER:
1600 if (from == NULL)
1601 from = g_strdup(p);
1602 break;
1603 case Q_SMTPSERVER:
1604 if (smtpserver == NULL)
1605 smtpserver = g_strdup(p);
1606 break;
1607 case Q_RECIPIENTS:
1608 to_list = address_list_append(to_list, p);
1609 break;
1610 case Q_NEWSGROUPS:
1611 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1612 break;
1613 case Q_MAIL_ACCOUNT_ID:
1614 mailac = account_find_from_id(atoi(p));
1615 break;
1616 case Q_NEWS_ACCOUNT_ID:
1617 newsac = account_find_from_id(atoi(p));
1618 break;
1619 case Q_SAVE_COPY_FOLDER:
1620 if (savecopyfolder == NULL)
1621 savecopyfolder = g_strdup(p);
1622 break;
1623 case Q_REPLY_MESSAGE_ID:
1624 if (replymessageid == NULL)
1625 replymessageid = g_strdup(p);
1626 break;
1627 case Q_FWD_MESSAGE_ID:
1628 if (fwdmessageid == NULL)
1629 fwdmessageid = g_strdup(p);
1630 break;
1631 case Q_ENCRYPT:
1632 case Q_ENCRYPT_OLD:
1633 if (p[0] == '1')
1634 encrypt = TRUE;
1635 break;
1636 case Q_CLAWS_HDRS:
1637 case Q_CLAWS_HDRS_OLD:
1638 /* end of special headers reached */
1639 g_free(buf);
1640 goto send_mail; /* can't "break;break;" */
1642 g_free(buf);
1645 send_mail:
1646 filepos = ftell(fp);
1647 if (filepos < 0) {
1648 FILE_OP_ERROR(file, "ftell");
1649 if (errstr) {
1650 if (*errstr) g_free(*errstr);
1651 *errstr = g_strdup_printf(_("Couldn't open file %s."), file);
1653 return -1;
1656 if (to_list) {
1657 debug_print("Sending message by mail\n");
1658 if (!from) {
1659 if (errstr) {
1660 if (*errstr) g_free(*errstr);
1661 *errstr = g_strdup_printf(_("Queued message header is broken."));
1663 mailval = -1;
1664 } else if (mailac && mailac->use_mail_command &&
1665 mailac->mail_command && (* mailac->mail_command)) {
1666 mailval = send_message_local(mailac->mail_command, fp);
1667 } else {
1668 if (!mailac) {
1669 mailac = account_find_from_smtp_server(from, smtpserver);
1670 if (!mailac) {
1671 g_warning("account not found, "
1672 "using current account...");
1673 mailac = cur_account;
1677 if (mailac) {
1678 mailval = send_message_smtp_full(mailac, to_list, fp, keep_session);
1679 if (mailval == -1 && errstr) {
1680 if (*errstr) g_free(*errstr);
1681 *errstr = g_strdup_printf(_("An error happened during SMTP session."));
1683 } else {
1684 PrefsAccount tmp_ac;
1686 g_warning("account not found");
1688 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1689 tmp_ac.address = from;
1690 tmp_ac.smtp_server = smtpserver;
1691 tmp_ac.smtpport = SMTP_PORT;
1692 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1693 if (mailval == -1 && errstr) {
1694 if (*errstr) g_free(*errstr);
1695 *errstr = g_strdup_printf(_("No specific account has been found to "
1696 "send, and an error happened during SMTP session."));
1700 } else if (!to_list && !newsgroup_list) {
1701 if (errstr) {
1702 if (*errstr) g_free(*errstr);
1703 *errstr = g_strdup(_("Couldn't determine sending information. "
1704 "Maybe the email hasn't been generated by Claws Mail."));
1706 mailval = -1;
1709 if (fseek(fp, filepos, SEEK_SET) < 0) {
1710 FILE_OP_ERROR(file, "fseek");
1711 mailval = -1;
1714 if (newsgroup_list && newsac && (mailval == 0)) {
1715 Folder *folder;
1716 gchar *tmp = NULL;
1717 gchar buf[BUFFSIZE];
1718 FILE *tmpfp;
1720 /* write to temporary file */
1721 tmp = g_strdup_printf("%s%cnntp%p", get_tmp_dir(),
1722 G_DIR_SEPARATOR, file);
1723 if ((tmpfp = claws_fopen(tmp, "wb")) == NULL) {
1724 FILE_OP_ERROR(tmp, "claws_fopen");
1725 newsval = -1;
1726 alertpanel_error(_("Couldn't create temporary file for news sending."));
1727 } else {
1728 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1729 FILE_OP_ERROR(tmp, "chmod");
1730 g_warning("can't change file mode");
1733 while ((newsval == 0) && claws_fgets(buf, sizeof(buf), fp) != NULL) {
1734 if (claws_fputs(buf, tmpfp) == EOF) {
1735 FILE_OP_ERROR(tmp, "claws_fputs");
1736 newsval = -1;
1737 if (errstr) {
1738 if (*errstr) g_free(*errstr);
1739 *errstr = g_strdup_printf(_("Error when writing temporary file for news sending."));
1743 claws_safe_fclose(tmpfp);
1745 if (newsval == 0) {
1746 debug_print("Sending message by news\n");
1748 folder = FOLDER(newsac->folder);
1750 newsval = news_post(folder, tmp);
1751 if (newsval < 0 && errstr) {
1752 if (*errstr) g_free(*errstr);
1753 *errstr = g_strdup_printf(_("Error occurred while posting the message to %s."),
1754 newsac->nntp_server);
1757 claws_unlink(tmp);
1759 g_free(tmp);
1762 claws_fclose(fp);
1764 /* update session statistics */
1765 if (mailval == 0 && newsval == 0) {
1766 /* update session stats */
1767 if (replymessageid)
1768 session_stats.replied++;
1769 else if (fwdmessageid)
1770 session_stats.forwarded++;
1771 else
1772 session_stats.sent++;
1775 /* save message to outbox */
1776 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1777 debug_print("saving sent message to %s...\n", savecopyfolder);
1779 if (!encrypt || !mailac->save_encrypted_as_clear_text) {
1780 outbox = folder_find_item_from_identifier(savecopyfolder);
1781 if (!outbox) {
1782 gchar *id;
1783 outbox = folder_get_default_outbox();
1784 if (outbox != NULL) {
1785 id = folder_item_get_identifier(outbox);
1786 debug_print("%s not found, using %s\n", savecopyfolder, id);
1787 g_free(id);
1788 } else {
1789 debug_print("could not find outbox\n");
1792 /* Mail was not saved to outbox before encrypting, save it now. */
1793 gboolean saved = FALSE;
1794 *queued_removed = FALSE;
1795 if (queue && msgnum > 0) {
1796 MsgInfo *queued_mail = folder_item_get_msginfo(queue, msgnum);
1797 if (folder_item_move_msg(outbox, queued_mail) >= 0) {
1798 debug_print("moved queued mail %d to sent folder\n", msgnum);
1799 saved = TRUE;
1800 *queued_removed = TRUE;
1801 } else if (folder_item_copy_msg(outbox, queued_mail) >= 0) {
1802 debug_print("copied queued mail %d to sent folder\n", msgnum);
1803 saved = TRUE;
1805 procmsg_msginfo_free(&queued_mail);
1807 if (!saved) {
1808 debug_print("resaving queued mail to sent folder\n");
1809 procmsg_save_to_outbox(outbox, file);
1814 if (replymessageid != NULL || fwdmessageid != NULL) {
1815 gchar **tokens;
1816 FolderItem *item;
1818 if (replymessageid != NULL)
1819 tokens = g_strsplit(replymessageid, "\t", 0);
1820 else
1821 tokens = g_strsplit(fwdmessageid, "\t", 0);
1822 item = folder_find_item_from_identifier(tokens[0]);
1824 /* check if queued message has valid folder and message id */
1825 if (item != NULL && tokens[2] != NULL) {
1826 MsgInfo *msginfo;
1828 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1830 /* check if referring message exists and has a message id */
1831 if ((msginfo != NULL) &&
1832 (msginfo->msgid != NULL) &&
1833 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1834 procmsg_msginfo_free(&msginfo);
1835 msginfo = NULL;
1838 if (msginfo == NULL) {
1839 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1842 if (msginfo != NULL) {
1843 if (replymessageid != NULL) {
1844 MsgPermFlags to_unset = 0;
1846 if (prefs_common.mark_as_read_on_new_window)
1847 to_unset = (MSG_NEW|MSG_UNREAD);
1849 procmsg_msginfo_unset_flags(msginfo, to_unset, 0);
1850 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1851 } else {
1852 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1854 procmsg_msginfo_free(&msginfo);
1857 g_strfreev(tokens);
1860 g_free(from);
1861 g_free(smtpserver);
1862 slist_free_strings_full(to_list);
1863 slist_free_strings_full(newsgroup_list);
1864 g_free(savecopyfolder);
1865 g_free(replymessageid);
1866 g_free(fwdmessageid);
1868 return (newsval != 0 ? newsval : mailval);
1871 gint procmsg_send_message_queue(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1873 gint result = procmsg_send_message_queue_full(file, FALSE, errstr, queue, msgnum, queued_removed);
1874 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
1875 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
1876 return result;
1879 gint procmsg_send_message_queue_with_lock(const gchar *file, gchar **errstr, FolderItem *queue, gint msgnum, gboolean *queued_removed)
1881 gint val;
1882 if (procmsg_queue_lock(errstr)) {
1883 val = procmsg_send_message_queue(file, errstr, queue, msgnum, queued_removed);
1884 procmsg_queue_unlock();
1885 return val;
1887 return -1;
1890 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1892 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1894 /* NEW flag */
1895 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1896 item->new_msgs++;
1899 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1900 item->new_msgs--;
1903 /* UNREAD flag */
1904 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1905 item->unread_msgs++;
1906 if (procmsg_msg_has_marked_parent(msginfo))
1907 item->unreadmarked_msgs++;
1910 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1911 item->unread_msgs--;
1912 if (procmsg_msg_has_marked_parent(msginfo))
1913 item->unreadmarked_msgs--;
1916 /* MARK flag */
1917 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1918 procmsg_update_unread_children(msginfo, TRUE);
1919 item->marked_msgs++;
1922 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1923 procmsg_update_unread_children(msginfo, FALSE);
1924 item->marked_msgs--;
1927 if (!(old_flags & MSG_REPLIED) && (new_flags & MSG_REPLIED)) {
1928 item->replied_msgs++;
1931 if ((old_flags & MSG_REPLIED) && !(new_flags & MSG_REPLIED)) {
1932 item->replied_msgs--;
1935 if (!(old_flags & MSG_FORWARDED) && (new_flags & MSG_FORWARDED)) {
1936 item->forwarded_msgs++;
1939 if ((old_flags & MSG_FORWARDED) && !(new_flags & MSG_FORWARDED)) {
1940 item->forwarded_msgs--;
1943 if (!(old_flags & MSG_LOCKED) && (new_flags & MSG_LOCKED)) {
1944 item->locked_msgs++;
1947 if ((old_flags & MSG_LOCKED) && !(new_flags & MSG_LOCKED)) {
1948 item->locked_msgs--;
1951 if ((old_flags & MSG_IGNORE_THREAD) && !(new_flags & MSG_IGNORE_THREAD)) {
1952 item->ignored_msgs--;
1955 if (!(old_flags & MSG_IGNORE_THREAD) && (new_flags & MSG_IGNORE_THREAD)) {
1956 item->ignored_msgs++;
1959 if ((old_flags & MSG_WATCH_THREAD) && !(new_flags & MSG_WATCH_THREAD)) {
1960 item->watched_msgs--;
1963 if (!(old_flags & MSG_WATCH_THREAD) && (new_flags & MSG_WATCH_THREAD)) {
1964 item->watched_msgs++;
1968 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1970 FolderItem *item;
1971 MsgInfoUpdate msginfo_update;
1972 MsgPermFlags perm_flags_new, perm_flags_old;
1973 MsgTmpFlags tmp_flags_old;
1975 cm_return_if_fail(msginfo != NULL);
1976 item = msginfo->folder;
1977 cm_return_if_fail(item != NULL);
1979 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1981 /* Perm Flags handling */
1982 perm_flags_old = msginfo->flags.perm_flags;
1983 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1984 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1985 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1987 if ((perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
1988 perm_flags_new &= ~(MSG_IGNORE_THREAD);
1991 if (perm_flags_old != perm_flags_new) {
1992 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1994 update_folder_msg_counts(item, msginfo, perm_flags_old);
1995 summary_update_unread(mainwindow_get_mainwindow()->summaryview, NULL);
1998 /* Tmp flags handling */
1999 tmp_flags_old = msginfo->flags.tmp_flags;
2000 msginfo->flags.tmp_flags |= tmp_flags;
2002 /* update notification */
2003 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2004 msginfo_update.msginfo = msginfo;
2005 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2006 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2007 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2011 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
2013 FolderItem *item;
2014 MsgInfoUpdate msginfo_update;
2015 MsgPermFlags perm_flags_new, perm_flags_old;
2016 MsgTmpFlags tmp_flags_old;
2018 cm_return_if_fail(msginfo != NULL);
2019 item = msginfo->folder;
2020 cm_return_if_fail(item != NULL);
2022 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
2024 /* Perm Flags handling */
2025 perm_flags_old = msginfo->flags.perm_flags;
2026 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
2028 if (perm_flags_old != perm_flags_new) {
2029 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
2031 update_folder_msg_counts(item, msginfo, perm_flags_old);
2034 /* Tmp flags hanlding */
2035 tmp_flags_old = msginfo->flags.tmp_flags;
2036 msginfo->flags.tmp_flags &= ~tmp_flags;
2038 /* update notification */
2039 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2040 msginfo_update.msginfo = msginfo;
2041 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2042 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2043 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2047 void procmsg_msginfo_change_flags(MsgInfo *msginfo,
2048 MsgPermFlags add_perm_flags, MsgTmpFlags add_tmp_flags,
2049 MsgPermFlags rem_perm_flags, MsgTmpFlags rem_tmp_flags)
2051 FolderItem *item;
2052 MsgInfoUpdate msginfo_update;
2053 MsgPermFlags perm_flags_new, perm_flags_old;
2054 MsgTmpFlags tmp_flags_old;
2056 cm_return_if_fail(msginfo != NULL);
2057 item = msginfo->folder;
2058 cm_return_if_fail(item != NULL);
2060 debug_print("Changing flags for message %d in folder %s\n", msginfo->msgnum, item->path);
2062 /* Perm Flags handling */
2063 perm_flags_old = msginfo->flags.perm_flags;
2064 perm_flags_new = (msginfo->flags.perm_flags & ~rem_perm_flags) | add_perm_flags;
2065 if ((add_perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
2066 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
2068 if ((add_perm_flags & MSG_WATCH_THREAD) || (perm_flags_old & MSG_WATCH_THREAD)) {
2069 perm_flags_new &= ~(MSG_IGNORE_THREAD);
2072 if (perm_flags_old != perm_flags_new) {
2073 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
2075 update_folder_msg_counts(item, msginfo, perm_flags_old);
2079 /* Tmp flags handling */
2080 tmp_flags_old = msginfo->flags.tmp_flags;
2081 msginfo->flags.tmp_flags &= ~rem_tmp_flags;
2082 msginfo->flags.tmp_flags |= add_tmp_flags;
2084 /* update notification */
2085 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
2086 msginfo_update.msginfo = msginfo;
2087 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
2088 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
2089 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
2094 *\brief check for flags (e.g. mark) in prior msgs of current thread
2096 *\param info Current message
2097 *\param perm_flags Flags to be checked
2098 *\param parentmsgs Hash of prior msgs to avoid loops
2100 *\return gboolean TRUE if perm_flags are found
2102 static gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
2103 MsgPermFlags perm_flags, GHashTable *parentmsgs)
2105 MsgInfo *tmp;
2107 cm_return_val_if_fail(info != NULL, FALSE);
2109 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
2110 tmp = folder_item_get_msginfo_by_msgid(info->folder,
2111 info->inreplyto);
2112 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
2113 procmsg_msginfo_free(&tmp);
2114 return TRUE;
2115 } else if (tmp != NULL) {
2116 gboolean result;
2118 if (g_hash_table_lookup(parentmsgs, info)) {
2119 debug_print("loop detected: %d\n",
2120 info->msgnum);
2121 result = FALSE;
2122 } else {
2123 g_hash_table_insert(parentmsgs, info, "1");
2124 result = procmsg_msg_has_flagged_parent_real(
2125 tmp, perm_flags, parentmsgs);
2127 procmsg_msginfo_free(&tmp);
2128 return result;
2129 } else {
2130 return FALSE;
2132 } else
2133 return FALSE;
2137 *\brief Callback for cleaning up hash of parentmsgs
2139 static gboolean parentmsgs_hash_remove(gpointer key,
2140 gpointer value,
2141 gpointer user_data)
2143 return TRUE;
2147 *\brief Set up list of parentmsgs
2148 * See procmsg_msg_has_flagged_parent_real()
2150 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
2152 gboolean result;
2153 static GHashTable *parentmsgs = NULL;
2155 if (parentmsgs == NULL)
2156 parentmsgs = g_hash_table_new(NULL, NULL);
2158 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
2159 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
2161 return result;
2165 *\brief Check if msgs prior in thread are marked
2166 * See procmsg_msg_has_flagged_parent_real()
2168 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
2170 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
2174 static GSList *procmsg_find_children_func(MsgInfo *info,
2175 GSList *children, GSList *all)
2177 GSList *cur;
2179 cm_return_val_if_fail(info!=NULL, children);
2180 if (info->msgid == NULL)
2181 return children;
2183 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2184 MsgInfo *tmp = (MsgInfo *)cur->data;
2185 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
2186 /* Check if message is already in the list */
2187 if ((children == NULL) ||
2188 (g_slist_index(children, tmp) == -1)) {
2189 children = g_slist_prepend(children,
2190 procmsg_msginfo_new_ref(tmp));
2191 children = procmsg_find_children_func(tmp,
2192 children,
2193 all);
2197 return children;
2200 static GSList *procmsg_find_children (MsgInfo *info)
2202 GSList *children;
2203 GSList *all, *cur;
2205 cm_return_val_if_fail(info!=NULL, NULL);
2206 all = folder_item_get_msg_list(info->folder);
2207 children = procmsg_find_children_func(info, NULL, all);
2208 if (children != NULL) {
2209 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
2210 /* this will not free the used pointers
2211 created with procmsg_msginfo_new_ref */
2212 procmsg_msginfo_free((MsgInfo **)&(cur->data));
2215 g_slist_free(all);
2217 return children;
2220 static void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
2222 GSList *children = procmsg_find_children(info);
2223 GSList *cur;
2224 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
2225 MsgInfo *tmp = (MsgInfo *)cur->data;
2226 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
2227 if(newly_marked)
2228 info->folder->unreadmarked_msgs++;
2229 else
2230 info->folder->unreadmarked_msgs--;
2231 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
2233 procmsg_msginfo_free(&tmp);
2235 g_slist_free(children);
2239 * Set the destination folder for a copy or move operation
2241 * \param msginfo The message which's destination folder is changed
2242 * \param to_folder The destination folder for the operation
2244 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
2246 if(msginfo->to_folder != NULL) {
2247 msginfo->to_folder->op_count--;
2248 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2250 msginfo->to_folder = to_folder;
2251 if(to_folder != NULL) {
2252 to_folder->op_count++;
2253 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
2258 * Apply filtering actions to the msginfo
2260 * \param msginfo The MsgInfo describing the message that should be filtered
2261 * \return TRUE if the message was moved and MsgInfo is now invalid,
2262 * FALSE otherwise
2264 static gboolean procmsg_msginfo_filter(MsgInfo *msginfo, PrefsAccount* ac_prefs)
2266 MailFilteringData mail_filtering_data;
2268 mail_filtering_data.msginfo = msginfo;
2269 mail_filtering_data.msglist = NULL;
2270 mail_filtering_data.filtered = NULL;
2271 mail_filtering_data.unfiltered = NULL;
2272 mail_filtering_data.account = ac_prefs;
2274 if (!ac_prefs || ac_prefs->filterhook_on_recv)
2275 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
2276 return TRUE;
2278 /* filter if enabled in prefs or move to inbox if not */
2279 if((filtering_rules != NULL) &&
2280 filter_message_by_msginfo(filtering_rules, msginfo, ac_prefs,
2281 FILTERING_INCORPORATION, NULL)) {
2282 return TRUE;
2285 return FALSE;
2288 void procmsg_msglist_filter(GSList *list, PrefsAccount *ac,
2289 GSList **filtered, GSList **unfiltered,
2290 gboolean do_filter)
2292 GSList *cur, *to_do = NULL;
2293 gint total = 0, curnum = 0;
2294 MailFilteringData mail_filtering_data;
2296 cm_return_if_fail(filtered != NULL);
2297 cm_return_if_fail(unfiltered != NULL);
2299 *filtered = NULL;
2300 *unfiltered = NULL;
2302 if (list == NULL)
2303 return;
2305 total = g_slist_length(list);
2307 if (!do_filter) {
2308 *filtered = NULL;
2309 *unfiltered = g_slist_copy(list);
2310 return;
2313 statusbar_print_all(_("Filtering messages...\n"));
2315 mail_filtering_data.msginfo = NULL;
2316 mail_filtering_data.msglist = list;
2317 mail_filtering_data.filtered = NULL;
2318 mail_filtering_data.unfiltered = NULL;
2319 mail_filtering_data.account = ac;
2321 if (!ac || ac->filterhook_on_recv)
2322 hooks_invoke(MAIL_LISTFILTERING_HOOKLIST, &mail_filtering_data);
2324 if (mail_filtering_data.filtered == NULL &&
2325 mail_filtering_data.unfiltered == NULL) {
2326 /* nothing happened */
2327 debug_print(MAIL_LISTFILTERING_HOOKLIST " did nothing. filtering whole list normally.\n");
2328 to_do = list;
2330 if (mail_filtering_data.filtered != NULL) {
2331 /* keep track of what's been filtered by the hooks */
2332 debug_print(MAIL_LISTFILTERING_HOOKLIST " filtered some stuff. total %d filtered %d unfilt %d.\n",
2333 g_slist_length(list),
2334 g_slist_length(mail_filtering_data.filtered),
2335 g_slist_length(mail_filtering_data.unfiltered));
2337 *filtered = g_slist_copy(mail_filtering_data.filtered);
2339 if (mail_filtering_data.unfiltered != NULL) {
2340 /* what the hooks didn't handle will go in filtered or
2341 * unfiltered in the next loop */
2342 debug_print(MAIL_LISTFILTERING_HOOKLIST " left unfiltered stuff. total %d filtered %d unfilt %d.\n",
2343 g_slist_length(list),
2344 g_slist_length(mail_filtering_data.filtered),
2345 g_slist_length(mail_filtering_data.unfiltered));
2346 to_do = mail_filtering_data.unfiltered;
2349 for (cur = to_do; cur; cur = cur->next) {
2350 MsgInfo *info = (MsgInfo *)cur->data;
2351 if (procmsg_msginfo_filter(info, ac))
2352 *filtered = g_slist_prepend(*filtered, info);
2353 else
2354 *unfiltered = g_slist_prepend(*unfiltered, info);
2355 statusbar_progress_all(curnum++, total, prefs_common.statusbar_update_step);
2358 g_slist_free(mail_filtering_data.filtered);
2359 g_slist_free(mail_filtering_data.unfiltered);
2361 *filtered = g_slist_reverse(*filtered);
2362 *unfiltered = g_slist_reverse(*unfiltered);
2364 statusbar_progress_all(0,0,0);
2365 statusbar_pop_all();
2368 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
2370 MsgInfo *tmp_msginfo = NULL;
2371 MsgFlags flags = {0, 0};
2372 gchar *tmpfile = get_tmp_file();
2373 FILE *fp = claws_fopen(tmpfile, "wb");
2375 if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
2376 g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
2377 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
2378 if (fp)
2379 claws_fclose(fp);
2380 g_free(tmpfile);
2381 return NULL;
2384 if (fp && procmime_write_mimeinfo(mimeinfo, fp) >= 0) {
2385 claws_safe_fclose(fp);
2386 fp = NULL;
2387 tmp_msginfo = procheader_parse_file(
2388 tmpfile, flags,
2389 TRUE, FALSE);
2391 if (fp)
2392 claws_safe_fclose(fp);
2394 if (tmp_msginfo != NULL) {
2395 if (src_msginfo)
2396 tmp_msginfo->folder = src_msginfo->folder;
2397 tmp_msginfo->plaintext_file = g_strdup(tmpfile);
2398 } else {
2399 g_warning("procmsg_msginfo_new_from_mimeinfo(): can't generate new msginfo");
2402 g_free(tmpfile);
2404 return tmp_msginfo;
2407 static GSList *spam_learners = NULL;
2409 void procmsg_register_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2411 if (!g_slist_find(spam_learners, learn_func))
2412 spam_learners = g_slist_append(spam_learners, learn_func);
2413 if (mainwindow_get_mainwindow()) {
2414 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2415 summary_set_menu_sensitive(
2416 mainwindow_get_mainwindow()->summaryview);
2417 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2421 void procmsg_unregister_spam_learner (int (*learn_func)(MsgInfo *info, GSList *list, gboolean spam))
2423 spam_learners = g_slist_remove(spam_learners, learn_func);
2424 if (mainwindow_get_mainwindow()) {
2425 main_window_set_menu_sensitive(mainwindow_get_mainwindow());
2426 summary_set_menu_sensitive(
2427 mainwindow_get_mainwindow()->summaryview);
2428 toolbar_main_set_sensitive(mainwindow_get_mainwindow());
2432 gboolean procmsg_spam_can_learn(void)
2434 return g_slist_length(spam_learners) > 0;
2437 int procmsg_spam_learner_learn (MsgInfo *info, GSList *list, gboolean spam)
2439 GSList *cur = spam_learners;
2440 int ret = 0;
2441 for (; cur; cur = cur->next) {
2442 int ((*func)(MsgInfo *info, GSList *list, gboolean spam)) = cur->data;
2443 ret |= func(info, list, spam);
2445 return ret;
2448 static gchar *spam_folder_item = NULL;
2449 static FolderItem * (*procmsg_spam_get_folder_func)(MsgInfo *msginfo) = NULL;
2450 void procmsg_spam_set_folder (const char *item_identifier, FolderItem *(*spam_get_folder_func)(MsgInfo *info))
2452 g_free(spam_folder_item);
2453 if (item_identifier)
2454 spam_folder_item = g_strdup(item_identifier);
2455 else
2456 spam_folder_item = NULL;
2457 if (spam_get_folder_func != NULL)
2458 procmsg_spam_get_folder_func = spam_get_folder_func;
2459 else
2460 procmsg_spam_get_folder_func = NULL;
2463 FolderItem *procmsg_spam_get_folder (MsgInfo *msginfo)
2465 FolderItem *item = NULL;
2467 if (procmsg_spam_get_folder_func)
2468 item = procmsg_spam_get_folder_func(msginfo);
2469 if (item == NULL && spam_folder_item)
2470 item = folder_find_item_from_identifier(spam_folder_item);
2471 if (item == NULL)
2472 item = folder_get_default_trash();
2473 return item;
2476 static void item_has_queued_mails(FolderItem *item, gpointer data)
2478 gboolean *result = (gboolean *)data;
2479 if (*result == TRUE)
2480 return;
2481 if (folder_has_parent_of_type(item, F_QUEUE)) {
2482 if (item->total_msgs == 0)
2483 return;
2484 else {
2485 GSList *msglist = folder_item_get_msg_list(item);
2486 GSList *cur;
2487 for (cur = msglist; cur; cur = cur->next) {
2488 MsgInfo *msginfo = (MsgInfo *)cur->data;
2489 if (!MSG_IS_DELETED(msginfo->flags) &&
2490 !MSG_IS_LOCKED(msginfo->flags)) {
2491 *result = TRUE;
2492 break;
2495 procmsg_msg_list_free(msglist);
2500 gboolean procmsg_have_queued_mails_fast (void)
2502 gboolean result = FALSE;
2503 folder_func_to_all_folders(item_has_queued_mails, &result);
2504 return result;
2507 static void item_has_trashed_mails(FolderItem *item, gpointer data)
2509 gboolean *result = (gboolean *)data;
2510 if (*result == TRUE)
2511 return;
2512 if (folder_has_parent_of_type(item, F_TRASH) && item->total_msgs > 0)
2513 *result = TRUE;
2516 gboolean procmsg_have_trashed_mails_fast (void)
2518 gboolean result = FALSE;
2519 folder_func_to_all_folders(item_has_trashed_mails, &result);
2520 return result;
2523 gchar *procmsg_msginfo_get_tags_str(MsgInfo *msginfo)
2525 GSList *cur = NULL;
2526 gchar *tags = NULL;
2528 if (!msginfo)
2529 return NULL;
2531 if (msginfo->tags == NULL)
2532 return NULL;
2533 for (cur = msginfo->tags; cur; cur = cur->next) {
2534 const gchar *tag = tags_get_tag(GPOINTER_TO_INT(cur->data));
2535 if (!tag)
2536 continue;
2537 if (!tags)
2538 tags = g_strdup(tag);
2539 else {
2540 int olen = strlen(tags);
2541 int nlen = olen + strlen(tag) + 2 /* strlen(", ") */;
2542 tags = g_realloc(tags, nlen+1);
2543 if (!tags)
2544 return NULL;
2545 strcpy(tags+olen, ", ");
2546 strcpy(tags+olen+2, tag);
2547 tags[nlen]='\0';
2550 return tags;
2553 void procmsg_msginfo_update_tags(MsgInfo *msginfo, gboolean set, gint id)
2555 GSList changed;
2557 if (id == 0)
2558 return;
2560 if (!set) {
2561 msginfo->tags = g_slist_remove(
2562 msginfo->tags,
2563 GINT_TO_POINTER(id));
2564 changed.data = GINT_TO_POINTER(id);
2565 changed.next = NULL;
2566 folder_item_commit_tags(msginfo->folder, msginfo, NULL, &changed);
2567 } else {
2568 if (!g_slist_find(msginfo->tags, GINT_TO_POINTER(id))) {
2569 msginfo->tags = g_slist_append(
2570 msginfo->tags,
2571 GINT_TO_POINTER(id));
2573 changed.data = GINT_TO_POINTER(id);
2574 changed.next = NULL;
2575 folder_item_commit_tags(msginfo->folder, msginfo, &changed, NULL);
2580 void procmsg_msginfo_clear_tags(MsgInfo *msginfo)
2582 GSList *unset = msginfo->tags;
2583 msginfo->tags = NULL;
2584 folder_item_commit_tags(msginfo->folder, msginfo, NULL, unset);
2585 g_slist_free(unset);