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"
40 #include "alertpanel.h"
45 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
47 GHashTable *msg_table;
49 if (mlist == NULL) return NULL;
51 msg_table = g_hash_table_new(NULL, g_direct_equal);
52 procmsg_msg_hash_table_append(msg_table, mlist);
57 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
62 if (msg_table == NULL || mlist == NULL) return;
64 for (cur = mlist; cur != NULL; cur = cur->next) {
65 msginfo = (MsgInfo *)cur->data;
67 g_hash_table_insert(msg_table,
68 GUINT_TO_POINTER(msginfo->msgnum),
73 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
75 GHashTable *msg_table;
79 if (mlist == NULL) return NULL;
81 msg_table = g_hash_table_new(NULL, g_direct_equal);
83 for (cur = mlist; cur != NULL; cur = cur->next) {
84 msginfo = (MsgInfo *)cur->data;
85 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
91 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
97 for (cur = mlist; cur != NULL; cur = cur->next) {
98 msginfo = (MsgInfo *)cur->data;
99 if (msginfo && msginfo->msgnum > last)
100 last = msginfo->msgnum;
106 void procmsg_msg_list_free(GSList *mlist)
111 for (cur = mlist; cur != NULL; cur = cur->next) {
112 msginfo = (MsgInfo *)cur->data;
113 procmsg_msginfo_free(msginfo);
127 /* CLAWS subject threading:
129 in the first round it inserts subject lines in a
130 relation (subject <-> node)
132 the second round finishes the threads by attaching
133 matching subject lines to the one found in the
134 relation. will use the oldest node with the same
135 subject that is not more then thread_by_subject_max_age
136 days old (see subject_relation_lookup)
139 static void subject_relation_insert(GRelation *relation, GNode *node)
144 g_return_if_fail(relation != NULL);
145 g_return_if_fail(node != NULL);
146 msginfo = (MsgInfo *) node->data;
147 g_return_if_fail(msginfo != NULL);
149 subject = msginfo->subject;
152 subject += subject_get_prefix_length(subject);
154 g_relation_insert(relation, subject, node);
157 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
164 g_return_val_if_fail(relation != NULL, NULL);
166 subject = msginfo->subject;
169 prefix_length = subject_get_prefix_length(subject);
170 if (prefix_length <= 0)
172 subject += prefix_length;
174 tuples = g_relation_select(relation, subject, 0);
178 if (tuples->len > 0) {
180 GNode *relation_node;
181 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
184 /* check all nodes with the same subject to find the best parent */
185 for (i = 0; i < tuples->len; i++) {
186 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
187 relation_msginfo = (MsgInfo *) relation_node->data;
190 /* best node should be the oldest in the found nodes */
191 /* parent node must not be older then msginfo */
192 if ((relation_msginfo->date_t < msginfo->date_t) &&
193 ((best_msginfo == NULL) ||
194 (best_msginfo->date_t > relation_msginfo->date_t)))
197 /* parent node must not be more then thread_by_subject_max_age
198 days older then msginfo */
199 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
200 prefs_common.thread_by_subject_max_age * 3600 * 24)
203 /* can add new tests for all matching
204 nodes found by subject */
207 node = relation_node;
208 best_msginfo = relation_msginfo;
213 g_tuples_destroy(tuples);
217 /* return the reversed thread tree */
218 GNode *procmsg_get_thread_tree(GSList *mlist)
220 GNode *root, *parent, *node, *next, *last;
221 GNode *prev; /* CLAWS */
222 GHashTable *msgid_table;
223 GRelation *subject_relation;
226 const gchar *subject;
228 root = g_node_new(NULL);
229 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
230 subject_relation = g_relation_new(2);
231 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
233 for (; mlist != NULL; mlist = mlist->next) {
234 msginfo = (MsgInfo *)mlist->data;
237 if (msginfo->inreplyto) {
238 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
239 if (parent == NULL) {
243 node = g_node_insert_data_before
244 (parent, parent == root ? parent->children : NULL,
246 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
247 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
249 /* CLAWS: add subject to relation (without prefix) */
250 if (prefs_common.thread_by_subject) {
251 subject_relation_insert(subject_relation, node);
255 /* complete the unfinished threads. record the last encountered node. */
256 for (node = root->children; node != NULL; ) {
257 parent = prev = NULL;
259 msginfo = (MsgInfo *)node->data;
260 if (msginfo->inreplyto) {
261 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
262 /* node should not be the parent, and node should not
263 be an ancestor of parent (circular reference) */
264 if (parent && parent != node &&
265 !g_node_is_ancestor(node, parent)) {
266 /* since this node is moved away, the previous
267 * one is the last recorded one */
271 (parent, parent->children, node);
274 last = prev ? prev : node;
278 if (prefs_common.thread_by_subject) {
279 for (node = last; node && node != NULL;) {
281 msginfo = (MsgInfo *) node->data;
283 /* may not parentize if parent was delivered after childs */
284 if (subject != msginfo->subject)
285 parent = subject_relation_lookup(subject_relation, msginfo);
289 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
290 find the parent node */
291 if (parent != NULL) {
292 if (g_node_is_ancestor(node, parent))
300 g_node_append(parent, node);
307 g_relation_destroy(subject_relation);
308 g_hash_table_destroy(msgid_table);
313 void procmsg_move_messages(GSList *mlist)
315 GSList *cur, *movelist = NULL;
317 FolderItem *dest = NULL;
321 folder_item_update_freeze();
323 for (cur = mlist; cur != NULL; cur = cur->next) {
324 msginfo = (MsgInfo *)cur->data;
326 dest = msginfo->to_folder;
327 movelist = g_slist_append(movelist, msginfo);
328 } else if (dest == msginfo->to_folder) {
329 movelist = g_slist_append(movelist, msginfo);
331 folder_item_move_msgs(dest, movelist);
332 g_slist_free(movelist);
334 dest = msginfo->to_folder;
335 movelist = g_slist_append(movelist, msginfo);
337 procmsg_msginfo_set_to_folder(msginfo, NULL);
341 folder_item_move_msgs(dest, movelist);
342 g_slist_free(movelist);
345 folder_item_update_thaw();
348 void procmsg_copy_messages(GSList *mlist)
350 GSList *cur, *copylist = NULL;
352 FolderItem *dest = NULL;
356 folder_item_update_freeze();
358 for (cur = mlist; cur != NULL; cur = cur->next) {
359 msginfo = (MsgInfo *)cur->data;
361 dest = msginfo->to_folder;
362 copylist = g_slist_append(copylist, msginfo);
363 } else if (dest == msginfo->to_folder) {
364 copylist = g_slist_append(copylist, msginfo);
366 folder_item_copy_msgs(dest, copylist);
367 g_slist_free(copylist);
369 dest = msginfo->to_folder;
370 copylist = g_slist_append(copylist, msginfo);
372 procmsg_msginfo_set_to_folder(msginfo, NULL);
376 folder_item_copy_msgs(dest, copylist);
377 g_slist_free(copylist);
380 folder_item_update_thaw();
383 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
387 g_return_val_if_fail(msginfo != NULL, NULL);
389 if (msginfo->plaintext_file)
390 file = g_strdup(msginfo->plaintext_file);
392 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
398 gchar *procmsg_get_message_file(MsgInfo *msginfo)
400 gchar *filename = NULL;
402 g_return_val_if_fail(msginfo != NULL, NULL);
404 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
406 debug_print("can't fetch message %d\n", msginfo->msgnum);
411 GSList *procmsg_get_message_file_list(GSList *mlist)
413 GSList *file_list = NULL;
415 MsgFileInfo *fileinfo;
418 while (mlist != NULL) {
419 msginfo = (MsgInfo *)mlist->data;
420 file = procmsg_get_message_file(msginfo);
422 procmsg_message_file_list_free(file_list);
425 fileinfo = g_new(MsgFileInfo, 1);
426 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
427 fileinfo->file = file;
428 fileinfo->flags = g_new(MsgFlags, 1);
429 *fileinfo->flags = msginfo->flags;
430 file_list = g_slist_prepend(file_list, fileinfo);
434 file_list = g_slist_reverse(file_list);
439 void procmsg_message_file_list_free(MsgInfoList *file_list)
442 MsgFileInfo *fileinfo;
444 for (cur = file_list; cur != NULL; cur = cur->next) {
445 fileinfo = (MsgFileInfo *)cur->data;
446 procmsg_msginfo_free(fileinfo->msginfo);
447 g_free(fileinfo->file);
448 g_free(fileinfo->flags);
452 g_slist_free(file_list);
455 FILE *procmsg_open_message(MsgInfo *msginfo)
460 g_return_val_if_fail(msginfo != NULL, NULL);
462 file = procmsg_get_message_file_path(msginfo);
463 g_return_val_if_fail(file != NULL, NULL);
465 if (!is_file_exist(file)) {
467 file = procmsg_get_message_file(msginfo);
472 if ((fp = fopen(file, "rb")) == NULL) {
473 FILE_OP_ERROR(file, "fopen");
480 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
483 while (fgets(buf, sizeof(buf), fp) != NULL)
484 if (buf[0] == '\r' || buf[0] == '\n') break;
490 gboolean procmsg_msg_exist(MsgInfo *msginfo)
495 if (!msginfo) return FALSE;
497 path = folder_item_get_path(msginfo->folder);
499 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
505 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
506 PrefsFilterType type)
508 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
509 {"X-ML-Name:", NULL, TRUE},
510 {"X-List:", NULL, TRUE},
511 {"X-Mailing-list:", NULL, TRUE},
512 {"List-Id:", NULL, TRUE},
513 {"X-Sequence:", NULL, TRUE},
514 {NULL, NULL, FALSE}};
520 H_X_MAILING_LIST = 3,
527 g_return_if_fail(msginfo != NULL);
528 g_return_if_fail(header != NULL);
529 g_return_if_fail(key != NULL);
538 if ((fp = procmsg_open_message(msginfo)) == NULL)
540 procheader_get_header_fields(fp, hentry);
543 #define SET_FILTER_KEY(hstr, idx) \
545 *header = g_strdup(hstr); \
546 *key = hentry[idx].body; \
547 hentry[idx].body = NULL; \
550 if (hentry[H_X_BEENTHERE].body != NULL) {
551 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
552 } else if (hentry[H_X_ML_NAME].body != NULL) {
553 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
554 } else if (hentry[H_X_LIST].body != NULL) {
555 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
556 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
557 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
558 } else if (hentry[H_LIST_ID].body != NULL) {
559 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
560 extract_list_id_str(*key);
561 } else if (hentry[H_X_SEQUENCE].body != NULL) {
564 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
567 while (*p != '\0' && !isspace(*p)) p++;
568 while (isspace(*p)) p++;
575 } else if (msginfo->subject) {
576 *header = g_strdup("subject");
577 *key = g_strdup(msginfo->subject);
580 #undef SET_FILTER_KEY
582 g_free(hentry[H_X_BEENTHERE].body);
583 hentry[H_X_BEENTHERE].body = NULL;
584 g_free(hentry[H_X_ML_NAME].body);
585 hentry[H_X_ML_NAME].body = NULL;
586 g_free(hentry[H_X_LIST].body);
587 hentry[H_X_LIST].body = NULL;
588 g_free(hentry[H_X_MAILING_LIST].body);
589 hentry[H_X_MAILING_LIST].body = NULL;
590 g_free(hentry[H_LIST_ID].body);
591 hentry[H_LIST_ID].body = NULL;
595 *header = g_strdup("from");
596 *key = g_strdup(msginfo->from);
599 *header = g_strdup("to");
600 *key = g_strdup(msginfo->to);
602 case FILTER_BY_SUBJECT:
603 *header = g_strdup("subject");
604 *key = g_strdup(msginfo->subject);
611 void procmsg_empty_trash(void)
616 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
617 trash = FOLDER(cur->data)->trash;
618 if (trash && trash->total_msgs > 0)
619 folder_item_remove_all_msg(trash);
624 *\brief Send messages in queue
626 *\param queue Queue folder to process
627 *\param save_msgs Unused
629 *\return Number of messages sent, negative if an error occurred
630 * positive if no error occurred
632 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
634 gint ret = 1, count = 0;
638 queue = folder_get_default_queue();
639 g_return_val_if_fail(queue != NULL, -1);
641 folder_item_scan(queue);
642 list = folder_item_get_msg_list(queue);
644 for (elem = list; elem != NULL; elem = elem->next) {
648 msginfo = (MsgInfo *)(elem->data);
649 if (!MSG_IS_LOCKED(msginfo->flags)) {
650 file = folder_item_fetch_msg(queue, msginfo->msgnum);
652 if (procmsg_send_message_queue(file) < 0) {
653 g_warning("Sending queued message %d failed.\n",
658 * We save in procmsg_send_message_queue because
659 * we need the destination folder from the queue
663 procmsg_save_to_outbox
664 (queue->folder->outbox,
668 folder_item_remove_msg(queue, msginfo->msgnum);
673 /* FIXME: supposedly if only one message is locked, and queue
674 * is being flushed, the following free says something like
675 * "freeing msg ## in folder (nil)". */
676 procmsg_msginfo_free(msginfo);
682 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
687 if ((fp = fopen(in, "rb")) == NULL) {
688 FILE_OP_ERROR(in, "fopen");
691 if ((outfp = fopen(out, "wb")) == NULL) {
692 FILE_OP_ERROR(out, "fopen");
696 while (fgets(buf, sizeof(buf), fp) != NULL)
697 if (buf[0] == '\r' || buf[0] == '\n') break;
698 while (fgets(buf, sizeof(buf), fp) != NULL)
705 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
709 MsgInfo *msginfo, *tmp_msginfo;
710 MsgFlags flag = {0, 0};
712 debug_print("saving sent message...\n");
715 outbox = folder_get_default_outbox();
716 g_return_val_if_fail(outbox != NULL, -1);
718 /* remove queueing headers */
720 gchar tmp[MAXPATHLEN + 1];
722 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
723 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
725 if (procmsg_remove_special_headers(file, tmp) !=0)
728 folder_item_scan(outbox);
729 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
730 g_warning("can't save message\n");
735 folder_item_scan(outbox);
736 if ((num = folder_item_add_msg
737 (outbox, file, &flag, FALSE)) < 0) {
738 g_warning("can't save message\n");
743 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
744 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
745 if (msginfo != NULL) {
746 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
747 procmsg_msginfo_free(msginfo); /* refcnt-- */
748 /* tmp_msginfo == msginfo */
749 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
750 msginfo->returnreceiptto)) {
751 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
752 procmsg_msginfo_free(msginfo); /* refcnt-- */
759 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
761 static const gchar *def_cmd = "lpr %s";
768 g_return_if_fail(msginfo);
770 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
771 g_warning("Can't get text part\n");
775 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
776 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
778 if ((prfp = fopen(prtmp, "wb")) == NULL) {
779 FILE_OP_ERROR(prtmp, "fopen");
785 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
786 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
787 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
788 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
789 if (msginfo->newsgroups)
790 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
791 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
794 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
800 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
802 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
805 g_warning("Print command line is invalid: `%s'\n",
807 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
813 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
817 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
824 MsgInfo *procmsg_msginfo_new(void)
828 newmsginfo = g_new0(MsgInfo, 1);
829 newmsginfo->refcnt = 1;
834 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
838 if (msginfo == NULL) return NULL;
840 newmsginfo = g_new0(MsgInfo, 1);
842 newmsginfo->refcnt = 1;
844 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
845 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
846 g_strdup(msginfo->mmb) : NULL
870 MEMBDUP(dispositionnotificationto);
871 MEMBDUP(returnreceiptto);
875 MEMBCOPY(threadscore);
880 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
882 MsgInfo *full_msginfo;
885 if (msginfo == NULL) return NULL;
887 file = procmsg_get_message_file(msginfo);
889 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
893 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
895 if (!full_msginfo) return NULL;
897 /* CLAWS: make sure we add the missing members; see:
898 * procheader.c::procheader_get_headernames() */
900 msginfo->xface = g_strdup(full_msginfo->xface);
901 if (!msginfo->dispositionnotificationto)
902 msginfo->dispositionnotificationto =
903 g_strdup(full_msginfo->dispositionnotificationto);
904 if (!msginfo->returnreceiptto)
905 msginfo->returnreceiptto = g_strdup
906 (full_msginfo->returnreceiptto);
907 procmsg_msginfo_free(full_msginfo);
909 return procmsg_msginfo_new_ref(msginfo);
912 void procmsg_msginfo_free(MsgInfo *msginfo)
914 if (msginfo == NULL) return;
917 if (msginfo->refcnt > 0)
920 if (msginfo->to_folder) {
921 msginfo->to_folder->op_count--;
922 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
925 g_free(msginfo->fromspace);
926 g_free(msginfo->references);
927 g_free(msginfo->returnreceiptto);
928 g_free(msginfo->dispositionnotificationto);
929 g_free(msginfo->xface);
931 g_free(msginfo->fromname);
933 g_free(msginfo->date);
934 g_free(msginfo->from);
937 g_free(msginfo->newsgroups);
938 g_free(msginfo->subject);
939 g_free(msginfo->msgid);
940 g_free(msginfo->inreplyto);
941 g_free(msginfo->xref);
946 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
950 memusage += sizeof(MsgInfo);
951 if (msginfo->fromname)
952 memusage += strlen(msginfo->fromname);
954 memusage += strlen(msginfo->date);
956 memusage += strlen(msginfo->from);
958 memusage += strlen(msginfo->to);
960 memusage += strlen(msginfo->cc);
961 if (msginfo->newsgroups)
962 memusage += strlen(msginfo->newsgroups);
963 if (msginfo->subject)
964 memusage += strlen(msginfo->subject);
966 memusage += strlen(msginfo->msgid);
967 if (msginfo->inreplyto)
968 memusage += strlen(msginfo->inreplyto);
970 memusage += strlen(msginfo->xface);
971 if (msginfo->dispositionnotificationto)
972 memusage += strlen(msginfo->dispositionnotificationto);
973 if (msginfo->returnreceiptto)
974 memusage += strlen(msginfo->returnreceiptto);
975 if (msginfo->references)
976 memusage += strlen(msginfo->references);
977 if (msginfo->fromspace)
978 memusage += strlen(msginfo->fromspace);
983 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
985 const MsgInfo *msginfo1 = a;
986 const MsgInfo *msginfo2 = b;
993 return msginfo1->msgnum - msginfo2->msgnum;
1002 Q_MAIL_ACCOUNT_ID = 4,
1003 Q_NEWS_ACCOUNT_ID = 5,
1004 Q_SAVE_COPY_FOLDER = 6,
1005 Q_REPLY_MESSAGE_ID = 7,
1006 Q_FWD_MESSAGE_ID = 8
1009 gint procmsg_send_message_queue(const gchar *file)
1011 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1012 {"SSV:", NULL, FALSE},
1013 {"R:", NULL, FALSE},
1014 {"NG:", NULL, FALSE},
1015 {"MAID:", NULL, FALSE},
1016 {"NAID:", NULL, FALSE},
1017 {"SCF:", NULL, FALSE},
1018 {"RMID:", NULL, FALSE},
1019 {"FMID:", NULL, FALSE},
1020 {NULL, NULL, FALSE}};
1023 gint mailval = 0, newsval = 0;
1025 gchar *smtpserver = NULL;
1026 GSList *to_list = NULL;
1027 GSList *newsgroup_list = NULL;
1028 gchar *savecopyfolder = NULL;
1029 gchar *replymessageid = NULL;
1030 gchar *fwdmessageid = NULL;
1031 gchar buf[BUFFSIZE];
1033 PrefsAccount *mailac = NULL, *newsac = NULL;
1036 g_return_val_if_fail(file != NULL, -1);
1038 if ((fp = fopen(file, "rb")) == NULL) {
1039 FILE_OP_ERROR(file, "fopen");
1043 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1045 gchar *p = buf + strlen(qentry[hnum].name);
1049 if (!from) from = g_strdup(p);
1052 if (!smtpserver) smtpserver = g_strdup(p);
1055 to_list = address_list_append(to_list, p);
1058 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1060 case Q_MAIL_ACCOUNT_ID:
1061 mailac = account_find_from_id(atoi(p));
1063 case Q_NEWS_ACCOUNT_ID:
1064 newsac = account_find_from_id(atoi(p));
1066 case Q_SAVE_COPY_FOLDER:
1067 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1069 case Q_REPLY_MESSAGE_ID:
1070 if (!replymessageid) replymessageid = g_strdup(p);
1072 case Q_FWD_MESSAGE_ID:
1073 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1077 filepos = ftell(fp);
1080 debug_print("Sending message by mail\n");
1082 g_warning("Queued message header is broken.\n");
1084 } else if (mailac && mailac->use_mail_command &&
1085 mailac->mail_command && (* mailac->mail_command)) {
1086 mailval = send_message_local(mailac->mail_command, fp);
1090 mailac = account_find_from_smtp_server(from, smtpserver);
1092 g_warning("Account not found. "
1093 "Using current account...\n");
1094 mailac = cur_account;
1099 mailval = send_message_smtp(mailac, to_list, fp);
1101 PrefsAccount tmp_ac;
1103 g_warning("Account not found.\n");
1105 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1106 tmp_ac.address = from;
1107 tmp_ac.smtp_server = smtpserver;
1108 tmp_ac.smtpport = SMTP_PORT;
1109 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1114 fseek(fp, filepos, SEEK_SET);
1115 if (newsgroup_list && (newsval == 0)) {
1120 /* write to temporary file */
1121 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1122 G_DIR_SEPARATOR, (gint)file);
1123 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1124 FILE_OP_ERROR(tmp, "fopen");
1126 alertpanel_error(_("Could not create temporary file for news sending."));
1128 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1129 FILE_OP_ERROR(tmp, "chmod");
1130 g_warning("can't change file mode\n");
1133 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1134 if (fputs(buf, tmpfp) == EOF) {
1135 FILE_OP_ERROR(tmp, "fputs");
1137 alertpanel_error(_("Error when writing temporary file for news sending."));
1143 debug_print("Sending message by news\n");
1145 folder = FOLDER(newsac->folder);
1147 newsval = news_post(folder, tmp);
1149 alertpanel_error(_("Error occurred while posting the message to %s ."),
1150 newsac->nntp_server);
1158 slist_free_strings(to_list);
1159 g_slist_free(to_list);
1160 slist_free_strings(newsgroup_list);
1161 g_slist_free(newsgroup_list);
1166 /* save message to outbox */
1167 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1170 debug_print("saving sent message...\n");
1172 outbox = folder_find_item_from_identifier(savecopyfolder);
1174 outbox = folder_get_default_outbox();
1176 procmsg_save_to_outbox(outbox, file, TRUE);
1179 if (replymessageid != NULL || fwdmessageid != NULL) {
1183 if (replymessageid != NULL)
1184 tokens = g_strsplit(replymessageid, "\x7f", 0);
1186 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1187 item = folder_find_item_from_identifier(tokens[0]);
1189 /* check if queued message has valid folder and message id */
1190 if (item != NULL && tokens[2] != NULL) {
1193 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1195 /* check if referring message exists and has a message id */
1196 if ((msginfo != NULL) &&
1197 (msginfo->msgid != NULL) &&
1198 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1199 procmsg_msginfo_free(msginfo);
1203 if (msginfo == NULL) {
1204 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1207 if (msginfo != NULL) {
1208 if (replymessageid != NULL) {
1209 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1210 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1213 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1214 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1216 procmsg_msginfo_free(msginfo);
1222 g_free(savecopyfolder);
1223 g_free(replymessageid);
1224 g_free(fwdmessageid);
1226 return (newsval != 0 ? newsval : mailval);
1229 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1231 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1234 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1238 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1243 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1244 item->unread_msgs++;
1245 if (procmsg_msg_has_marked_parent(msginfo))
1246 item->unreadmarked_msgs++;
1249 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1250 item->unread_msgs--;
1251 if (procmsg_msg_has_marked_parent(msginfo))
1252 item->unreadmarked_msgs--;
1256 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1257 procmsg_update_unread_children(msginfo, TRUE);
1260 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1261 procmsg_update_unread_children(msginfo, FALSE);
1265 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1268 MsgInfoUpdate msginfo_update;
1269 MsgPermFlags perm_flags_new, perm_flags_old;
1271 g_return_if_fail(msginfo != NULL);
1272 item = msginfo->folder;
1273 g_return_if_fail(item != NULL);
1275 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1277 /* Perm Flags handling */
1278 perm_flags_old = msginfo->flags.perm_flags;
1279 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1280 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1281 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1284 if (perm_flags_old != perm_flags_new) {
1285 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1287 update_folder_msg_counts(item, msginfo, perm_flags_old);
1289 msginfo_update.msginfo = msginfo;
1290 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1291 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1292 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1295 /* Tmp flags hanlding */
1296 msginfo->flags.tmp_flags |= tmp_flags;
1299 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1302 MsgInfoUpdate msginfo_update;
1303 MsgPermFlags perm_flags_new, perm_flags_old;
1305 g_return_if_fail(msginfo != NULL);
1306 item = msginfo->folder;
1307 g_return_if_fail(item != NULL);
1309 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1311 /* Perm Flags handling */
1312 perm_flags_old = msginfo->flags.perm_flags;
1313 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1315 if (perm_flags_old != perm_flags_new) {
1316 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1318 update_folder_msg_counts(item, msginfo, perm_flags_old);
1320 msginfo_update.msginfo = msginfo;
1321 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1322 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1323 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1326 /* Tmp flags hanlding */
1327 msginfo->flags.tmp_flags &= ~tmp_flags;
1331 *\brief check for flags (e.g. mark) in prior msgs of current thread
1333 *\param info Current message
1334 *\param perm_flags Flags to be checked
1335 *\param parentmsgs Hash of prior msgs to avoid loops
1337 *\return gboolean TRUE if perm_flags are found
1339 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1340 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1344 g_return_val_if_fail(info != NULL, FALSE);
1346 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1347 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1349 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1350 procmsg_msginfo_free(tmp);
1352 } else if (tmp != NULL) {
1355 if (g_hash_table_lookup(parentmsgs, info)) {
1356 debug_print("loop detected: %s%c%d\n",
1357 folder_item_get_path(info->folder),
1358 G_DIR_SEPARATOR, info->msgnum);
1361 g_hash_table_insert(parentmsgs, info, "1");
1362 result = procmsg_msg_has_flagged_parent_real(
1363 tmp, perm_flags, parentmsgs);
1365 procmsg_msginfo_free(tmp);
1375 *\brief Callback for cleaning up hash of parentmsgs
1377 gboolean parentmsgs_hash_remove(gpointer key,
1385 *\brief Set up list of parentmsgs
1386 * See procmsg_msg_has_flagged_parent_real()
1388 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1391 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1393 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1394 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1395 g_hash_table_destroy(parentmsgs);
1400 *\brief Check if msgs prior in thread are marked
1401 * See procmsg_msg_has_flagged_parent_real()
1403 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1405 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1409 GSList *procmsg_find_children_func(MsgInfo *info,
1410 GSList *children, GSList *all)
1414 g_return_val_if_fail(info!=NULL, children);
1415 if (info->msgid == NULL)
1418 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1419 MsgInfo *tmp = (MsgInfo *)cur->data;
1420 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1421 /* Check if message is already in the list */
1422 if ((children == NULL) ||
1423 (g_slist_index(children, tmp) == -1)) {
1424 children = g_slist_prepend(children,
1425 procmsg_msginfo_new_ref(tmp));
1426 children = procmsg_find_children_func(tmp,
1435 GSList *procmsg_find_children (MsgInfo *info)
1440 g_return_val_if_fail(info!=NULL, NULL);
1441 all = folder_item_get_msg_list(info->folder);
1442 children = procmsg_find_children_func(info, NULL, all);
1443 if (children != NULL) {
1444 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1445 /* this will not free the used pointers
1446 created with procmsg_msginfo_new_ref */
1447 procmsg_msginfo_free((MsgInfo *)cur->data);
1455 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1457 GSList *children = procmsg_find_children(info);
1459 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1460 MsgInfo *tmp = (MsgInfo *)cur->data;
1461 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1463 info->folder->unreadmarked_msgs++;
1465 info->folder->unreadmarked_msgs--;
1466 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1468 procmsg_msginfo_free(tmp);
1470 g_slist_free(children);
1474 * Set the destination folder for a copy or move operation
1476 * \param msginfo The message which's destination folder is changed
1477 * \param to_folder The destination folder for the operation
1479 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1481 if(msginfo->to_folder != NULL) {
1482 msginfo->to_folder->op_count--;
1483 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1485 msginfo->to_folder = to_folder;
1486 if(to_folder != NULL) {
1487 to_folder->op_count++;
1488 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1493 * Apply filtering actions to the msginfo
1495 * \param msginfo The MsgInfo describing the message that should be filtered
1496 * \return TRUE if the message was moved and MsgInfo is now invalid,
1499 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1501 MailFilteringData mail_filtering_data;
1503 mail_filtering_data.msginfo = msginfo;
1504 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1507 /* filter if enabled in prefs or move to inbox if not */
1508 if((filtering_rules != NULL) &&
1509 filter_message_by_msginfo(filtering_rules, msginfo))