2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 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 2 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include "procheader.h"
32 #include "send_message.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
38 #include "prefs_common.h"
43 #include "alertpanel.h"
48 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
50 GHashTable *msg_table;
52 if (mlist == NULL) return NULL;
54 msg_table = g_hash_table_new(NULL, g_direct_equal);
55 procmsg_msg_hash_table_append(msg_table, mlist);
60 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
65 if (msg_table == NULL || mlist == NULL) return;
67 for (cur = mlist; cur != NULL; cur = cur->next) {
68 msginfo = (MsgInfo *)cur->data;
70 g_hash_table_insert(msg_table,
71 GUINT_TO_POINTER(msginfo->msgnum),
76 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
78 GHashTable *msg_table;
82 if (mlist == NULL) return NULL;
84 msg_table = g_hash_table_new(NULL, g_direct_equal);
86 for (cur = mlist; cur != NULL; cur = cur->next) {
87 msginfo = (MsgInfo *)cur->data;
88 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
94 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
100 for (cur = mlist; cur != NULL; cur = cur->next) {
101 msginfo = (MsgInfo *)cur->data;
102 if (msginfo && msginfo->msgnum > last)
103 last = msginfo->msgnum;
109 void procmsg_msg_list_free(GSList *mlist)
114 for (cur = mlist; cur != NULL; cur = cur->next) {
115 msginfo = (MsgInfo *)cur->data;
116 procmsg_msginfo_free(msginfo);
130 static gboolean procmsg_ignore_node(GNode *node, gpointer data)
132 MsgInfo *msginfo = (MsgInfo *)node->data;
134 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
135 procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
140 /* CLAWS subject threading:
142 in the first round it inserts subject lines in a
143 relation (subject <-> node)
145 the second round finishes the threads by attaching
146 matching subject lines to the one found in the
147 relation. will use the oldest node with the same
148 subject that is not more then thread_by_subject_max_age
149 days old (see subject_relation_lookup)
152 static void subject_relation_insert(GRelation *relation, GNode *node)
157 g_return_if_fail(relation != NULL);
158 g_return_if_fail(node != NULL);
159 msginfo = (MsgInfo *) node->data;
160 g_return_if_fail(msginfo != NULL);
162 subject = msginfo->subject;
165 subject += subject_get_prefix_length(subject);
167 g_relation_insert(relation, subject, node);
170 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
177 g_return_val_if_fail(relation != NULL, NULL);
179 subject = msginfo->subject;
182 prefix_length = subject_get_prefix_length(subject);
183 if (prefix_length <= 0)
185 subject += prefix_length;
187 tuples = g_relation_select(relation, subject, 0);
191 if (tuples->len > 0) {
193 GNode *relation_node;
194 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
197 /* check all nodes with the same subject to find the best parent */
198 for (i = 0; i < tuples->len; i++) {
199 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
200 relation_msginfo = (MsgInfo *) relation_node->data;
203 /* best node should be the oldest in the found nodes */
204 /* parent node must not be older then msginfo */
205 if ((relation_msginfo->date_t < msginfo->date_t) &&
206 ((best_msginfo == NULL) ||
207 (best_msginfo->date_t > relation_msginfo->date_t)))
210 /* parent node must not be more then thread_by_subject_max_age
211 days older then msginfo */
212 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
213 prefs_common.thread_by_subject_max_age * 3600 * 24)
216 /* can add new tests for all matching
217 nodes found by subject */
220 node = relation_node;
221 best_msginfo = relation_msginfo;
226 g_tuples_destroy(tuples);
230 /* return the reversed thread tree */
231 GNode *procmsg_get_thread_tree(GSList *mlist)
233 GNode *root, *parent, *node, *next, *last;
234 GNode *prev; /* CLAWS */
235 GHashTable *msgid_table;
236 GRelation *subject_relation;
239 const gchar *subject;
241 root = g_node_new(NULL);
242 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
243 subject_relation = g_relation_new(2);
244 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
246 for (; mlist != NULL; mlist = mlist->next) {
247 msginfo = (MsgInfo *)mlist->data;
250 if (msginfo->inreplyto) {
251 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
252 if (parent == NULL) {
256 node = g_node_insert_data_before
257 (parent, parent == root ? parent->children : NULL,
259 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
260 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
262 /* CLAWS: add subject to relation (without prefix) */
263 if (prefs_common.thread_by_subject) {
264 subject_relation_insert(subject_relation, node);
268 /* complete the unfinished threads */
269 for (node = root->children; node != NULL; ) {
270 prev = node->prev; /* CLAWS: need the last node */
273 msginfo = (MsgInfo *)node->data;
274 if (msginfo->inreplyto) {
275 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
276 /* node should not be the parent, and node should not
277 be an ancestor of parent (circular reference) */
278 if (parent && parent != node &&
279 !g_node_is_ancestor(node, parent)) {
282 (parent, parent->children, node);
285 last = (next == NULL) ? prev : node;
289 if (prefs_common.thread_by_subject) {
290 for (node = last; node && node != NULL;) {
292 msginfo = (MsgInfo *) node->data;
294 /* may not parentize if parent was delivered after childs */
295 if (subject != msginfo->subject)
296 parent = subject_relation_lookup(subject_relation, msginfo);
300 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
301 find the parent node */
302 if (parent != NULL) {
303 if (g_node_is_ancestor(node, parent))
311 g_node_append(parent, node);
318 g_relation_destroy(subject_relation);
319 g_hash_table_destroy(msgid_table);
324 void procmsg_move_messages(GSList *mlist)
326 GSList *cur, *movelist = NULL;
328 FolderItem *dest = NULL;
332 folder_item_update_freeze();
334 for (cur = mlist; cur != NULL; cur = cur->next) {
335 msginfo = (MsgInfo *)cur->data;
337 dest = msginfo->to_folder;
338 movelist = g_slist_append(movelist, msginfo);
339 } else if (dest == msginfo->to_folder) {
340 movelist = g_slist_append(movelist, msginfo);
342 folder_item_move_msgs(dest, movelist);
343 g_slist_free(movelist);
345 dest = msginfo->to_folder;
346 movelist = g_slist_append(movelist, msginfo);
348 procmsg_msginfo_set_to_folder(msginfo, NULL);
352 folder_item_move_msgs(dest, movelist);
353 g_slist_free(movelist);
356 folder_item_update_thaw();
359 void procmsg_copy_messages(GSList *mlist)
361 GSList *cur, *copylist = NULL;
363 FolderItem *dest = NULL;
367 folder_item_update_freeze();
369 for (cur = mlist; cur != NULL; cur = cur->next) {
370 msginfo = (MsgInfo *)cur->data;
372 dest = msginfo->to_folder;
373 copylist = g_slist_append(copylist, msginfo);
374 } else if (dest == msginfo->to_folder) {
375 copylist = g_slist_append(copylist, msginfo);
377 folder_item_copy_msgs(dest, copylist);
378 g_slist_free(copylist);
380 dest = msginfo->to_folder;
381 copylist = g_slist_append(copylist, msginfo);
383 procmsg_msginfo_set_to_folder(msginfo, NULL);
387 folder_item_copy_msgs(dest, copylist);
388 g_slist_free(copylist);
391 folder_item_update_thaw();
394 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
398 g_return_val_if_fail(msginfo != NULL, NULL);
400 if (msginfo->plaintext_file)
401 file = g_strdup(msginfo->plaintext_file);
403 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
409 gchar *procmsg_get_message_file(MsgInfo *msginfo)
411 gchar *filename = NULL;
413 g_return_val_if_fail(msginfo != NULL, NULL);
415 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
417 g_warning("can't fetch message %d\n", msginfo->msgnum);
422 GSList *procmsg_get_message_file_list(GSList *mlist)
424 GSList *file_list = NULL;
426 MsgFileInfo *fileinfo;
429 while (mlist != NULL) {
430 msginfo = (MsgInfo *)mlist->data;
431 file = procmsg_get_message_file(msginfo);
433 procmsg_message_file_list_free(file_list);
436 fileinfo = g_new(MsgFileInfo, 1);
437 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
438 fileinfo->file = file;
439 fileinfo->flags = g_new(MsgFlags, 1);
440 *fileinfo->flags = msginfo->flags;
441 file_list = g_slist_prepend(file_list, fileinfo);
445 file_list = g_slist_reverse(file_list);
450 void procmsg_message_file_list_free(MsgInfoList *file_list)
453 MsgFileInfo *fileinfo;
455 for (cur = file_list; cur != NULL; cur = cur->next) {
456 fileinfo = (MsgFileInfo *)cur->data;
457 procmsg_msginfo_free(fileinfo->msginfo);
458 g_free(fileinfo->file);
459 g_free(fileinfo->flags);
463 g_slist_free(file_list);
466 FILE *procmsg_open_message(MsgInfo *msginfo)
471 g_return_val_if_fail(msginfo != NULL, NULL);
473 file = procmsg_get_message_file_path(msginfo);
474 g_return_val_if_fail(file != NULL, NULL);
476 if (!is_file_exist(file)) {
478 file = procmsg_get_message_file(msginfo);
479 g_return_val_if_fail(file != NULL, NULL);
482 if ((fp = fopen(file, "rb")) == NULL) {
483 FILE_OP_ERROR(file, "fopen");
490 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
493 while (fgets(buf, sizeof(buf), fp) != NULL)
494 if (buf[0] == '\r' || buf[0] == '\n') break;
501 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
507 g_return_val_if_fail(msginfo != NULL, NULL);
509 if (mimeinfo) *mimeinfo = NULL;
511 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
513 mimeinfo_ = procmime_scan_mime_header(fp);
519 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
520 rfc2015_is_encrypted(mimeinfo_)) {
521 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
524 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
525 !msginfo->plaintext_file &&
526 !msginfo->decryption_failed) {
528 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
529 if (msginfo->plaintext_file &&
530 !msginfo->decryption_failed) {
532 procmime_mimeinfo_free_all(mimeinfo_);
533 if ((fp = procmsg_open_message(msginfo)) == NULL)
535 mimeinfo_ = procmime_scan_mime_header(fp);
541 if (fseek(fp, fpos, SEEK_SET) < 0)
546 if (mimeinfo) *mimeinfo = mimeinfo_;
551 gboolean procmsg_msg_exist(MsgInfo *msginfo)
556 if (!msginfo) return FALSE;
558 path = folder_item_get_path(msginfo->folder);
560 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
566 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
567 PrefsFilterType type)
569 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
570 {"X-ML-Name:", NULL, TRUE},
571 {"X-List:", NULL, TRUE},
572 {"X-Mailing-list:", NULL, TRUE},
573 {"List-Id:", NULL, TRUE},
574 {"X-Sequence:", NULL, TRUE},
575 {NULL, NULL, FALSE}};
581 H_X_MAILING_LIST = 3,
588 g_return_if_fail(msginfo != NULL);
589 g_return_if_fail(header != NULL);
590 g_return_if_fail(key != NULL);
599 if ((fp = procmsg_open_message(msginfo)) == NULL)
601 procheader_get_header_fields(fp, hentry);
604 #define SET_FILTER_KEY(hstr, idx) \
606 *header = g_strdup(hstr); \
607 *key = hentry[idx].body; \
608 hentry[idx].body = NULL; \
611 if (hentry[H_X_BEENTHERE].body != NULL) {
612 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
613 } else if (hentry[H_X_ML_NAME].body != NULL) {
614 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
615 } else if (hentry[H_X_LIST].body != NULL) {
616 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
617 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
618 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
619 } else if (hentry[H_LIST_ID].body != NULL) {
620 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
621 extract_list_id_str(*key);
622 } else if (hentry[H_X_SEQUENCE].body != NULL) {
625 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
628 while (*p != '\0' && !isspace(*p)) p++;
629 while (isspace(*p)) p++;
636 } else if (msginfo->subject) {
637 *header = g_strdup("subject");
638 *key = g_strdup(msginfo->subject);
641 #undef SET_FILTER_KEY
643 g_free(hentry[H_X_BEENTHERE].body);
644 hentry[H_X_BEENTHERE].body = NULL;
645 g_free(hentry[H_X_ML_NAME].body);
646 hentry[H_X_ML_NAME].body = NULL;
647 g_free(hentry[H_X_LIST].body);
648 hentry[H_X_LIST].body = NULL;
649 g_free(hentry[H_X_MAILING_LIST].body);
650 hentry[H_X_MAILING_LIST].body = NULL;
651 g_free(hentry[H_LIST_ID].body);
652 hentry[H_LIST_ID].body = NULL;
656 *header = g_strdup("from");
657 *key = g_strdup(msginfo->from);
660 *header = g_strdup("to");
661 *key = g_strdup(msginfo->to);
663 case FILTER_BY_SUBJECT:
664 *header = g_strdup("subject");
665 *key = g_strdup(msginfo->subject);
672 void procmsg_empty_trash(void)
677 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
678 trash = FOLDER(cur->data)->trash;
679 if (trash && trash->total_msgs > 0)
680 folder_item_remove_all_msg(trash);
685 *\brief Send messages in queue
687 *\param queue Queue folder to process
688 *\param save_msgs Unused
690 *\return Number of messages sent, negative if an error occurred
691 * positive if no error occurred
693 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
695 gint ret = 1, count = 0;
699 queue = folder_get_default_queue();
700 g_return_val_if_fail(queue != NULL, -1);
702 folder_item_scan(queue);
703 list = folder_item_get_msg_list(queue);
705 for (elem = list; elem != NULL; elem = elem->next) {
709 msginfo = (MsgInfo *)(elem->data);
710 if (!MSG_IS_LOCKED(msginfo->flags)) {
711 file = folder_item_fetch_msg(queue, msginfo->msgnum);
713 if (procmsg_send_message_queue(file) < 0) {
714 g_warning("Sending queued message %d failed.\n",
719 * We save in procmsg_send_message_queue because
720 * we need the destination folder from the queue
724 procmsg_save_to_outbox
725 (queue->folder->outbox,
729 folder_item_remove_msg(queue, msginfo->msgnum);
734 /* FIXME: supposedly if only one message is locked, and queue
735 * is being flushed, the following free says something like
736 * "freeing msg ## in folder (nil)". */
737 procmsg_msginfo_free(msginfo);
743 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
748 if ((fp = fopen(in, "rb")) == NULL) {
749 FILE_OP_ERROR(in, "fopen");
752 if ((outfp = fopen(out, "wb")) == NULL) {
753 FILE_OP_ERROR(out, "fopen");
757 while (fgets(buf, sizeof(buf), fp) != NULL)
758 if (buf[0] == '\r' || buf[0] == '\n') break;
759 while (fgets(buf, sizeof(buf), fp) != NULL)
766 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
770 MsgInfo *msginfo, *tmp_msginfo;
771 MsgFlags flag = {0, 0};
773 debug_print("saving sent message...\n");
776 outbox = folder_get_default_outbox();
777 g_return_val_if_fail(outbox != NULL, -1);
779 /* remove queueing headers */
781 gchar tmp[MAXPATHLEN + 1];
783 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
784 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
786 if (procmsg_remove_special_headers(file, tmp) !=0)
789 folder_item_scan(outbox);
790 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
791 g_warning("can't save message\n");
796 folder_item_scan(outbox);
797 if ((num = folder_item_add_msg
798 (outbox, file, &flag, FALSE)) < 0) {
799 g_warning("can't save message\n");
804 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
805 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
806 if (msginfo != NULL) {
807 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
808 procmsg_msginfo_free(msginfo); /* refcnt-- */
809 /* tmp_msginfo == msginfo */
810 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
811 msginfo->returnreceiptto)) {
812 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
813 procmsg_msginfo_free(msginfo); /* refcnt-- */
816 folder_item_update(outbox, TRUE);
821 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
823 static const gchar *def_cmd = "lpr %s";
830 g_return_if_fail(msginfo);
832 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
833 g_warning("Can't get text part\n");
837 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
838 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
840 if ((prfp = fopen(prtmp, "wb")) == NULL) {
841 FILE_OP_ERROR(prtmp, "fopen");
847 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
848 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
849 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
850 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
851 if (msginfo->newsgroups)
852 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
853 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
856 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
862 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
864 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
867 g_warning("Print command line is invalid: `%s'\n",
869 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
875 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
879 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
886 MsgInfo *procmsg_msginfo_new(void)
890 newmsginfo = g_new0(MsgInfo, 1);
891 newmsginfo->refcnt = 1;
896 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
900 if (msginfo == NULL) return NULL;
902 newmsginfo = g_new0(MsgInfo, 1);
904 newmsginfo->refcnt = 1;
906 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
907 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
908 g_strdup(msginfo->mmb) : NULL
932 MEMBDUP(dispositionnotificationto);
933 MEMBDUP(returnreceiptto);
937 MEMBCOPY(threadscore);
942 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
944 MsgInfo *full_msginfo;
947 if (msginfo == NULL) return NULL;
949 file = procmsg_get_message_file(msginfo);
951 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
955 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
957 if (!full_msginfo) return NULL;
959 /* CLAWS: make sure we add the missing members; see:
960 * procheader.c::procheader_get_headernames() */
962 msginfo->xface = g_strdup(full_msginfo->xface);
963 if (!msginfo->dispositionnotificationto)
964 msginfo->dispositionnotificationto =
965 g_strdup(full_msginfo->dispositionnotificationto);
966 if (!msginfo->returnreceiptto)
967 msginfo->returnreceiptto = g_strdup
968 (full_msginfo->returnreceiptto);
969 procmsg_msginfo_free(full_msginfo);
971 return procmsg_msginfo_new_ref(msginfo);
973 full_msginfo->msgnum = msginfo->msgnum;
974 full_msginfo->size = msginfo->size;
975 full_msginfo->mtime = msginfo->mtime;
976 full_msginfo->folder = msginfo->folder;
978 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
979 full_msginfo->decryption_failed = msginfo->decryption_failed;
981 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
987 void procmsg_msginfo_free(MsgInfo *msginfo)
989 if (msginfo == NULL) return;
992 if (msginfo->refcnt > 0)
995 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
997 if (msginfo->to_folder) {
998 msginfo->to_folder->op_count--;
999 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1002 g_free(msginfo->fromspace);
1003 g_free(msginfo->references);
1004 g_free(msginfo->returnreceiptto);
1005 g_free(msginfo->dispositionnotificationto);
1006 g_free(msginfo->xface);
1008 g_free(msginfo->fromname);
1010 g_free(msginfo->date);
1011 g_free(msginfo->from);
1012 g_free(msginfo->to);
1013 g_free(msginfo->cc);
1014 g_free(msginfo->newsgroups);
1015 g_free(msginfo->subject);
1016 g_free(msginfo->msgid);
1017 g_free(msginfo->inreplyto);
1018 g_free(msginfo->xref);
1023 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1027 memusage += sizeof(MsgInfo);
1028 if (msginfo->fromname)
1029 memusage += strlen(msginfo->fromname);
1031 memusage += strlen(msginfo->date);
1033 memusage += strlen(msginfo->from);
1035 memusage += strlen(msginfo->to);
1037 memusage += strlen(msginfo->cc);
1038 if (msginfo->newsgroups)
1039 memusage += strlen(msginfo->newsgroups);
1040 if (msginfo->subject)
1041 memusage += strlen(msginfo->subject);
1043 memusage += strlen(msginfo->msgid);
1044 if (msginfo->inreplyto)
1045 memusage += strlen(msginfo->inreplyto);
1047 memusage += strlen(msginfo->xface);
1048 if (msginfo->dispositionnotificationto)
1049 memusage += strlen(msginfo->dispositionnotificationto);
1050 if (msginfo->returnreceiptto)
1051 memusage += strlen(msginfo->returnreceiptto);
1052 if (msginfo->references)
1053 memusage += strlen(msginfo->references);
1054 if (msginfo->fromspace)
1055 memusage += strlen(msginfo->fromspace);
1060 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1062 const MsgInfo *msginfo1 = a;
1063 const MsgInfo *msginfo2 = b;
1070 return msginfo1->msgnum - msginfo2->msgnum;
1079 Q_MAIL_ACCOUNT_ID = 4,
1080 Q_NEWS_ACCOUNT_ID = 5,
1081 Q_SAVE_COPY_FOLDER = 6,
1082 Q_REPLY_MESSAGE_ID = 7,
1083 Q_FWD_MESSAGE_ID = 8
1086 gint procmsg_send_message_queue(const gchar *file)
1088 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1089 {"SSV:", NULL, FALSE},
1090 {"R:", NULL, FALSE},
1091 {"NG:", NULL, FALSE},
1092 {"MAID:", NULL, FALSE},
1093 {"NAID:", NULL, FALSE},
1094 {"SCF:", NULL, FALSE},
1095 {"RMID:", NULL, FALSE},
1096 {"FMID:", NULL, FALSE},
1097 {NULL, NULL, FALSE}};
1100 gint mailval = 0, newsval = 0;
1102 gchar *smtpserver = NULL;
1103 GSList *to_list = NULL;
1104 GSList *newsgroup_list = NULL;
1105 gchar *savecopyfolder = NULL;
1106 gchar *replymessageid = NULL;
1107 gchar *fwdmessageid = NULL;
1108 gchar buf[BUFFSIZE];
1110 PrefsAccount *mailac = NULL, *newsac = NULL;
1113 g_return_val_if_fail(file != NULL, -1);
1115 if ((fp = fopen(file, "rb")) == NULL) {
1116 FILE_OP_ERROR(file, "fopen");
1120 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1122 gchar *p = buf + strlen(qentry[hnum].name);
1126 if (!from) from = g_strdup(p);
1129 if (!smtpserver) smtpserver = g_strdup(p);
1132 to_list = address_list_append(to_list, p);
1135 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1137 case Q_MAIL_ACCOUNT_ID:
1138 mailac = account_find_from_id(atoi(p));
1140 case Q_NEWS_ACCOUNT_ID:
1141 newsac = account_find_from_id(atoi(p));
1143 case Q_SAVE_COPY_FOLDER:
1144 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1146 case Q_REPLY_MESSAGE_ID:
1147 if (!replymessageid) replymessageid = g_strdup(p);
1149 case Q_FWD_MESSAGE_ID:
1150 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1154 filepos = ftell(fp);
1157 debug_print("Sending message by mail\n");
1159 g_warning("Queued message header is broken.\n");
1161 } else if (mailac && mailac->use_mail_command &&
1162 mailac->mail_command && (* mailac->mail_command)) {
1163 mailval = send_message_local(mailac->mail_command, fp);
1165 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1166 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1170 mailac = account_find_from_smtp_server(from, smtpserver);
1172 g_warning("Account not found. "
1173 "Using current account...\n");
1174 mailac = cur_account;
1179 mailval = send_message_smtp(mailac, to_list, fp);
1181 PrefsAccount tmp_ac;
1183 g_warning("Account not found.\n");
1185 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1186 tmp_ac.address = from;
1187 tmp_ac.smtp_server = smtpserver;
1188 tmp_ac.smtpport = SMTP_PORT;
1189 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1194 fseek(fp, filepos, SEEK_SET);
1195 if (newsgroup_list && (newsval == 0)) {
1200 /* write to temporary file */
1201 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1202 G_DIR_SEPARATOR, (gint)file);
1203 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1204 FILE_OP_ERROR(tmp, "fopen");
1206 alertpanel_error(_("Could not create temporary file for news sending."));
1208 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1209 FILE_OP_ERROR(tmp, "chmod");
1210 g_warning("can't change file mode\n");
1213 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1214 if (fputs(buf, tmpfp) == EOF) {
1215 FILE_OP_ERROR(tmp, "fputs");
1217 alertpanel_error(_("Error when writing temporary file for news sending."));
1223 debug_print("Sending message by news\n");
1225 folder = FOLDER(newsac->folder);
1227 newsval = news_post(folder, tmp);
1229 alertpanel_error(_("Error occurred while posting the message to %s ."),
1230 newsac->nntp_server);
1238 slist_free_strings(to_list);
1239 g_slist_free(to_list);
1240 slist_free_strings(newsgroup_list);
1241 g_slist_free(newsgroup_list);
1246 /* save message to outbox */
1247 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1250 debug_print("saving sent message...\n");
1252 outbox = folder_find_item_from_identifier(savecopyfolder);
1254 outbox = folder_get_default_outbox();
1256 procmsg_save_to_outbox(outbox, file, TRUE);
1259 if (replymessageid != NULL || fwdmessageid != NULL) {
1263 if (replymessageid != NULL)
1264 tokens = g_strsplit(replymessageid, "\x7f", 0);
1266 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1267 item = folder_find_item_from_identifier(tokens[0]);
1269 /* check if queued message has valid folder and message id */
1270 if (item != NULL && tokens[2] != NULL) {
1273 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1275 /* check if referring message exists and has a message id */
1276 if ((msginfo != NULL) &&
1277 (msginfo->msgid != NULL) &&
1278 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1279 procmsg_msginfo_free(msginfo);
1283 if (msginfo == NULL) {
1284 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1287 if (msginfo != NULL) {
1288 if (replymessageid != NULL) {
1289 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1290 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1293 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1294 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1296 procmsg_msginfo_free(msginfo);
1302 g_free(savecopyfolder);
1303 g_free(replymessageid);
1304 g_free(fwdmessageid);
1306 return (newsval != 0 ? newsval : mailval);
1309 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1311 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1314 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1318 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1323 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1324 item->unread_msgs++;
1325 if (procmsg_msg_has_marked_parent(msginfo))
1326 item->unreadmarked_msgs++;
1329 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1330 item->unread_msgs--;
1331 if (procmsg_msg_has_marked_parent(msginfo))
1332 item->unreadmarked_msgs--;
1336 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1337 procmsg_update_unread_children(msginfo, TRUE);
1340 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1341 procmsg_update_unread_children(msginfo, FALSE);
1345 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1348 MsgInfoUpdate msginfo_update;
1349 MsgPermFlags perm_flags_new, perm_flags_old;
1351 g_return_if_fail(msginfo != NULL);
1352 item = msginfo->folder;
1353 g_return_if_fail(item != NULL);
1355 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1357 /* Perm Flags handling */
1358 perm_flags_old = msginfo->flags.perm_flags;
1359 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1360 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1361 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1364 if (perm_flags_old != perm_flags_new) {
1365 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1367 update_folder_msg_counts(item, msginfo, perm_flags_old);
1369 msginfo_update.msginfo = msginfo;
1370 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1371 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1372 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1375 /* Tmp flags hanlding */
1376 msginfo->flags.tmp_flags |= tmp_flags;
1379 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1382 MsgInfoUpdate msginfo_update;
1383 MsgPermFlags perm_flags_new, perm_flags_old;
1385 g_return_if_fail(msginfo != NULL);
1386 item = msginfo->folder;
1387 g_return_if_fail(item != NULL);
1389 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1391 /* Perm Flags handling */
1392 perm_flags_old = msginfo->flags.perm_flags;
1393 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1395 if (perm_flags_old != perm_flags_new) {
1396 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1398 update_folder_msg_counts(item, msginfo, perm_flags_old);
1400 msginfo_update.msginfo = msginfo;
1401 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1402 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1403 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1406 /* Tmp flags hanlding */
1407 msginfo->flags.tmp_flags &= ~tmp_flags;
1411 *\brief check for flags (e.g. mark) in prior msgs of current thread
1413 *\param info Current message
1414 *\param perm_flags Flags to be checked
1415 *\param parentmsgs Hash of prior msgs to avoid loops
1417 *\return gboolean TRUE if perm_flags are found
1419 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1420 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1424 g_return_val_if_fail(info != NULL, FALSE);
1426 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1427 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1429 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1430 procmsg_msginfo_free(tmp);
1432 } else if (tmp != NULL) {
1435 if (g_hash_table_lookup(parentmsgs, info)) {
1436 debug_print("loop detected: %s%c%d\n",
1437 folder_item_get_path(info->folder),
1438 G_DIR_SEPARATOR, info->msgnum);
1441 g_hash_table_insert(parentmsgs, info, "1");
1442 result = procmsg_msg_has_flagged_parent_real(
1443 tmp, perm_flags, parentmsgs);
1445 procmsg_msginfo_free(tmp);
1455 *\brief Callback for cleaning up hash of parentmsgs
1457 gboolean parentmsgs_hash_remove(gpointer key,
1465 *\brief Set up list of parentmsgs
1466 * See procmsg_msg_has_flagged_parent_real()
1468 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1471 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1473 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1474 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1475 g_hash_table_destroy(parentmsgs);
1480 *\brief Check if msgs prior in thread are marked
1481 * See procmsg_msg_has_flagged_parent_real()
1483 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1485 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1489 GSList *procmsg_find_children_func(MsgInfo *info,
1490 GSList *children, GSList *all)
1494 g_return_val_if_fail(info!=NULL, children);
1495 if (info->msgid == NULL)
1498 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1499 MsgInfo *tmp = (MsgInfo *)cur->data;
1500 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1501 /* Check if message is already in the list */
1502 if ((children == NULL) ||
1503 (g_slist_index(children, tmp) == -1)) {
1504 children = g_slist_prepend(children,
1505 procmsg_msginfo_new_ref(tmp));
1506 children = procmsg_find_children_func(tmp,
1515 GSList *procmsg_find_children (MsgInfo *info)
1520 g_return_val_if_fail(info!=NULL, NULL);
1521 all = folder_item_get_msg_list(info->folder);
1522 children = procmsg_find_children_func(info, NULL, all);
1523 if (children != NULL) {
1524 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1525 /* this will not free the used pointers
1526 created with procmsg_msginfo_new_ref */
1527 procmsg_msginfo_free((MsgInfo *)cur->data);
1535 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1537 GSList *children = procmsg_find_children(info);
1539 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1540 MsgInfo *tmp = (MsgInfo *)cur->data;
1541 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1543 info->folder->unreadmarked_msgs++;
1545 info->folder->unreadmarked_msgs--;
1546 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1548 procmsg_msginfo_free(tmp);
1550 g_slist_free(children);
1554 * Set the destination folder for a copy or move operation
1556 * \param msginfo The message which's destination folder is changed
1557 * \param to_folder The destination folder for the operation
1559 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1561 if(msginfo->to_folder != NULL) {
1562 msginfo->to_folder->op_count--;
1563 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1565 msginfo->to_folder = to_folder;
1566 if(to_folder != NULL) {
1567 to_folder->op_count++;
1568 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1573 * Apply filtering actions to the msginfo
1575 * \param msginfo The MsgInfo describing the message that should be filtered
1576 * \return TRUE if the message was moved and MsgInfo is now invalid,
1579 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1581 MailFilteringData mail_filtering_data;
1583 mail_filtering_data.msginfo = msginfo;
1584 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1587 /* filter if enabled in prefs or move to inbox if not */
1588 if((global_processing != NULL) &&
1589 filter_message_by_msginfo(global_processing, msginfo))