2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2004 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"
44 #include "partial_download.h"
46 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
48 GHashTable *msg_table;
50 if (mlist == NULL) return NULL;
52 msg_table = g_hash_table_new(NULL, g_direct_equal);
53 procmsg_msg_hash_table_append(msg_table, mlist);
58 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
63 if (msg_table == NULL || mlist == NULL) return;
65 for (cur = mlist; cur != NULL; cur = cur->next) {
66 msginfo = (MsgInfo *)cur->data;
68 g_hash_table_insert(msg_table,
69 GUINT_TO_POINTER(msginfo->msgnum),
74 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
76 GHashTable *msg_table;
80 if (mlist == NULL) return NULL;
82 msg_table = g_hash_table_new(NULL, g_direct_equal);
84 for (cur = mlist; cur != NULL; cur = cur->next) {
85 msginfo = (MsgInfo *)cur->data;
86 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
92 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
98 for (cur = mlist; cur != NULL; cur = cur->next) {
99 msginfo = (MsgInfo *)cur->data;
100 if (msginfo && msginfo->msgnum > last)
101 last = msginfo->msgnum;
107 void procmsg_msg_list_free(GSList *mlist)
112 for (cur = mlist; cur != NULL; cur = cur->next) {
113 msginfo = (MsgInfo *)cur->data;
114 procmsg_msginfo_free(msginfo);
128 /* CLAWS subject threading:
130 in the first round it inserts subject lines in a
131 relation (subject <-> node)
133 the second round finishes the threads by attaching
134 matching subject lines to the one found in the
135 relation. will use the oldest node with the same
136 subject that is not more then thread_by_subject_max_age
137 days old (see subject_relation_lookup)
140 static void subject_relation_insert(GRelation *relation, GNode *node)
145 g_return_if_fail(relation != NULL);
146 g_return_if_fail(node != NULL);
147 msginfo = (MsgInfo *) node->data;
148 g_return_if_fail(msginfo != NULL);
150 subject = msginfo->subject;
153 subject += subject_get_prefix_length(subject);
155 g_relation_insert(relation, subject, node);
158 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
165 g_return_val_if_fail(relation != NULL, NULL);
167 subject = msginfo->subject;
170 prefix_length = subject_get_prefix_length(subject);
171 if (prefix_length <= 0)
173 subject += prefix_length;
175 tuples = g_relation_select(relation, subject, 0);
179 if (tuples->len > 0) {
181 GNode *relation_node;
182 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
185 /* check all nodes with the same subject to find the best parent */
186 for (i = 0; i < tuples->len; i++) {
187 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
188 relation_msginfo = (MsgInfo *) relation_node->data;
191 /* best node should be the oldest in the found nodes */
192 /* parent node must not be older then msginfo */
193 if ((relation_msginfo->date_t < msginfo->date_t) &&
194 ((best_msginfo == NULL) ||
195 (best_msginfo->date_t > relation_msginfo->date_t)))
198 /* parent node must not be more then thread_by_subject_max_age
199 days older then msginfo */
200 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
201 prefs_common.thread_by_subject_max_age * 3600 * 24)
204 /* can add new tests for all matching
205 nodes found by subject */
208 node = relation_node;
209 best_msginfo = relation_msginfo;
214 g_tuples_destroy(tuples);
218 /* return the reversed thread tree */
219 GNode *procmsg_get_thread_tree(GSList *mlist)
221 GNode *root, *parent, *node, *next;
222 GHashTable *msgid_table;
223 GRelation *subject_relation;
227 root = g_node_new(NULL);
228 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
229 subject_relation = g_relation_new(2);
230 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
232 for (; mlist != NULL; mlist = mlist->next) {
233 msginfo = (MsgInfo *)mlist->data;
236 if (msginfo->inreplyto) {
237 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
238 if (parent == NULL) {
242 node = g_node_insert_data_before
243 (parent, parent == root ? parent->children : NULL,
245 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
246 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
248 /* CLAWS: add subject to relation (without prefix) */
249 if (prefs_common.thread_by_subject) {
250 subject_relation_insert(subject_relation, node);
254 /* complete the unfinished threads */
255 for (node = root->children; node != NULL; ) {
258 msginfo = (MsgInfo *)node->data;
259 if (msginfo->inreplyto) {
260 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
261 /* node should not be the parent, and node should not
262 be an ancestor of parent (circular reference) */
263 if (parent && parent != node &&
264 !g_node_is_ancestor(node, parent)) {
267 (parent, parent->children, node);
273 if (prefs_common.thread_by_subject) {
274 for (node = root->children; node && node != NULL;) {
276 msginfo = (MsgInfo *) node->data;
278 parent = subject_relation_lookup(subject_relation, msginfo);
280 /* the node may already be threaded by IN-REPLY-TO, so go up
282 find the parent node */
283 if (parent != NULL) {
284 if (g_node_is_ancestor(node, parent))
292 g_node_append(parent, node);
299 g_relation_destroy(subject_relation);
300 g_hash_table_destroy(msgid_table);
305 void procmsg_move_messages(GSList *mlist)
307 GSList *cur, *movelist = NULL;
309 FolderItem *dest = NULL;
313 folder_item_update_freeze();
315 for (cur = mlist; cur != NULL; cur = cur->next) {
316 msginfo = (MsgInfo *)cur->data;
318 dest = msginfo->to_folder;
319 movelist = g_slist_append(movelist, msginfo);
320 } else if (dest == msginfo->to_folder) {
321 movelist = g_slist_append(movelist, msginfo);
323 folder_item_move_msgs(dest, movelist);
324 g_slist_free(movelist);
326 dest = msginfo->to_folder;
327 movelist = g_slist_append(movelist, msginfo);
329 procmsg_msginfo_set_to_folder(msginfo, NULL);
333 folder_item_move_msgs(dest, movelist);
334 g_slist_free(movelist);
337 folder_item_update_thaw();
340 void procmsg_copy_messages(GSList *mlist)
342 GSList *cur, *copylist = NULL;
344 FolderItem *dest = NULL;
348 folder_item_update_freeze();
350 for (cur = mlist; cur != NULL; cur = cur->next) {
351 msginfo = (MsgInfo *)cur->data;
353 dest = msginfo->to_folder;
354 copylist = g_slist_append(copylist, msginfo);
355 } else if (dest == msginfo->to_folder) {
356 copylist = g_slist_append(copylist, msginfo);
358 folder_item_copy_msgs(dest, copylist);
359 g_slist_free(copylist);
361 dest = msginfo->to_folder;
362 copylist = g_slist_append(copylist, msginfo);
364 procmsg_msginfo_set_to_folder(msginfo, NULL);
368 folder_item_copy_msgs(dest, copylist);
369 g_slist_free(copylist);
372 folder_item_update_thaw();
375 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
379 g_return_val_if_fail(msginfo != NULL, NULL);
381 if (msginfo->plaintext_file)
382 file = g_strdup(msginfo->plaintext_file);
384 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
390 gchar *procmsg_get_message_file(MsgInfo *msginfo)
392 gchar *filename = NULL;
394 g_return_val_if_fail(msginfo != NULL, NULL);
396 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
398 debug_print("can't fetch message %d\n", msginfo->msgnum);
403 GSList *procmsg_get_message_file_list(GSList *mlist)
405 GSList *file_list = NULL;
407 MsgFileInfo *fileinfo;
410 while (mlist != NULL) {
411 msginfo = (MsgInfo *)mlist->data;
412 file = procmsg_get_message_file(msginfo);
414 procmsg_message_file_list_free(file_list);
417 fileinfo = g_new(MsgFileInfo, 1);
418 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
419 fileinfo->file = file;
420 fileinfo->flags = g_new(MsgFlags, 1);
421 *fileinfo->flags = msginfo->flags;
422 file_list = g_slist_prepend(file_list, fileinfo);
426 file_list = g_slist_reverse(file_list);
431 void procmsg_message_file_list_free(MsgInfoList *file_list)
434 MsgFileInfo *fileinfo;
436 for (cur = file_list; cur != NULL; cur = cur->next) {
437 fileinfo = (MsgFileInfo *)cur->data;
438 procmsg_msginfo_free(fileinfo->msginfo);
439 g_free(fileinfo->file);
440 g_free(fileinfo->flags);
444 g_slist_free(file_list);
447 FILE *procmsg_open_message(MsgInfo *msginfo)
452 g_return_val_if_fail(msginfo != NULL, NULL);
454 file = procmsg_get_message_file_path(msginfo);
455 g_return_val_if_fail(file != NULL, NULL);
457 if (!is_file_exist(file)) {
459 file = procmsg_get_message_file(msginfo);
464 if ((fp = fopen(file, "rb")) == NULL) {
465 FILE_OP_ERROR(file, "fopen");
472 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
475 while (fgets(buf, sizeof(buf), fp) != NULL)
476 if (buf[0] == '\r' || buf[0] == '\n') break;
482 gboolean procmsg_msg_exist(MsgInfo *msginfo)
487 if (!msginfo) return FALSE;
489 path = folder_item_get_path(msginfo->folder);
491 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
497 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
498 PrefsFilterType type)
500 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
501 {"X-ML-Name:", NULL, TRUE},
502 {"X-List:", NULL, TRUE},
503 {"X-Mailing-list:", NULL, TRUE},
504 {"List-Id:", NULL, TRUE},
505 {"X-Sequence:", NULL, TRUE},
506 {NULL, NULL, FALSE}};
512 H_X_MAILING_LIST = 3,
519 g_return_if_fail(msginfo != NULL);
520 g_return_if_fail(header != NULL);
521 g_return_if_fail(key != NULL);
530 if ((fp = procmsg_open_message(msginfo)) == NULL)
532 procheader_get_header_fields(fp, hentry);
535 #define SET_FILTER_KEY(hstr, idx) \
537 *header = g_strdup(hstr); \
538 *key = hentry[idx].body; \
539 hentry[idx].body = NULL; \
542 if (hentry[H_X_BEENTHERE].body != NULL) {
543 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
544 } else if (hentry[H_X_ML_NAME].body != NULL) {
545 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
546 } else if (hentry[H_X_LIST].body != NULL) {
547 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
548 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
549 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
550 } else if (hentry[H_LIST_ID].body != NULL) {
551 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
552 extract_list_id_str(*key);
553 } else if (hentry[H_X_SEQUENCE].body != NULL) {
556 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
559 while (*p != '\0' && !isspace(*p)) p++;
560 while (isspace(*p)) p++;
567 } else if (msginfo->subject) {
568 *header = g_strdup("subject");
569 *key = g_strdup(msginfo->subject);
572 #undef SET_FILTER_KEY
574 g_free(hentry[H_X_BEENTHERE].body);
575 hentry[H_X_BEENTHERE].body = NULL;
576 g_free(hentry[H_X_ML_NAME].body);
577 hentry[H_X_ML_NAME].body = NULL;
578 g_free(hentry[H_X_LIST].body);
579 hentry[H_X_LIST].body = NULL;
580 g_free(hentry[H_X_MAILING_LIST].body);
581 hentry[H_X_MAILING_LIST].body = NULL;
582 g_free(hentry[H_LIST_ID].body);
583 hentry[H_LIST_ID].body = NULL;
587 *header = g_strdup("from");
588 *key = g_strdup(msginfo->from);
591 *header = g_strdup("to");
592 *key = g_strdup(msginfo->to);
594 case FILTER_BY_SUBJECT:
595 *header = g_strdup("subject");
596 *key = g_strdup(msginfo->subject);
603 void procmsg_empty_trash(FolderItem *trash)
605 if (trash && trash->total_msgs > 0) {
606 GSList *mlist = folder_item_get_msg_list(trash);
608 for (cur = mlist ; cur != NULL ; cur = cur->next) {
609 MsgInfo * msginfo = (MsgInfo *) cur->data;
610 partial_mark_for_delete(msginfo);
611 procmsg_msginfo_free(msginfo);
614 folder_item_remove_all_msg(trash);
618 void procmsg_empty_all_trash(void)
623 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
624 trash = FOLDER(cur->data)->trash;
625 procmsg_empty_trash(trash);
630 *\brief Send messages in queue
632 *\param queue Queue folder to process
633 *\param save_msgs Unused
635 *\return Number of messages sent, negative if an error occurred
636 * positive if no error occurred
638 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
640 gint sent = 0, err = 0;
644 queue = folder_get_default_queue();
645 g_return_val_if_fail(queue != NULL, -1);
647 folder_item_scan(queue);
648 list = folder_item_get_msg_list(queue);
650 for (elem = list; elem != NULL; elem = elem->next) {
654 msginfo = (MsgInfo *)(elem->data);
655 if (!MSG_IS_LOCKED(msginfo->flags)) {
656 file = folder_item_fetch_msg(queue, msginfo->msgnum);
658 if (procmsg_send_message_queue(file) < 0) {
659 g_warning("Sending queued message %d failed.\n",
664 * We save in procmsg_send_message_queue because
665 * we need the destination folder from the queue
669 procmsg_save_to_outbox
670 (queue->folder->outbox,
674 folder_item_remove_msg(queue, msginfo->msgnum);
679 /* FIXME: supposedly if only one message is locked, and queue
680 * is being flushed, the following free says something like
681 * "freeing msg ## in folder (nil)". */
682 procmsg_msginfo_free(msginfo);
685 return (err != 0 ? -err : sent);
688 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
693 if ((fp = fopen(in, "rb")) == NULL) {
694 FILE_OP_ERROR(in, "fopen");
697 if ((outfp = fopen(out, "wb")) == NULL) {
698 FILE_OP_ERROR(out, "fopen");
702 while (fgets(buf, sizeof(buf), fp) != NULL)
703 if (buf[0] == '\r' || buf[0] == '\n') break;
704 while (fgets(buf, sizeof(buf), fp) != NULL)
711 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
715 MsgInfo *msginfo, *tmp_msginfo;
716 MsgFlags flag = {0, 0};
718 debug_print("saving sent message...\n");
721 outbox = folder_get_default_outbox();
722 g_return_val_if_fail(outbox != NULL, -1);
724 /* remove queueing headers */
726 gchar tmp[MAXPATHLEN + 1];
728 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
729 get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
731 if (procmsg_remove_special_headers(file, tmp) !=0)
734 folder_item_scan(outbox);
735 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
736 g_warning("can't save message\n");
741 folder_item_scan(outbox);
742 if ((num = folder_item_add_msg
743 (outbox, file, &flag, FALSE)) < 0) {
744 g_warning("can't save message\n");
749 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
750 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
751 if (msginfo != NULL) {
752 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
753 procmsg_msginfo_free(msginfo); /* refcnt-- */
754 /* tmp_msginfo == msginfo */
755 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
756 msginfo->returnreceiptto)) {
757 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
758 procmsg_msginfo_free(msginfo); /* refcnt-- */
765 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
767 static const gchar *def_cmd = "lpr %s";
774 g_return_if_fail(msginfo);
776 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
777 g_warning("Can't get text part\n");
781 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
782 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
784 if ((prfp = fopen(prtmp, "wb")) == NULL) {
785 FILE_OP_ERROR(prtmp, "fopen");
791 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
792 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
793 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
794 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
795 if (msginfo->newsgroups)
796 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
797 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
800 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
806 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
808 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
811 g_warning("Print command line is invalid: `%s'\n",
813 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
819 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
823 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
830 MsgInfo *procmsg_msginfo_new(void)
834 newmsginfo = g_new0(MsgInfo, 1);
835 newmsginfo->refcnt = 1;
840 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
844 if (msginfo == NULL) return NULL;
846 newmsginfo = g_new0(MsgInfo, 1);
848 newmsginfo->refcnt = 1;
850 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
851 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
852 g_strdup(msginfo->mmb) : NULL
877 MEMBDUP(dispositionnotificationto);
878 MEMBDUP(returnreceiptto);
882 MEMBCOPY(threadscore);
883 MEMBDUP(plaintext_file);
888 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
890 MsgInfo *full_msginfo;
893 if (msginfo == NULL) return NULL;
895 file = procmsg_get_message_file(msginfo);
897 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
901 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
903 if (!full_msginfo) return NULL;
905 /* CLAWS: make sure we add the missing members; see:
906 * procheader.c::procheader_get_headernames() */
908 msginfo->xface = g_strdup(full_msginfo->xface);
909 if (!msginfo->dispositionnotificationto)
910 msginfo->dispositionnotificationto =
911 g_strdup(full_msginfo->dispositionnotificationto);
912 if (!msginfo->returnreceiptto)
913 msginfo->returnreceiptto = g_strdup
914 (full_msginfo->returnreceiptto);
915 if (!msginfo->partial_recv && full_msginfo->partial_recv)
916 msginfo->partial_recv = g_strdup
917 (full_msginfo->partial_recv);
918 msginfo->total_size = full_msginfo->total_size;
919 if (!msginfo->account_server && full_msginfo->account_server)
920 msginfo->account_server = g_strdup
921 (full_msginfo->account_server);
922 if (!msginfo->account_login && full_msginfo->account_login)
923 msginfo->account_login = g_strdup
924 (full_msginfo->account_login);
925 msginfo->planned_download = full_msginfo->planned_download;
926 procmsg_msginfo_free(full_msginfo);
928 return procmsg_msginfo_new_ref(msginfo);
931 void procmsg_msginfo_free(MsgInfo *msginfo)
933 if (msginfo == NULL) return;
936 if (msginfo->refcnt > 0)
939 if (msginfo->to_folder) {
940 msginfo->to_folder->op_count--;
941 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
944 g_free(msginfo->fromspace);
945 g_free(msginfo->references);
946 g_free(msginfo->returnreceiptto);
947 g_free(msginfo->dispositionnotificationto);
948 g_free(msginfo->xface);
950 g_free(msginfo->fromname);
952 g_free(msginfo->date);
953 g_free(msginfo->from);
956 g_free(msginfo->newsgroups);
957 g_free(msginfo->subject);
958 g_free(msginfo->msgid);
959 g_free(msginfo->inreplyto);
960 g_free(msginfo->xref);
962 g_free(msginfo->partial_recv);
963 g_free(msginfo->account_server);
964 g_free(msginfo->account_login);
966 g_free(msginfo->plaintext_file);
971 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
975 memusage += sizeof(MsgInfo);
976 if (msginfo->fromname)
977 memusage += strlen(msginfo->fromname);
979 memusage += strlen(msginfo->date);
981 memusage += strlen(msginfo->from);
983 memusage += strlen(msginfo->to);
985 memusage += strlen(msginfo->cc);
986 if (msginfo->newsgroups)
987 memusage += strlen(msginfo->newsgroups);
988 if (msginfo->subject)
989 memusage += strlen(msginfo->subject);
991 memusage += strlen(msginfo->msgid);
992 if (msginfo->inreplyto)
993 memusage += strlen(msginfo->inreplyto);
995 memusage += strlen(msginfo->xface);
996 if (msginfo->dispositionnotificationto)
997 memusage += strlen(msginfo->dispositionnotificationto);
998 if (msginfo->returnreceiptto)
999 memusage += strlen(msginfo->returnreceiptto);
1000 if (msginfo->references)
1001 memusage += strlen(msginfo->references);
1002 if (msginfo->fromspace)
1003 memusage += strlen(msginfo->fromspace);
1008 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1010 const MsgInfo *msginfo1 = a;
1011 const MsgInfo *msginfo2 = b;
1018 return msginfo1->msgnum - msginfo2->msgnum;
1027 Q_MAIL_ACCOUNT_ID = 4,
1028 Q_NEWS_ACCOUNT_ID = 5,
1029 Q_SAVE_COPY_FOLDER = 6,
1030 Q_REPLY_MESSAGE_ID = 7,
1031 Q_FWD_MESSAGE_ID = 8,
1032 Q_PRIVACY_SYSTEM = 9,
1034 Q_ENCRYPT_DATA = 11,
1037 gint procmsg_send_message_queue(const gchar *file)
1039 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1040 {"SSV:", NULL, FALSE},
1041 {"R:", NULL, FALSE},
1042 {"NG:", NULL, FALSE},
1043 {"MAID:", NULL, FALSE},
1044 {"NAID:", NULL, FALSE},
1045 {"SCF:", NULL, FALSE},
1046 {"RMID:", NULL, FALSE},
1047 {"FMID:", NULL, FALSE},
1048 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1049 {"X-Sylpheed-Encrypt:", NULL, FALSE},
1050 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1051 {NULL, NULL, FALSE}};
1054 gint mailval = 0, newsval = 0;
1056 gchar *smtpserver = NULL;
1057 GSList *to_list = NULL;
1058 GSList *newsgroup_list = NULL;
1059 gchar *savecopyfolder = NULL;
1060 gchar *replymessageid = NULL;
1061 gchar *fwdmessageid = NULL;
1062 gchar *privacy_system = NULL;
1063 gboolean encrypt = FALSE;
1064 gchar *encrypt_data = NULL;
1065 gchar buf[BUFFSIZE];
1067 PrefsAccount *mailac = NULL, *newsac = NULL;
1068 gboolean save_clear_text = TRUE;
1069 gchar *tmp_enc_file = NULL;
1073 g_return_val_if_fail(file != NULL, -1);
1075 if ((fp = fopen(file, "rb")) == NULL) {
1076 FILE_OP_ERROR(file, "fopen");
1080 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1082 gchar *p = buf + strlen(qentry[hnum].name);
1090 if (smtpserver == NULL)
1091 smtpserver = g_strdup(p);
1094 to_list = address_list_append(to_list, p);
1097 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1099 case Q_MAIL_ACCOUNT_ID:
1100 mailac = account_find_from_id(atoi(p));
1102 case Q_NEWS_ACCOUNT_ID:
1103 newsac = account_find_from_id(atoi(p));
1105 case Q_SAVE_COPY_FOLDER:
1106 if (savecopyfolder == NULL)
1107 savecopyfolder = g_strdup(p);
1109 case Q_REPLY_MESSAGE_ID:
1110 if (replymessageid == NULL)
1111 replymessageid = g_strdup(p);
1113 case Q_FWD_MESSAGE_ID:
1114 if (fwdmessageid == NULL)
1115 fwdmessageid = g_strdup(p);
1117 case Q_PRIVACY_SYSTEM:
1118 if (privacy_system == NULL)
1119 privacy_system = g_strdup(p);
1125 case Q_ENCRYPT_DATA:
1126 if (encrypt_data == NULL)
1127 encrypt_data = g_strdup(p);
1131 filepos = ftell(fp);
1136 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1141 mimeinfo = procmime_scan_queue_file(file);
1142 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1143 || (fp = my_tmpfile()) == NULL
1144 || procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1147 procmime_mimeinfo_free_all(mimeinfo);
1150 slist_free_strings(to_list);
1151 g_slist_free(to_list);
1152 slist_free_strings(newsgroup_list);
1153 g_slist_free(newsgroup_list);
1154 g_free(savecopyfolder);
1155 g_free(replymessageid);
1156 g_free(fwdmessageid);
1157 g_free(privacy_system);
1158 g_free(encrypt_data);
1163 if (!save_clear_text) {
1164 gchar *content = NULL;
1165 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1169 content = file_read_stream_to_str(fp);
1172 get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1173 str_write_to_file(content, tmp_enc_file);
1176 g_warning("couldn't get tempfile\n");
1180 procmime_mimeinfo_free_all(mimeinfo);
1186 debug_print("Sending message by mail\n");
1188 g_warning("Queued message header is broken.\n");
1190 } else if (mailac && mailac->use_mail_command &&
1191 mailac->mail_command && (* mailac->mail_command)) {
1192 mailval = send_message_local(mailac->mail_command, fp);
1196 mailac = account_find_from_smtp_server(from, smtpserver);
1198 g_warning("Account not found. "
1199 "Using current account...\n");
1200 mailac = cur_account;
1205 mailval = send_message_smtp(mailac, to_list, fp);
1207 PrefsAccount tmp_ac;
1209 g_warning("Account not found.\n");
1211 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1212 tmp_ac.address = from;
1213 tmp_ac.smtp_server = smtpserver;
1214 tmp_ac.smtpport = SMTP_PORT;
1215 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1220 fseek(fp, filepos, SEEK_SET);
1221 if (newsgroup_list && (mailval == 0)) {
1226 /* write to temporary file */
1227 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1228 G_DIR_SEPARATOR, (gint)file);
1229 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1230 FILE_OP_ERROR(tmp, "fopen");
1232 alertpanel_error(_("Could not create temporary file for news sending."));
1234 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1235 FILE_OP_ERROR(tmp, "chmod");
1236 g_warning("can't change file mode\n");
1239 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1240 if (fputs(buf, tmpfp) == EOF) {
1241 FILE_OP_ERROR(tmp, "fputs");
1243 alertpanel_error(_("Error when writing temporary file for news sending."));
1249 debug_print("Sending message by news\n");
1251 folder = FOLDER(newsac->folder);
1253 newsval = news_post(folder, tmp);
1255 alertpanel_error(_("Error occurred while posting the message to %s ."),
1256 newsac->nntp_server);
1266 /* save message to outbox */
1267 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1270 debug_print("saving sent message...\n");
1272 outbox = folder_find_item_from_identifier(savecopyfolder);
1274 outbox = folder_get_default_outbox();
1276 if (save_clear_text || tmp_enc_file == NULL) {
1277 procmsg_save_to_outbox(outbox, file, TRUE);
1279 procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1280 unlink(tmp_enc_file);
1285 if (replymessageid != NULL || fwdmessageid != NULL) {
1289 if (replymessageid != NULL)
1290 tokens = g_strsplit(replymessageid, "\x7f", 0);
1292 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1293 item = folder_find_item_from_identifier(tokens[0]);
1295 /* check if queued message has valid folder and message id */
1296 if (item != NULL && tokens[2] != NULL) {
1299 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1301 /* check if referring message exists and has a message id */
1302 if ((msginfo != NULL) &&
1303 (msginfo->msgid != NULL) &&
1304 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1305 procmsg_msginfo_free(msginfo);
1309 if (msginfo == NULL) {
1310 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1313 if (msginfo != NULL) {
1314 if (replymessageid != NULL) {
1315 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1316 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1318 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1319 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1321 procmsg_msginfo_free(msginfo);
1329 slist_free_strings(to_list);
1330 g_slist_free(to_list);
1331 slist_free_strings(newsgroup_list);
1332 g_slist_free(newsgroup_list);
1333 g_free(savecopyfolder);
1334 g_free(replymessageid);
1335 g_free(fwdmessageid);
1336 g_free(privacy_system);
1337 g_free(encrypt_data);
1339 return (newsval != 0 ? newsval : mailval);
1342 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1344 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1347 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1351 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1356 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1357 item->unread_msgs++;
1358 if (procmsg_msg_has_marked_parent(msginfo))
1359 item->unreadmarked_msgs++;
1362 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1363 item->unread_msgs--;
1364 if (procmsg_msg_has_marked_parent(msginfo))
1365 item->unreadmarked_msgs--;
1369 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1370 procmsg_update_unread_children(msginfo, TRUE);
1373 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1374 procmsg_update_unread_children(msginfo, FALSE);
1378 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1381 MsgInfoUpdate msginfo_update;
1382 MsgPermFlags perm_flags_new, perm_flags_old;
1383 MsgTmpFlags tmp_flags_old;
1385 g_return_if_fail(msginfo != NULL);
1386 item = msginfo->folder;
1387 g_return_if_fail(item != NULL);
1389 debug_print("Setting 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;
1394 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1395 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1398 if (perm_flags_old != perm_flags_new) {
1399 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1401 update_folder_msg_counts(item, msginfo, perm_flags_old);
1405 /* Tmp flags handling */
1406 tmp_flags_old = msginfo->flags.tmp_flags;
1407 msginfo->flags.tmp_flags |= tmp_flags;
1409 /* update notification */
1410 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1411 msginfo_update.msginfo = msginfo;
1412 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1413 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1414 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1418 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1421 MsgInfoUpdate msginfo_update;
1422 MsgPermFlags perm_flags_new, perm_flags_old;
1423 MsgTmpFlags tmp_flags_old;
1425 g_return_if_fail(msginfo != NULL);
1426 item = msginfo->folder;
1427 g_return_if_fail(item != NULL);
1429 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1431 /* Perm Flags handling */
1432 perm_flags_old = msginfo->flags.perm_flags;
1433 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1435 if (perm_flags_old != perm_flags_new) {
1436 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1438 update_folder_msg_counts(item, msginfo, perm_flags_old);
1440 msginfo_update.msginfo = msginfo;
1441 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1442 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1443 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1446 /* Tmp flags hanlding */
1447 tmp_flags_old = msginfo->flags.tmp_flags;
1448 msginfo->flags.tmp_flags &= ~tmp_flags;
1450 /* update notification */
1451 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1452 msginfo_update.msginfo = msginfo;
1453 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1454 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1455 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1460 *\brief check for flags (e.g. mark) in prior msgs of current thread
1462 *\param info Current message
1463 *\param perm_flags Flags to be checked
1464 *\param parentmsgs Hash of prior msgs to avoid loops
1466 *\return gboolean TRUE if perm_flags are found
1468 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1469 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1473 g_return_val_if_fail(info != NULL, FALSE);
1475 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1476 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1478 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1479 procmsg_msginfo_free(tmp);
1481 } else if (tmp != NULL) {
1484 if (g_hash_table_lookup(parentmsgs, info)) {
1485 debug_print("loop detected: %s%c%d\n",
1486 folder_item_get_path(info->folder),
1487 G_DIR_SEPARATOR, info->msgnum);
1490 g_hash_table_insert(parentmsgs, info, "1");
1491 result = procmsg_msg_has_flagged_parent_real(
1492 tmp, perm_flags, parentmsgs);
1494 procmsg_msginfo_free(tmp);
1504 *\brief Callback for cleaning up hash of parentmsgs
1506 gboolean parentmsgs_hash_remove(gpointer key,
1514 *\brief Set up list of parentmsgs
1515 * See procmsg_msg_has_flagged_parent_real()
1517 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1520 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1522 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1523 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1524 g_hash_table_destroy(parentmsgs);
1529 *\brief Check if msgs prior in thread are marked
1530 * See procmsg_msg_has_flagged_parent_real()
1532 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1534 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1538 GSList *procmsg_find_children_func(MsgInfo *info,
1539 GSList *children, GSList *all)
1543 g_return_val_if_fail(info!=NULL, children);
1544 if (info->msgid == NULL)
1547 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1548 MsgInfo *tmp = (MsgInfo *)cur->data;
1549 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1550 /* Check if message is already in the list */
1551 if ((children == NULL) ||
1552 (g_slist_index(children, tmp) == -1)) {
1553 children = g_slist_prepend(children,
1554 procmsg_msginfo_new_ref(tmp));
1555 children = procmsg_find_children_func(tmp,
1564 GSList *procmsg_find_children (MsgInfo *info)
1569 g_return_val_if_fail(info!=NULL, NULL);
1570 all = folder_item_get_msg_list(info->folder);
1571 children = procmsg_find_children_func(info, NULL, all);
1572 if (children != NULL) {
1573 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1574 /* this will not free the used pointers
1575 created with procmsg_msginfo_new_ref */
1576 procmsg_msginfo_free((MsgInfo *)cur->data);
1584 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1586 GSList *children = procmsg_find_children(info);
1588 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1589 MsgInfo *tmp = (MsgInfo *)cur->data;
1590 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1592 info->folder->unreadmarked_msgs++;
1594 info->folder->unreadmarked_msgs--;
1595 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1597 procmsg_msginfo_free(tmp);
1599 g_slist_free(children);
1603 * Set the destination folder for a copy or move operation
1605 * \param msginfo The message which's destination folder is changed
1606 * \param to_folder The destination folder for the operation
1608 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1610 if(msginfo->to_folder != NULL) {
1611 msginfo->to_folder->op_count--;
1612 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1614 msginfo->to_folder = to_folder;
1615 if(to_folder != NULL) {
1616 to_folder->op_count++;
1617 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1622 * Apply filtering actions to the msginfo
1624 * \param msginfo The MsgInfo describing the message that should be filtered
1625 * \return TRUE if the message was moved and MsgInfo is now invalid,
1628 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1630 MailFilteringData mail_filtering_data;
1632 mail_filtering_data.msginfo = msginfo;
1633 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1636 /* filter if enabled in prefs or move to inbox if not */
1637 if((filtering_rules != NULL) &&
1638 filter_message_by_msginfo(filtering_rules, msginfo))