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 hash
143 table. a duplicate subject line replaces the one in
144 the table only if its older. (this round should actually
145 create a list of all duplicate subject lines)
147 the second round finishes the threads by attaching
148 duplicate subject lines to the one found in the
149 hash table. as soon as a subject line is found that
150 is too old, that one becomes the new parent for
151 the next iteration. (this fails when a parent arrived
152 later than its child.)
155 static void subject_relation_insert(GRelation *relation, GNode *node)
160 g_return_if_fail(relation != NULL);
161 g_return_if_fail(node != NULL);
162 msginfo = (MsgInfo *) node->data;
163 g_return_if_fail(msginfo != NULL);
165 subject = msginfo->subject;
168 subject += subject_get_prefix_length(subject);
170 g_relation_insert(relation, subject, node);
173 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
179 g_return_val_if_fail(relation != NULL, NULL);
181 subject = msginfo->subject;
184 subject += subject_get_prefix_length(subject);
186 tuples = g_relation_select(relation, subject, 0);
190 if (tuples->len > 0) {
192 GNode *relation_node;
193 MsgInfo *relation_msginfo, *best_msginfo;
196 /* check all nodes with the same subject to find the best parent */
197 for (i = 0; i < tuples->len; i++) {
198 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
199 relation_msginfo = (MsgInfo *) relation_node->data;
202 /* best node should be the oldest in the found nodes */
203 /* parent node must not be older then msginfo */
204 if (best_msginfo->date_t < relation_msginfo->date_t &&
205 relation_msginfo->date_t < msginfo->date_t)
208 /* parent node must not be more then thread_by_subject_max_age
209 days older then msginfo */
210 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
211 prefs_common.thread_by_subject_max_age * 3600 * 24)
214 /* can add new tests for all matching
215 nodes found by subject */
218 node = relation_node;
219 best_msginfo = relation_msginfo;
224 g_tuples_destroy(tuples);
228 /* return the reversed thread tree */
229 GNode *procmsg_get_thread_tree(GSList *mlist)
231 GNode *root, *parent, *node, *next, *last;
232 GNode *prev; /* CLAWS */
233 GHashTable *msgid_table;
234 GRelation *subject_relation;
237 const gchar *subject;
239 root = g_node_new(NULL);
240 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
241 subject_relation = g_relation_new(2);
242 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
244 for (; mlist != NULL; mlist = mlist->next) {
245 msginfo = (MsgInfo *)mlist->data;
248 if (msginfo->inreplyto) {
249 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
250 if (parent == NULL) {
254 node = g_node_insert_data_before
255 (parent, parent == root ? parent->children : NULL,
257 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
258 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
260 /* CLAWS: add subject to relation (without prefix) */
261 if (prefs_common.thread_by_subject) {
262 subject_relation_insert(subject_relation, node);
266 /* complete the unfinished threads */
267 for (node = root->children; node != NULL; ) {
268 prev = node->prev; /* CLAWS: need the last node */
271 msginfo = (MsgInfo *)node->data;
272 if (msginfo->inreplyto) {
273 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
274 /* node should not be the parent, and node should not
275 be an ancestor of parent (circular reference) */
276 if (parent && parent != node &&
277 !g_node_is_ancestor(node, parent)) {
280 (parent, parent->children, node);
283 last = (next == NULL) ? prev : node;
287 if (prefs_common.thread_by_subject) {
288 for (node = last; node && node != NULL;) {
290 msginfo = (MsgInfo *) node->data;
292 /* may not parentize if parent was delivered after childs */
293 if (subject != msginfo->subject)
294 parent = subject_relation_lookup(subject_relation, msginfo);
298 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
299 find the parent node */
300 if (parent != NULL) {
301 if (g_node_is_ancestor(node, parent))
309 g_node_append(parent, node);
316 g_relation_destroy(subject_relation);
317 g_hash_table_destroy(msgid_table);
322 void procmsg_move_messages(GSList *mlist)
324 GSList *cur, *movelist = NULL;
326 FolderItem *dest = NULL;
330 folder_item_update_freeze();
332 for (cur = mlist; cur != NULL; cur = cur->next) {
333 msginfo = (MsgInfo *)cur->data;
335 dest = msginfo->to_folder;
336 movelist = g_slist_append(movelist, msginfo);
337 } else if (dest == msginfo->to_folder) {
338 movelist = g_slist_append(movelist, msginfo);
340 folder_item_move_msgs(dest, movelist);
341 g_slist_free(movelist);
343 dest = msginfo->to_folder;
344 movelist = g_slist_append(movelist, msginfo);
346 procmsg_msginfo_set_to_folder(msginfo, NULL);
350 folder_item_move_msgs(dest, movelist);
351 g_slist_free(movelist);
354 folder_item_update_thaw();
357 void procmsg_copy_messages(GSList *mlist)
359 GSList *cur, *copylist = NULL;
361 FolderItem *dest = NULL;
365 folder_item_update_freeze();
367 for (cur = mlist; cur != NULL; cur = cur->next) {
368 msginfo = (MsgInfo *)cur->data;
370 dest = msginfo->to_folder;
371 copylist = g_slist_append(copylist, msginfo);
372 } else if (dest == msginfo->to_folder) {
373 copylist = g_slist_append(copylist, msginfo);
375 folder_item_copy_msgs(dest, copylist);
376 g_slist_free(copylist);
378 dest = msginfo->to_folder;
379 copylist = g_slist_append(copylist, msginfo);
381 procmsg_msginfo_set_to_folder(msginfo, NULL);
385 folder_item_copy_msgs(dest, copylist);
386 g_slist_free(copylist);
389 folder_item_update_thaw();
392 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
396 g_return_val_if_fail(msginfo != NULL, NULL);
398 if (msginfo->plaintext_file)
399 file = g_strdup(msginfo->plaintext_file);
401 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
407 gchar *procmsg_get_message_file(MsgInfo *msginfo)
409 gchar *filename = NULL;
411 g_return_val_if_fail(msginfo != NULL, NULL);
413 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
415 g_warning("can't fetch message %d\n", msginfo->msgnum);
420 GSList *procmsg_get_message_file_list(GSList *mlist)
422 GSList *file_list = NULL;
424 MsgFileInfo *fileinfo;
427 while (mlist != NULL) {
428 msginfo = (MsgInfo *)mlist->data;
429 file = procmsg_get_message_file(msginfo);
431 procmsg_message_file_list_free(file_list);
434 fileinfo = g_new(MsgFileInfo, 1);
435 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
436 fileinfo->file = file;
437 fileinfo->flags = g_new(MsgFlags, 1);
438 *fileinfo->flags = msginfo->flags;
439 file_list = g_slist_prepend(file_list, fileinfo);
443 file_list = g_slist_reverse(file_list);
448 void procmsg_message_file_list_free(MsgInfoList *file_list)
451 MsgFileInfo *fileinfo;
453 for (cur = file_list; cur != NULL; cur = cur->next) {
454 fileinfo = (MsgFileInfo *)cur->data;
455 procmsg_msginfo_free(fileinfo->msginfo);
456 g_free(fileinfo->file);
457 g_free(fileinfo->flags);
461 g_slist_free(file_list);
464 FILE *procmsg_open_message(MsgInfo *msginfo)
469 g_return_val_if_fail(msginfo != NULL, NULL);
471 file = procmsg_get_message_file_path(msginfo);
472 g_return_val_if_fail(file != NULL, NULL);
474 if (!is_file_exist(file)) {
476 file = procmsg_get_message_file(msginfo);
477 g_return_val_if_fail(file != NULL, NULL);
480 if ((fp = fopen(file, "rb")) == NULL) {
481 FILE_OP_ERROR(file, "fopen");
488 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
491 while (fgets(buf, sizeof(buf), fp) != NULL)
492 if (buf[0] == '\r' || buf[0] == '\n') break;
499 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
505 g_return_val_if_fail(msginfo != NULL, NULL);
507 if (mimeinfo) *mimeinfo = NULL;
509 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
511 mimeinfo_ = procmime_scan_mime_header(fp);
517 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
518 rfc2015_is_encrypted(mimeinfo_)) {
519 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
522 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
523 !msginfo->plaintext_file &&
524 !msginfo->decryption_failed) {
526 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
527 if (msginfo->plaintext_file &&
528 !msginfo->decryption_failed) {
530 procmime_mimeinfo_free_all(mimeinfo_);
531 if ((fp = procmsg_open_message(msginfo)) == NULL)
533 mimeinfo_ = procmime_scan_mime_header(fp);
539 if (fseek(fp, fpos, SEEK_SET) < 0)
544 if (mimeinfo) *mimeinfo = mimeinfo_;
549 gboolean procmsg_msg_exist(MsgInfo *msginfo)
554 if (!msginfo) return FALSE;
556 path = folder_item_get_path(msginfo->folder);
558 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
564 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
565 PrefsFilterType type)
567 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
568 {"X-ML-Name:", NULL, TRUE},
569 {"X-List:", NULL, TRUE},
570 {"X-Mailing-list:", NULL, TRUE},
571 {"List-Id:", NULL, TRUE},
572 {"X-Sequence:", NULL, TRUE},
573 {NULL, NULL, FALSE}};
579 H_X_MAILING_LIST = 3,
586 g_return_if_fail(msginfo != NULL);
587 g_return_if_fail(header != NULL);
588 g_return_if_fail(key != NULL);
597 if ((fp = procmsg_open_message(msginfo)) == NULL)
599 procheader_get_header_fields(fp, hentry);
602 #define SET_FILTER_KEY(hstr, idx) \
604 *header = g_strdup(hstr); \
605 *key = hentry[idx].body; \
606 hentry[idx].body = NULL; \
609 if (hentry[H_X_BEENTHERE].body != NULL) {
610 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
611 } else if (hentry[H_X_ML_NAME].body != NULL) {
612 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
613 } else if (hentry[H_X_LIST].body != NULL) {
614 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
615 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
616 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
617 } else if (hentry[H_LIST_ID].body != NULL) {
618 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
619 extract_list_id_str(*key);
620 } else if (hentry[H_X_SEQUENCE].body != NULL) {
623 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
626 while (*p != '\0' && !isspace(*p)) p++;
627 while (isspace(*p)) p++;
634 } else if (msginfo->subject) {
635 *header = g_strdup("subject");
636 *key = g_strdup(msginfo->subject);
639 #undef SET_FILTER_KEY
641 g_free(hentry[H_X_BEENTHERE].body);
642 hentry[H_X_BEENTHERE].body = NULL;
643 g_free(hentry[H_X_ML_NAME].body);
644 hentry[H_X_ML_NAME].body = NULL;
645 g_free(hentry[H_X_LIST].body);
646 hentry[H_X_LIST].body = NULL;
647 g_free(hentry[H_X_MAILING_LIST].body);
648 hentry[H_X_MAILING_LIST].body = NULL;
649 g_free(hentry[H_LIST_ID].body);
650 hentry[H_LIST_ID].body = NULL;
654 *header = g_strdup("from");
655 *key = g_strdup(msginfo->from);
658 *header = g_strdup("to");
659 *key = g_strdup(msginfo->to);
661 case FILTER_BY_SUBJECT:
662 *header = g_strdup("subject");
663 *key = g_strdup(msginfo->subject);
670 void procmsg_empty_trash(void)
675 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
676 trash = FOLDER(cur->data)->trash;
677 if (trash && trash->total_msgs > 0)
678 folder_item_remove_all_msg(trash);
683 *\brief Send messages in queue
685 *\param queue Queue folder to process
686 *\param save_msgs Unused
688 *\return Number of messages sent, negative if an error occurred
689 * positive if no error occurred
691 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
693 gint ret = 1, count = 0;
697 queue = folder_get_default_queue();
698 g_return_val_if_fail(queue != NULL, -1);
700 folder_item_scan(queue);
701 list = folder_item_get_msg_list(queue);
703 for (elem = list; elem != NULL; elem = elem->next) {
707 msginfo = (MsgInfo *)(elem->data);
708 if (!MSG_IS_LOCKED(msginfo->flags)) {
709 file = folder_item_fetch_msg(queue, msginfo->msgnum);
711 if (procmsg_send_message_queue(file) < 0) {
712 g_warning("Sending queued message %d failed.\n",
717 * We save in procmsg_send_message_queue because
718 * we need the destination folder from the queue
722 procmsg_save_to_outbox
723 (queue->folder->outbox,
727 folder_item_remove_msg(queue, msginfo->msgnum);
732 /* FIXME: supposedly if only one message is locked, and queue
733 * is being flushed, the following free says something like
734 * "freeing msg ## in folder (nil)". */
735 procmsg_msginfo_free(msginfo);
741 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
746 if ((fp = fopen(in, "rb")) == NULL) {
747 FILE_OP_ERROR(in, "fopen");
750 if ((outfp = fopen(out, "wb")) == NULL) {
751 FILE_OP_ERROR(out, "fopen");
755 while (fgets(buf, sizeof(buf), fp) != NULL)
756 if (buf[0] == '\r' || buf[0] == '\n') break;
757 while (fgets(buf, sizeof(buf), fp) != NULL)
764 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
768 MsgInfo *msginfo, *tmp_msginfo;
769 MsgFlags flag = {0, 0};
771 debug_print("saving sent message...\n");
774 outbox = folder_get_default_outbox();
775 g_return_val_if_fail(outbox != NULL, -1);
777 /* remove queueing headers */
779 gchar tmp[MAXPATHLEN + 1];
781 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
782 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
784 if (procmsg_remove_special_headers(file, tmp) !=0)
787 folder_item_scan(outbox);
788 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
789 g_warning("can't save message\n");
794 folder_item_scan(outbox);
795 if ((num = folder_item_add_msg
796 (outbox, file, &flag, FALSE)) < 0) {
797 g_warning("can't save message\n");
802 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
803 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
804 if (msginfo != NULL) {
805 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
806 procmsg_msginfo_free(msginfo); /* refcnt-- */
807 /* tmp_msginfo == msginfo */
808 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
809 msginfo->returnreceiptto)) {
810 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
811 procmsg_msginfo_free(msginfo); /* refcnt-- */
814 folder_item_update(outbox, TRUE);
819 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
821 static const gchar *def_cmd = "lpr %s";
828 g_return_if_fail(msginfo);
830 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
831 g_warning("Can't get text part\n");
835 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
836 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
838 if ((prfp = fopen(prtmp, "wb")) == NULL) {
839 FILE_OP_ERROR(prtmp, "fopen");
845 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
846 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
847 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
848 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
849 if (msginfo->newsgroups)
850 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
851 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
854 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
860 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
862 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
865 g_warning("Print command line is invalid: `%s'\n",
867 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
873 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
877 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
884 MsgInfo *procmsg_msginfo_new(void)
888 newmsginfo = g_new0(MsgInfo, 1);
889 newmsginfo->refcnt = 1;
894 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
898 if (msginfo == NULL) return NULL;
900 newmsginfo = g_new0(MsgInfo, 1);
902 newmsginfo->refcnt = 1;
904 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
905 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
906 g_strdup(msginfo->mmb) : NULL
930 MEMBDUP(dispositionnotificationto);
931 MEMBDUP(returnreceiptto);
935 MEMBCOPY(threadscore);
940 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
942 MsgInfo *full_msginfo;
945 if (msginfo == NULL) return NULL;
947 file = procmsg_get_message_file(msginfo);
949 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
953 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
955 if (!full_msginfo) return NULL;
957 /* CLAWS: make sure we add the missing members; see:
958 * procheader.c::procheader_get_headernames() */
960 msginfo->xface = g_strdup(full_msginfo->xface);
961 if (!msginfo->dispositionnotificationto)
962 msginfo->dispositionnotificationto =
963 g_strdup(full_msginfo->dispositionnotificationto);
964 if (!msginfo->returnreceiptto)
965 msginfo->returnreceiptto = g_strdup
966 (full_msginfo->returnreceiptto);
967 procmsg_msginfo_free(full_msginfo);
969 return procmsg_msginfo_new_ref(msginfo);
971 full_msginfo->msgnum = msginfo->msgnum;
972 full_msginfo->size = msginfo->size;
973 full_msginfo->mtime = msginfo->mtime;
974 full_msginfo->folder = msginfo->folder;
976 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
977 full_msginfo->decryption_failed = msginfo->decryption_failed;
979 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
985 void procmsg_msginfo_free(MsgInfo *msginfo)
987 if (msginfo == NULL) return;
990 if (msginfo->refcnt > 0)
993 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
995 if (msginfo->to_folder) {
996 msginfo->to_folder->op_count--;
997 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1000 g_free(msginfo->fromspace);
1001 g_free(msginfo->references);
1002 g_free(msginfo->returnreceiptto);
1003 g_free(msginfo->dispositionnotificationto);
1004 g_free(msginfo->xface);
1006 g_free(msginfo->fromname);
1008 g_free(msginfo->date);
1009 g_free(msginfo->from);
1010 g_free(msginfo->to);
1011 g_free(msginfo->cc);
1012 g_free(msginfo->newsgroups);
1013 g_free(msginfo->subject);
1014 g_free(msginfo->msgid);
1015 g_free(msginfo->inreplyto);
1016 g_free(msginfo->xref);
1021 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1025 memusage += sizeof(MsgInfo);
1026 if (msginfo->fromname)
1027 memusage += strlen(msginfo->fromname);
1029 memusage += strlen(msginfo->date);
1031 memusage += strlen(msginfo->from);
1033 memusage += strlen(msginfo->to);
1035 memusage += strlen(msginfo->cc);
1036 if (msginfo->newsgroups)
1037 memusage += strlen(msginfo->newsgroups);
1038 if (msginfo->subject)
1039 memusage += strlen(msginfo->subject);
1041 memusage += strlen(msginfo->msgid);
1042 if (msginfo->inreplyto)
1043 memusage += strlen(msginfo->inreplyto);
1045 memusage += strlen(msginfo->xface);
1046 if (msginfo->dispositionnotificationto)
1047 memusage += strlen(msginfo->dispositionnotificationto);
1048 if (msginfo->returnreceiptto)
1049 memusage += strlen(msginfo->returnreceiptto);
1050 if (msginfo->references)
1051 memusage += strlen(msginfo->references);
1052 if (msginfo->fromspace)
1053 memusage += strlen(msginfo->fromspace);
1058 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1060 const MsgInfo *msginfo1 = a;
1061 const MsgInfo *msginfo2 = b;
1068 return msginfo1->msgnum - msginfo2->msgnum;
1077 Q_MAIL_ACCOUNT_ID = 4,
1078 Q_NEWS_ACCOUNT_ID = 5,
1079 Q_SAVE_COPY_FOLDER = 6,
1080 Q_REPLY_MESSAGE_ID = 7,
1081 Q_FWD_MESSAGE_ID = 8
1084 gint procmsg_send_message_queue(const gchar *file)
1086 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1087 {"SSV:", NULL, FALSE},
1088 {"R:", NULL, FALSE},
1089 {"NG:", NULL, FALSE},
1090 {"MAID:", NULL, FALSE},
1091 {"NAID:", NULL, FALSE},
1092 {"SCF:", NULL, FALSE},
1093 {"RMID:", NULL, FALSE},
1094 {"FMID:", NULL, FALSE},
1095 {NULL, NULL, FALSE}};
1098 gint mailval = 0, newsval = 0;
1100 gchar *smtpserver = NULL;
1101 GSList *to_list = NULL;
1102 GSList *newsgroup_list = NULL;
1103 gchar *savecopyfolder = NULL;
1104 gchar *replymessageid = NULL;
1105 gchar *fwdmessageid = NULL;
1106 gchar buf[BUFFSIZE];
1108 PrefsAccount *mailac = NULL, *newsac = NULL;
1111 g_return_val_if_fail(file != NULL, -1);
1113 if ((fp = fopen(file, "rb")) == NULL) {
1114 FILE_OP_ERROR(file, "fopen");
1118 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1120 gchar *p = buf + strlen(qentry[hnum].name);
1124 if (!from) from = g_strdup(p);
1127 if (!smtpserver) smtpserver = g_strdup(p);
1130 to_list = address_list_append(to_list, p);
1133 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1135 case Q_MAIL_ACCOUNT_ID:
1136 mailac = account_find_from_id(atoi(p));
1138 case Q_NEWS_ACCOUNT_ID:
1139 newsac = account_find_from_id(atoi(p));
1141 case Q_SAVE_COPY_FOLDER:
1142 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1144 case Q_REPLY_MESSAGE_ID:
1145 if (!replymessageid) replymessageid = g_strdup(p);
1147 case Q_FWD_MESSAGE_ID:
1148 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1152 filepos = ftell(fp);
1155 debug_print("Sending message by mail\n");
1157 g_warning("Queued message header is broken.\n");
1159 } else if (mailac && mailac->use_mail_command &&
1160 mailac->mail_command && (* mailac->mail_command)) {
1161 mailval = send_message_local(mailac->mail_command, fp);
1163 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1164 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1168 mailac = account_find_from_smtp_server(from, smtpserver);
1170 g_warning("Account not found. "
1171 "Using current account...\n");
1172 mailac = cur_account;
1177 mailval = send_message_smtp(mailac, to_list, fp);
1179 PrefsAccount tmp_ac;
1181 g_warning("Account not found.\n");
1183 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1184 tmp_ac.address = from;
1185 tmp_ac.smtp_server = smtpserver;
1186 tmp_ac.smtpport = SMTP_PORT;
1187 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1192 fseek(fp, filepos, SEEK_SET);
1193 if (newsgroup_list && (newsval == 0)) {
1198 /* write to temporary file */
1199 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1200 G_DIR_SEPARATOR, (gint)file);
1201 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1202 FILE_OP_ERROR(tmp, "fopen");
1204 alertpanel_error(_("Could not create temporary file for news sending."));
1206 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1207 FILE_OP_ERROR(tmp, "chmod");
1208 g_warning("can't change file mode\n");
1211 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1212 if (fputs(buf, tmpfp) == EOF) {
1213 FILE_OP_ERROR(tmp, "fputs");
1215 alertpanel_error(_("Error when writing temporary file for news sending."));
1221 debug_print("Sending message by news\n");
1223 folder = FOLDER(newsac->folder);
1225 newsval = news_post(folder, tmp);
1227 alertpanel_error(_("Error occurred while posting the message to %s ."),
1228 newsac->nntp_server);
1236 slist_free_strings(to_list);
1237 g_slist_free(to_list);
1238 slist_free_strings(newsgroup_list);
1239 g_slist_free(newsgroup_list);
1244 /* save message to outbox */
1245 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1248 debug_print("saving sent message...\n");
1250 outbox = folder_find_item_from_identifier(savecopyfolder);
1252 outbox = folder_get_default_outbox();
1254 procmsg_save_to_outbox(outbox, file, TRUE);
1257 if (replymessageid != NULL || fwdmessageid != NULL) {
1261 if (replymessageid != NULL)
1262 tokens = g_strsplit(replymessageid, "\x7f", 0);
1264 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1265 item = folder_find_item_from_identifier(tokens[0]);
1267 /* check if queued message has valid folder and message id */
1268 if (item != NULL && tokens[2] != NULL) {
1271 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1273 /* check if referring message exists and has a message id */
1274 if ((msginfo != NULL) &&
1275 (msginfo->msgid != NULL) &&
1276 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1277 procmsg_msginfo_free(msginfo);
1281 if (msginfo == NULL) {
1282 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1285 if (msginfo != NULL) {
1286 if (replymessageid != NULL) {
1287 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1288 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1291 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1292 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1294 procmsg_msginfo_free(msginfo);
1300 g_free(savecopyfolder);
1301 g_free(replymessageid);
1302 g_free(fwdmessageid);
1304 return (newsval != 0 ? newsval : mailval);
1307 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1309 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1312 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1316 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1321 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1322 item->unread_msgs++;
1323 if (procmsg_msg_has_marked_parent(msginfo))
1324 item->unreadmarked_msgs++;
1327 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1328 item->unread_msgs--;
1329 if (procmsg_msg_has_marked_parent(msginfo))
1330 item->unreadmarked_msgs--;
1334 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1335 procmsg_update_unread_children(msginfo, TRUE);
1338 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1339 procmsg_update_unread_children(msginfo, FALSE);
1343 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1346 MsgInfoUpdate msginfo_update;
1347 MsgPermFlags perm_flags_new, perm_flags_old;
1349 g_return_if_fail(msginfo != NULL);
1350 item = msginfo->folder;
1351 g_return_if_fail(item != NULL);
1353 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1355 /* Perm Flags handling */
1356 perm_flags_old = msginfo->flags.perm_flags;
1357 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1358 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1359 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1362 if (perm_flags_old != perm_flags_new) {
1363 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1365 update_folder_msg_counts(item, msginfo, perm_flags_old);
1367 msginfo_update.msginfo = msginfo;
1368 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1369 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1372 /* Tmp flags hanlding */
1373 msginfo->flags.tmp_flags |= tmp_flags;
1376 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1379 MsgInfoUpdate msginfo_update;
1380 MsgPermFlags perm_flags_new, perm_flags_old;
1382 g_return_if_fail(msginfo != NULL);
1383 item = msginfo->folder;
1384 g_return_if_fail(item != NULL);
1386 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1388 /* Perm Flags handling */
1389 perm_flags_old = msginfo->flags.perm_flags;
1390 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1392 if (perm_flags_old != perm_flags_new) {
1393 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1395 update_folder_msg_counts(item, msginfo, perm_flags_old);
1397 msginfo_update.msginfo = msginfo;
1398 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1399 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1402 /* Tmp flags hanlding */
1403 msginfo->flags.tmp_flags &= ~tmp_flags;
1407 *\brief check for flags (e.g. mark) in prior msgs of current thread
1409 *\param info Current message
1410 *\param perm_flags Flags to be checked
1411 *\param parentmsgs Hash of prior msgs to avoid loops
1413 *\return gboolean TRUE if perm_flags are found
1415 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1416 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1420 g_return_val_if_fail(info != NULL, FALSE);
1422 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1423 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1425 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1426 procmsg_msginfo_free(tmp);
1428 } else if (tmp != NULL) {
1431 if (g_hash_table_lookup(parentmsgs, info)) {
1432 debug_print("loop detected: %s%c%d\n",
1433 folder_item_get_path(info->folder),
1434 G_DIR_SEPARATOR, info->msgnum);
1437 g_hash_table_insert(parentmsgs, info, "1");
1438 result = procmsg_msg_has_flagged_parent_real(
1439 tmp, perm_flags, parentmsgs);
1441 procmsg_msginfo_free(tmp);
1451 *\brief Callback for cleaning up hash of parentmsgs
1453 gboolean parentmsgs_hash_remove(gpointer key,
1461 *\brief Set up list of parentmsgs
1462 * See procmsg_msg_has_flagged_parent_real()
1464 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1467 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1469 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1470 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1471 g_hash_table_destroy(parentmsgs);
1476 *\brief Check if msgs prior in thread are marked
1477 * See procmsg_msg_has_flagged_parent_real()
1479 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1481 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1485 GSList *procmsg_find_children_func(MsgInfo *info,
1486 GSList *children, GSList *all)
1490 g_return_val_if_fail(info!=NULL, children);
1491 if (info->msgid == NULL)
1494 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1495 MsgInfo *tmp = (MsgInfo *)cur->data;
1496 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1497 /* Check if message is already in the list */
1498 if ((children == NULL) ||
1499 (g_slist_index(children, tmp) == -1)) {
1500 children = g_slist_prepend(children,
1501 procmsg_msginfo_new_ref(tmp));
1502 children = procmsg_find_children_func(tmp,
1511 GSList *procmsg_find_children (MsgInfo *info)
1516 g_return_val_if_fail(info!=NULL, NULL);
1517 all = folder_item_get_msg_list(info->folder);
1518 children = procmsg_find_children_func(info, NULL, all);
1519 if (children != NULL) {
1520 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1521 /* this will not free the used pointers
1522 created with procmsg_msginfo_new_ref */
1523 procmsg_msginfo_free((MsgInfo *)cur->data);
1531 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1533 GSList *children = procmsg_find_children(info);
1535 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1536 MsgInfo *tmp = (MsgInfo *)cur->data;
1537 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1539 info->folder->unreadmarked_msgs++;
1541 info->folder->unreadmarked_msgs--;
1542 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1544 procmsg_msginfo_free(tmp);
1546 g_slist_free(children);
1550 * Set the destination folder for a copy or move operation
1552 * \param msginfo The message which's destination folder is changed
1553 * \param to_folder The destination folder for the operation
1555 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1557 if(msginfo->to_folder != NULL) {
1558 msginfo->to_folder->op_count--;
1559 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1561 msginfo->to_folder = to_folder;
1562 if(to_folder != NULL) {
1563 to_folder->op_count++;
1564 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1569 * Apply filtering actions to the msginfo
1571 * \param msginfo The MsgInfo describing the message that should be filtered
1572 * \return TRUE if the message was moved and MsgInfo is now invalid,
1575 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1577 MailFilteringData mail_filtering_data;
1579 mail_filtering_data.msginfo = msginfo;
1580 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1583 /* filter if enabled in prefs or move to inbox if not */
1584 if((global_processing != NULL) &&
1585 filter_message_by_msginfo(global_processing, msginfo))