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.
23 #include <glib/gi18n.h>
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_path(msginfo);
896 if (!file || !is_file_exist(file)) {
898 file = procmsg_get_message_file(msginfo);
900 if (!file || !is_file_exist(file)) {
901 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
905 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
907 if (!full_msginfo) return NULL;
909 /* CLAWS: make sure we add the missing members; see:
910 * procheader.c::procheader_get_headernames() */
912 msginfo->xface = g_strdup(full_msginfo->xface);
913 if (!msginfo->dispositionnotificationto)
914 msginfo->dispositionnotificationto =
915 g_strdup(full_msginfo->dispositionnotificationto);
916 if (!msginfo->returnreceiptto)
917 msginfo->returnreceiptto = g_strdup
918 (full_msginfo->returnreceiptto);
919 if (!msginfo->partial_recv && full_msginfo->partial_recv)
920 msginfo->partial_recv = g_strdup
921 (full_msginfo->partial_recv);
922 msginfo->total_size = full_msginfo->total_size;
923 if (!msginfo->account_server && full_msginfo->account_server)
924 msginfo->account_server = g_strdup
925 (full_msginfo->account_server);
926 if (!msginfo->account_login && full_msginfo->account_login)
927 msginfo->account_login = g_strdup
928 (full_msginfo->account_login);
929 msginfo->planned_download = full_msginfo->planned_download;
930 procmsg_msginfo_free(full_msginfo);
932 return procmsg_msginfo_new_ref(msginfo);
935 void procmsg_msginfo_free(MsgInfo *msginfo)
937 if (msginfo == NULL) return;
940 if (msginfo->refcnt > 0)
943 if (msginfo->to_folder) {
944 msginfo->to_folder->op_count--;
945 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
948 g_free(msginfo->fromspace);
949 g_free(msginfo->references);
950 g_free(msginfo->returnreceiptto);
951 g_free(msginfo->dispositionnotificationto);
952 g_free(msginfo->xface);
954 g_free(msginfo->fromname);
956 g_free(msginfo->date);
957 g_free(msginfo->from);
960 g_free(msginfo->newsgroups);
961 g_free(msginfo->subject);
962 g_free(msginfo->msgid);
963 g_free(msginfo->inreplyto);
964 g_free(msginfo->xref);
966 g_free(msginfo->partial_recv);
967 g_free(msginfo->account_server);
968 g_free(msginfo->account_login);
970 g_free(msginfo->plaintext_file);
975 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
979 memusage += sizeof(MsgInfo);
980 if (msginfo->fromname)
981 memusage += strlen(msginfo->fromname);
983 memusage += strlen(msginfo->date);
985 memusage += strlen(msginfo->from);
987 memusage += strlen(msginfo->to);
989 memusage += strlen(msginfo->cc);
990 if (msginfo->newsgroups)
991 memusage += strlen(msginfo->newsgroups);
992 if (msginfo->subject)
993 memusage += strlen(msginfo->subject);
995 memusage += strlen(msginfo->msgid);
996 if (msginfo->inreplyto)
997 memusage += strlen(msginfo->inreplyto);
999 memusage += strlen(msginfo->xface);
1000 if (msginfo->dispositionnotificationto)
1001 memusage += strlen(msginfo->dispositionnotificationto);
1002 if (msginfo->returnreceiptto)
1003 memusage += strlen(msginfo->returnreceiptto);
1004 if (msginfo->references)
1005 memusage += strlen(msginfo->references);
1006 if (msginfo->fromspace)
1007 memusage += strlen(msginfo->fromspace);
1012 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1014 const MsgInfo *msginfo1 = a;
1015 const MsgInfo *msginfo2 = b;
1022 return msginfo1->msgnum - msginfo2->msgnum;
1031 Q_MAIL_ACCOUNT_ID = 4,
1032 Q_NEWS_ACCOUNT_ID = 5,
1033 Q_SAVE_COPY_FOLDER = 6,
1034 Q_REPLY_MESSAGE_ID = 7,
1035 Q_FWD_MESSAGE_ID = 8,
1036 Q_PRIVACY_SYSTEM = 9,
1038 Q_ENCRYPT_DATA = 11,
1041 gint procmsg_send_message_queue(const gchar *file)
1043 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1044 {"SSV:", NULL, FALSE},
1045 {"R:", NULL, FALSE},
1046 {"NG:", NULL, FALSE},
1047 {"MAID:", NULL, FALSE},
1048 {"NAID:", NULL, FALSE},
1049 {"SCF:", NULL, FALSE},
1050 {"RMID:", NULL, FALSE},
1051 {"FMID:", NULL, FALSE},
1052 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1053 {"X-Sylpheed-Encrypt:", NULL, FALSE},
1054 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1055 {NULL, NULL, FALSE}};
1058 gint mailval = 0, newsval = 0;
1060 gchar *smtpserver = NULL;
1061 GSList *to_list = NULL;
1062 GSList *newsgroup_list = NULL;
1063 gchar *savecopyfolder = NULL;
1064 gchar *replymessageid = NULL;
1065 gchar *fwdmessageid = NULL;
1066 gchar *privacy_system = NULL;
1067 gboolean encrypt = FALSE;
1068 gchar *encrypt_data = NULL;
1069 gchar buf[BUFFSIZE];
1071 PrefsAccount *mailac = NULL, *newsac = NULL;
1072 gboolean save_clear_text = TRUE;
1073 gchar *tmp_enc_file = NULL;
1077 g_return_val_if_fail(file != NULL, -1);
1079 if ((fp = fopen(file, "rb")) == NULL) {
1080 FILE_OP_ERROR(file, "fopen");
1084 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1086 gchar *p = buf + strlen(qentry[hnum].name);
1094 if (smtpserver == NULL)
1095 smtpserver = g_strdup(p);
1098 to_list = address_list_append(to_list, p);
1101 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1103 case Q_MAIL_ACCOUNT_ID:
1104 mailac = account_find_from_id(atoi(p));
1106 case Q_NEWS_ACCOUNT_ID:
1107 newsac = account_find_from_id(atoi(p));
1109 case Q_SAVE_COPY_FOLDER:
1110 if (savecopyfolder == NULL)
1111 savecopyfolder = g_strdup(p);
1113 case Q_REPLY_MESSAGE_ID:
1114 if (replymessageid == NULL)
1115 replymessageid = g_strdup(p);
1117 case Q_FWD_MESSAGE_ID:
1118 if (fwdmessageid == NULL)
1119 fwdmessageid = g_strdup(p);
1121 case Q_PRIVACY_SYSTEM:
1122 if (privacy_system == NULL)
1123 privacy_system = g_strdup(p);
1129 case Q_ENCRYPT_DATA:
1130 if (encrypt_data == NULL)
1131 encrypt_data = g_strdup(p);
1135 filepos = ftell(fp);
1140 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1145 mimeinfo = procmime_scan_queue_file(file);
1146 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1147 || (fp = my_tmpfile()) == NULL
1148 || procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1151 procmime_mimeinfo_free_all(mimeinfo);
1154 slist_free_strings(to_list);
1155 g_slist_free(to_list);
1156 slist_free_strings(newsgroup_list);
1157 g_slist_free(newsgroup_list);
1158 g_free(savecopyfolder);
1159 g_free(replymessageid);
1160 g_free(fwdmessageid);
1161 g_free(privacy_system);
1162 g_free(encrypt_data);
1167 if (!save_clear_text) {
1168 gchar *content = NULL;
1169 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1173 content = file_read_stream_to_str(fp);
1176 str_write_to_file(content, tmp_enc_file);
1179 g_warning("couldn't get tempfile\n");
1183 procmime_mimeinfo_free_all(mimeinfo);
1189 debug_print("Sending message by mail\n");
1191 g_warning("Queued message header is broken.\n");
1193 } else if (mailac && mailac->use_mail_command &&
1194 mailac->mail_command && (* mailac->mail_command)) {
1195 mailval = send_message_local(mailac->mail_command, fp);
1199 mailac = account_find_from_smtp_server(from, smtpserver);
1201 g_warning("Account not found. "
1202 "Using current account...\n");
1203 mailac = cur_account;
1208 mailval = send_message_smtp(mailac, to_list, fp);
1210 PrefsAccount tmp_ac;
1212 g_warning("Account not found.\n");
1214 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1215 tmp_ac.address = from;
1216 tmp_ac.smtp_server = smtpserver;
1217 tmp_ac.smtpport = SMTP_PORT;
1218 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1223 fseek(fp, filepos, SEEK_SET);
1224 if (newsgroup_list && (mailval == 0)) {
1229 /* write to temporary file */
1230 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1231 G_DIR_SEPARATOR, (gint)file);
1232 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1233 FILE_OP_ERROR(tmp, "fopen");
1235 alertpanel_error(_("Could not create temporary file for news sending."));
1237 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1238 FILE_OP_ERROR(tmp, "chmod");
1239 g_warning("can't change file mode\n");
1242 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1243 if (fputs(buf, tmpfp) == EOF) {
1244 FILE_OP_ERROR(tmp, "fputs");
1246 alertpanel_error(_("Error when writing temporary file for news sending."));
1252 debug_print("Sending message by news\n");
1254 folder = FOLDER(newsac->folder);
1256 newsval = news_post(folder, tmp);
1258 alertpanel_error(_("Error occurred while posting the message to %s ."),
1259 newsac->nntp_server);
1269 /* save message to outbox */
1270 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1273 debug_print("saving sent message...\n");
1275 outbox = folder_find_item_from_identifier(savecopyfolder);
1277 outbox = folder_get_default_outbox();
1279 if (save_clear_text || tmp_enc_file == NULL) {
1280 procmsg_save_to_outbox(outbox, file, TRUE);
1282 procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1286 if (tmp_enc_file != NULL) {
1287 unlink(tmp_enc_file);
1289 tmp_enc_file = NULL;
1292 if (replymessageid != NULL || fwdmessageid != NULL) {
1296 if (replymessageid != NULL)
1297 tokens = g_strsplit(replymessageid, "\x7f", 0);
1299 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1300 item = folder_find_item_from_identifier(tokens[0]);
1302 /* check if queued message has valid folder and message id */
1303 if (item != NULL && tokens[2] != NULL) {
1306 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1308 /* check if referring message exists and has a message id */
1309 if ((msginfo != NULL) &&
1310 (msginfo->msgid != NULL) &&
1311 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1312 procmsg_msginfo_free(msginfo);
1316 if (msginfo == NULL) {
1317 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1320 if (msginfo != NULL) {
1321 if (replymessageid != NULL) {
1322 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1323 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1325 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1326 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1328 procmsg_msginfo_free(msginfo);
1336 slist_free_strings(to_list);
1337 g_slist_free(to_list);
1338 slist_free_strings(newsgroup_list);
1339 g_slist_free(newsgroup_list);
1340 g_free(savecopyfolder);
1341 g_free(replymessageid);
1342 g_free(fwdmessageid);
1343 g_free(privacy_system);
1344 g_free(encrypt_data);
1346 return (newsval != 0 ? newsval : mailval);
1349 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1351 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1354 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1358 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1363 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1364 item->unread_msgs++;
1365 if (procmsg_msg_has_marked_parent(msginfo))
1366 item->unreadmarked_msgs++;
1369 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1370 item->unread_msgs--;
1371 if (procmsg_msg_has_marked_parent(msginfo))
1372 item->unreadmarked_msgs--;
1376 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1377 procmsg_update_unread_children(msginfo, TRUE);
1380 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1381 procmsg_update_unread_children(msginfo, FALSE);
1385 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1388 MsgInfoUpdate msginfo_update;
1389 MsgPermFlags perm_flags_new, perm_flags_old;
1390 MsgTmpFlags tmp_flags_old;
1392 g_return_if_fail(msginfo != NULL);
1393 item = msginfo->folder;
1394 g_return_if_fail(item != NULL);
1396 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1398 /* Perm Flags handling */
1399 perm_flags_old = msginfo->flags.perm_flags;
1400 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1401 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1402 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1405 if (perm_flags_old != perm_flags_new) {
1406 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1408 update_folder_msg_counts(item, msginfo, perm_flags_old);
1412 /* Tmp flags handling */
1413 tmp_flags_old = msginfo->flags.tmp_flags;
1414 msginfo->flags.tmp_flags |= tmp_flags;
1416 /* update notification */
1417 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1418 msginfo_update.msginfo = msginfo;
1419 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1420 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1421 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1425 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1428 MsgInfoUpdate msginfo_update;
1429 MsgPermFlags perm_flags_new, perm_flags_old;
1430 MsgTmpFlags tmp_flags_old;
1432 g_return_if_fail(msginfo != NULL);
1433 item = msginfo->folder;
1434 g_return_if_fail(item != NULL);
1436 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1438 /* Perm Flags handling */
1439 perm_flags_old = msginfo->flags.perm_flags;
1440 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1442 if (perm_flags_old != perm_flags_new) {
1443 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1445 update_folder_msg_counts(item, msginfo, perm_flags_old);
1447 msginfo_update.msginfo = msginfo;
1448 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1449 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1450 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1453 /* Tmp flags hanlding */
1454 tmp_flags_old = msginfo->flags.tmp_flags;
1455 msginfo->flags.tmp_flags &= ~tmp_flags;
1457 /* update notification */
1458 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1459 msginfo_update.msginfo = msginfo;
1460 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1461 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1462 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1467 *\brief check for flags (e.g. mark) in prior msgs of current thread
1469 *\param info Current message
1470 *\param perm_flags Flags to be checked
1471 *\param parentmsgs Hash of prior msgs to avoid loops
1473 *\return gboolean TRUE if perm_flags are found
1475 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1476 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1480 g_return_val_if_fail(info != NULL, FALSE);
1482 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1483 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1485 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1486 procmsg_msginfo_free(tmp);
1488 } else if (tmp != NULL) {
1491 if (g_hash_table_lookup(parentmsgs, info)) {
1492 debug_print("loop detected: %s%c%d\n",
1493 folder_item_get_path(info->folder),
1494 G_DIR_SEPARATOR, info->msgnum);
1497 g_hash_table_insert(parentmsgs, info, "1");
1498 result = procmsg_msg_has_flagged_parent_real(
1499 tmp, perm_flags, parentmsgs);
1501 procmsg_msginfo_free(tmp);
1511 *\brief Callback for cleaning up hash of parentmsgs
1513 gboolean parentmsgs_hash_remove(gpointer key,
1521 *\brief Set up list of parentmsgs
1522 * See procmsg_msg_has_flagged_parent_real()
1524 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1527 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1529 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1530 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1531 g_hash_table_destroy(parentmsgs);
1536 *\brief Check if msgs prior in thread are marked
1537 * See procmsg_msg_has_flagged_parent_real()
1539 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1541 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1545 GSList *procmsg_find_children_func(MsgInfo *info,
1546 GSList *children, GSList *all)
1550 g_return_val_if_fail(info!=NULL, children);
1551 if (info->msgid == NULL)
1554 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1555 MsgInfo *tmp = (MsgInfo *)cur->data;
1556 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1557 /* Check if message is already in the list */
1558 if ((children == NULL) ||
1559 (g_slist_index(children, tmp) == -1)) {
1560 children = g_slist_prepend(children,
1561 procmsg_msginfo_new_ref(tmp));
1562 children = procmsg_find_children_func(tmp,
1571 GSList *procmsg_find_children (MsgInfo *info)
1576 g_return_val_if_fail(info!=NULL, NULL);
1577 all = folder_item_get_msg_list(info->folder);
1578 children = procmsg_find_children_func(info, NULL, all);
1579 if (children != NULL) {
1580 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1581 /* this will not free the used pointers
1582 created with procmsg_msginfo_new_ref */
1583 procmsg_msginfo_free((MsgInfo *)cur->data);
1591 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1593 GSList *children = procmsg_find_children(info);
1595 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1596 MsgInfo *tmp = (MsgInfo *)cur->data;
1597 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1599 info->folder->unreadmarked_msgs++;
1601 info->folder->unreadmarked_msgs--;
1602 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1604 procmsg_msginfo_free(tmp);
1606 g_slist_free(children);
1610 * Set the destination folder for a copy or move operation
1612 * \param msginfo The message which's destination folder is changed
1613 * \param to_folder The destination folder for the operation
1615 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1617 if(msginfo->to_folder != NULL) {
1618 msginfo->to_folder->op_count--;
1619 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1621 msginfo->to_folder = to_folder;
1622 if(to_folder != NULL) {
1623 to_folder->op_count++;
1624 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1629 * Apply filtering actions to the msginfo
1631 * \param msginfo The MsgInfo describing the message that should be filtered
1632 * \return TRUE if the message was moved and MsgInfo is now invalid,
1635 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1637 MailFilteringData mail_filtering_data;
1639 mail_filtering_data.msginfo = msginfo;
1640 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1643 /* filter if enabled in prefs or move to inbox if not */
1644 if((filtering_rules != NULL) &&
1645 filter_message_by_msginfo(filtering_rules, msginfo))
1651 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1653 MsgInfo *tmp_msginfo = NULL;
1654 MsgFlags flags = {0, 0};
1657 if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1658 g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1659 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1663 if (mimeinfo->content == MIMECONTENT_MEM) {
1664 gchar *tmpfile = get_tmp_file();
1665 str_write_to_file(mimeinfo->data.mem, tmpfile);
1666 g_free(mimeinfo->data.mem);
1667 mimeinfo->content == MIMECONTENT_FILE;
1668 mimeinfo->data.filename = g_strdup(tmpfile);
1672 tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1673 flags, TRUE, FALSE);
1675 if (tmp_msginfo != NULL) {
1676 tmp_msginfo->folder = src_msginfo->folder;
1677 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1679 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");