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)
607 if (trash && trash->total_msgs > 0) {
608 GSList *mlist = folder_item_get_msg_list(trash);
610 for (cur = mlist ; cur != NULL ; cur = cur->next) {
611 MsgInfo * msginfo = (MsgInfo *) cur->data;
612 partial_mark_for_delete(msginfo);
613 procmsg_msginfo_free(msginfo);
616 folder_item_remove_all_msg(trash);
620 void procmsg_empty_all_trash(void)
625 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
626 trash = FOLDER(cur->data)->trash;
627 procmsg_empty_trash(trash);
632 *\brief Send messages in queue
634 *\param queue Queue folder to process
635 *\param save_msgs Unused
637 *\return Number of messages sent, negative if an error occurred
638 * positive if no error occurred
640 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
642 gint sent = 0, err = 0;
646 queue = folder_get_default_queue();
647 g_return_val_if_fail(queue != NULL, -1);
649 folder_item_scan(queue);
650 list = folder_item_get_msg_list(queue);
652 for (elem = list; elem != NULL; elem = elem->next) {
656 msginfo = (MsgInfo *)(elem->data);
657 if (!MSG_IS_LOCKED(msginfo->flags)) {
658 file = folder_item_fetch_msg(queue, msginfo->msgnum);
660 if (procmsg_send_message_queue(file) < 0) {
661 g_warning("Sending queued message %d failed.\n",
666 * We save in procmsg_send_message_queue because
667 * we need the destination folder from the queue
671 procmsg_save_to_outbox
672 (queue->folder->outbox,
676 folder_item_remove_msg(queue, msginfo->msgnum);
681 /* FIXME: supposedly if only one message is locked, and queue
682 * is being flushed, the following free says something like
683 * "freeing msg ## in folder (nil)". */
684 procmsg_msginfo_free(msginfo);
687 return (err != 0 ? -err : sent);
690 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
695 if ((fp = fopen(in, "rb")) == NULL) {
696 FILE_OP_ERROR(in, "fopen");
699 if ((outfp = fopen(out, "wb")) == NULL) {
700 FILE_OP_ERROR(out, "fopen");
704 while (fgets(buf, sizeof(buf), fp) != NULL)
705 if (buf[0] == '\r' || buf[0] == '\n') break;
706 while (fgets(buf, sizeof(buf), fp) != NULL)
713 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
717 MsgInfo *msginfo, *tmp_msginfo;
718 MsgFlags flag = {0, 0};
720 debug_print("saving sent message...\n");
723 outbox = folder_get_default_outbox();
724 g_return_val_if_fail(outbox != NULL, -1);
726 /* remove queueing headers */
728 gchar tmp[MAXPATHLEN + 1];
730 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
731 get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
733 if (procmsg_remove_special_headers(file, tmp) !=0)
736 folder_item_scan(outbox);
737 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
738 g_warning("can't save message\n");
743 folder_item_scan(outbox);
744 if ((num = folder_item_add_msg
745 (outbox, file, &flag, FALSE)) < 0) {
746 g_warning("can't save message\n");
751 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
752 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
753 if (msginfo != NULL) {
754 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
755 procmsg_msginfo_free(msginfo); /* refcnt-- */
756 /* tmp_msginfo == msginfo */
757 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
758 msginfo->returnreceiptto)) {
759 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
760 procmsg_msginfo_free(msginfo); /* refcnt-- */
767 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
769 static const gchar *def_cmd = "lpr %s";
776 g_return_if_fail(msginfo);
778 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
779 g_warning("Can't get text part\n");
783 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
784 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
786 if ((prfp = fopen(prtmp, "wb")) == NULL) {
787 FILE_OP_ERROR(prtmp, "fopen");
793 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
794 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
795 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
796 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
797 if (msginfo->newsgroups)
798 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
799 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
802 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
808 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
810 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
813 g_warning("Print command line is invalid: `%s'\n",
815 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
821 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
825 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
832 MsgInfo *procmsg_msginfo_new(void)
836 newmsginfo = g_new0(MsgInfo, 1);
837 newmsginfo->refcnt = 1;
842 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
846 if (msginfo == NULL) return NULL;
848 newmsginfo = g_new0(MsgInfo, 1);
850 newmsginfo->refcnt = 1;
852 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
853 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
854 g_strdup(msginfo->mmb) : NULL
879 MEMBDUP(dispositionnotificationto);
880 MEMBDUP(returnreceiptto);
884 MEMBCOPY(threadscore);
885 MEMBDUP(plaintext_file);
890 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
892 MsgInfo *full_msginfo;
895 if (msginfo == NULL) return NULL;
897 file = procmsg_get_message_file_path(msginfo);
898 if (!file || !is_file_exist(file)) {
900 file = procmsg_get_message_file(msginfo);
902 if (!file || !is_file_exist(file)) {
903 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
907 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
909 if (!full_msginfo) return NULL;
911 /* CLAWS: make sure we add the missing members; see:
912 * procheader.c::procheader_get_headernames() */
914 msginfo->xface = g_strdup(full_msginfo->xface);
915 if (!msginfo->dispositionnotificationto)
916 msginfo->dispositionnotificationto =
917 g_strdup(full_msginfo->dispositionnotificationto);
918 if (!msginfo->returnreceiptto)
919 msginfo->returnreceiptto = g_strdup
920 (full_msginfo->returnreceiptto);
921 if (!msginfo->partial_recv && full_msginfo->partial_recv)
922 msginfo->partial_recv = g_strdup
923 (full_msginfo->partial_recv);
924 msginfo->total_size = full_msginfo->total_size;
925 if (!msginfo->account_server && full_msginfo->account_server)
926 msginfo->account_server = g_strdup
927 (full_msginfo->account_server);
928 if (!msginfo->account_login && full_msginfo->account_login)
929 msginfo->account_login = g_strdup
930 (full_msginfo->account_login);
931 msginfo->planned_download = full_msginfo->planned_download;
932 procmsg_msginfo_free(full_msginfo);
934 return procmsg_msginfo_new_ref(msginfo);
937 void procmsg_msginfo_free(MsgInfo *msginfo)
939 if (msginfo == NULL) return;
942 if (msginfo->refcnt > 0)
945 if (msginfo->to_folder) {
946 msginfo->to_folder->op_count--;
947 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
950 g_free(msginfo->fromspace);
951 g_free(msginfo->references);
952 g_free(msginfo->returnreceiptto);
953 g_free(msginfo->dispositionnotificationto);
954 g_free(msginfo->xface);
956 g_free(msginfo->fromname);
958 g_free(msginfo->date);
959 g_free(msginfo->from);
962 g_free(msginfo->newsgroups);
963 g_free(msginfo->subject);
964 g_free(msginfo->msgid);
965 g_free(msginfo->inreplyto);
966 g_free(msginfo->xref);
968 g_free(msginfo->partial_recv);
969 g_free(msginfo->account_server);
970 g_free(msginfo->account_login);
972 g_free(msginfo->plaintext_file);
977 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
981 memusage += sizeof(MsgInfo);
982 if (msginfo->fromname)
983 memusage += strlen(msginfo->fromname);
985 memusage += strlen(msginfo->date);
987 memusage += strlen(msginfo->from);
989 memusage += strlen(msginfo->to);
991 memusage += strlen(msginfo->cc);
992 if (msginfo->newsgroups)
993 memusage += strlen(msginfo->newsgroups);
994 if (msginfo->subject)
995 memusage += strlen(msginfo->subject);
997 memusage += strlen(msginfo->msgid);
998 if (msginfo->inreplyto)
999 memusage += strlen(msginfo->inreplyto);
1001 memusage += strlen(msginfo->xface);
1002 if (msginfo->dispositionnotificationto)
1003 memusage += strlen(msginfo->dispositionnotificationto);
1004 if (msginfo->returnreceiptto)
1005 memusage += strlen(msginfo->returnreceiptto);
1006 if (msginfo->references)
1007 memusage += strlen(msginfo->references);
1008 if (msginfo->fromspace)
1009 memusage += strlen(msginfo->fromspace);
1014 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1016 const MsgInfo *msginfo1 = a;
1017 const MsgInfo *msginfo2 = b;
1024 return msginfo1->msgnum - msginfo2->msgnum;
1033 Q_MAIL_ACCOUNT_ID = 4,
1034 Q_NEWS_ACCOUNT_ID = 5,
1035 Q_SAVE_COPY_FOLDER = 6,
1036 Q_REPLY_MESSAGE_ID = 7,
1037 Q_FWD_MESSAGE_ID = 8,
1038 Q_PRIVACY_SYSTEM = 9,
1040 Q_ENCRYPT_DATA = 11,
1043 gint procmsg_send_message_queue(const gchar *file)
1045 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1046 {"SSV:", NULL, FALSE},
1047 {"R:", NULL, FALSE},
1048 {"NG:", NULL, FALSE},
1049 {"MAID:", NULL, FALSE},
1050 {"NAID:", NULL, FALSE},
1051 {"SCF:", NULL, FALSE},
1052 {"RMID:", NULL, FALSE},
1053 {"FMID:", NULL, FALSE},
1054 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1055 {"X-Sylpheed-Encrypt:", NULL, FALSE},
1056 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1057 {NULL, NULL, FALSE}};
1060 gint mailval = 0, newsval = 0;
1062 gchar *smtpserver = NULL;
1063 GSList *to_list = NULL;
1064 GSList *newsgroup_list = NULL;
1065 gchar *savecopyfolder = NULL;
1066 gchar *replymessageid = NULL;
1067 gchar *fwdmessageid = NULL;
1068 gchar *privacy_system = NULL;
1069 gboolean encrypt = FALSE;
1070 gchar *encrypt_data = NULL;
1071 gchar buf[BUFFSIZE];
1073 PrefsAccount *mailac = NULL, *newsac = NULL;
1074 gboolean save_clear_text = TRUE;
1075 gchar *tmp_enc_file = NULL;
1079 g_return_val_if_fail(file != NULL, -1);
1081 if ((fp = fopen(file, "rb")) == NULL) {
1082 FILE_OP_ERROR(file, "fopen");
1086 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1088 gchar *p = buf + strlen(qentry[hnum].name);
1096 if (smtpserver == NULL)
1097 smtpserver = g_strdup(p);
1100 to_list = address_list_append(to_list, p);
1103 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1105 case Q_MAIL_ACCOUNT_ID:
1106 mailac = account_find_from_id(atoi(p));
1108 case Q_NEWS_ACCOUNT_ID:
1109 newsac = account_find_from_id(atoi(p));
1111 case Q_SAVE_COPY_FOLDER:
1112 if (savecopyfolder == NULL)
1113 savecopyfolder = g_strdup(p);
1115 case Q_REPLY_MESSAGE_ID:
1116 if (replymessageid == NULL)
1117 replymessageid = g_strdup(p);
1119 case Q_FWD_MESSAGE_ID:
1120 if (fwdmessageid == NULL)
1121 fwdmessageid = g_strdup(p);
1123 case Q_PRIVACY_SYSTEM:
1124 if (privacy_system == NULL)
1125 privacy_system = g_strdup(p);
1131 case Q_ENCRYPT_DATA:
1132 if (encrypt_data == NULL)
1133 encrypt_data = g_strdup(p);
1137 filepos = ftell(fp);
1142 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1147 mimeinfo = procmime_scan_queue_file(file);
1148 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1149 || (fp = my_tmpfile()) == NULL
1150 || procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1153 procmime_mimeinfo_free_all(mimeinfo);
1156 slist_free_strings(to_list);
1157 g_slist_free(to_list);
1158 slist_free_strings(newsgroup_list);
1159 g_slist_free(newsgroup_list);
1160 g_free(savecopyfolder);
1161 g_free(replymessageid);
1162 g_free(fwdmessageid);
1163 g_free(privacy_system);
1164 g_free(encrypt_data);
1169 if (!save_clear_text) {
1170 gchar *content = NULL;
1171 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1175 content = file_read_stream_to_str(fp);
1178 get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1179 str_write_to_file(content, tmp_enc_file);
1182 g_warning("couldn't get tempfile\n");
1186 procmime_mimeinfo_free_all(mimeinfo);
1192 debug_print("Sending message by mail\n");
1194 g_warning("Queued message header is broken.\n");
1196 } else if (mailac && mailac->use_mail_command &&
1197 mailac->mail_command && (* mailac->mail_command)) {
1198 mailval = send_message_local(mailac->mail_command, fp);
1202 mailac = account_find_from_smtp_server(from, smtpserver);
1204 g_warning("Account not found. "
1205 "Using current account...\n");
1206 mailac = cur_account;
1211 mailval = send_message_smtp(mailac, to_list, fp);
1213 PrefsAccount tmp_ac;
1215 g_warning("Account not found.\n");
1217 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1218 tmp_ac.address = from;
1219 tmp_ac.smtp_server = smtpserver;
1220 tmp_ac.smtpport = SMTP_PORT;
1221 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1226 fseek(fp, filepos, SEEK_SET);
1227 if (newsgroup_list && (mailval == 0)) {
1232 /* write to temporary file */
1233 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1234 G_DIR_SEPARATOR, (gint)file);
1235 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1236 FILE_OP_ERROR(tmp, "fopen");
1238 alertpanel_error(_("Could not create temporary file for news sending."));
1240 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1241 FILE_OP_ERROR(tmp, "chmod");
1242 g_warning("can't change file mode\n");
1245 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1246 if (fputs(buf, tmpfp) == EOF) {
1247 FILE_OP_ERROR(tmp, "fputs");
1249 alertpanel_error(_("Error when writing temporary file for news sending."));
1255 debug_print("Sending message by news\n");
1257 folder = FOLDER(newsac->folder);
1259 newsval = news_post(folder, tmp);
1261 alertpanel_error(_("Error occurred while posting the message to %s ."),
1262 newsac->nntp_server);
1272 /* save message to outbox */
1273 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1276 debug_print("saving sent message...\n");
1278 outbox = folder_find_item_from_identifier(savecopyfolder);
1280 outbox = folder_get_default_outbox();
1282 if (save_clear_text || tmp_enc_file == NULL) {
1283 procmsg_save_to_outbox(outbox, file, TRUE);
1285 procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1286 unlink(tmp_enc_file);
1291 if (replymessageid != NULL || fwdmessageid != NULL) {
1295 if (replymessageid != NULL)
1296 tokens = g_strsplit(replymessageid, "\x7f", 0);
1298 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1299 item = folder_find_item_from_identifier(tokens[0]);
1301 /* check if queued message has valid folder and message id */
1302 if (item != NULL && tokens[2] != NULL) {
1305 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1307 /* check if referring message exists and has a message id */
1308 if ((msginfo != NULL) &&
1309 (msginfo->msgid != NULL) &&
1310 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1311 procmsg_msginfo_free(msginfo);
1315 if (msginfo == NULL) {
1316 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1319 if (msginfo != NULL) {
1320 if (replymessageid != NULL) {
1321 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1322 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1324 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1325 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1327 procmsg_msginfo_free(msginfo);
1335 slist_free_strings(to_list);
1336 g_slist_free(to_list);
1337 slist_free_strings(newsgroup_list);
1338 g_slist_free(newsgroup_list);
1339 g_free(savecopyfolder);
1340 g_free(replymessageid);
1341 g_free(fwdmessageid);
1342 g_free(privacy_system);
1343 g_free(encrypt_data);
1345 return (newsval != 0 ? newsval : mailval);
1348 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1350 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1353 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1357 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
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++;
1368 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1369 item->unread_msgs--;
1370 if (procmsg_msg_has_marked_parent(msginfo))
1371 item->unreadmarked_msgs--;
1375 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1376 procmsg_update_unread_children(msginfo, TRUE);
1379 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1380 procmsg_update_unread_children(msginfo, FALSE);
1384 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1387 MsgInfoUpdate msginfo_update;
1388 MsgPermFlags perm_flags_new, perm_flags_old;
1389 MsgTmpFlags tmp_flags_old;
1391 g_return_if_fail(msginfo != NULL);
1392 item = msginfo->folder;
1393 g_return_if_fail(item != NULL);
1395 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1397 /* Perm Flags handling */
1398 perm_flags_old = msginfo->flags.perm_flags;
1399 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1400 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1401 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1404 if (perm_flags_old != perm_flags_new) {
1405 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1407 update_folder_msg_counts(item, msginfo, perm_flags_old);
1411 /* Tmp flags handling */
1412 tmp_flags_old = msginfo->flags.tmp_flags;
1413 msginfo->flags.tmp_flags |= tmp_flags;
1415 /* update notification */
1416 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1417 msginfo_update.msginfo = msginfo;
1418 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1419 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1420 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1424 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1427 MsgInfoUpdate msginfo_update;
1428 MsgPermFlags perm_flags_new, perm_flags_old;
1429 MsgTmpFlags tmp_flags_old;
1431 g_return_if_fail(msginfo != NULL);
1432 item = msginfo->folder;
1433 g_return_if_fail(item != NULL);
1435 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1437 /* Perm Flags handling */
1438 perm_flags_old = msginfo->flags.perm_flags;
1439 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1441 if (perm_flags_old != perm_flags_new) {
1442 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1444 update_folder_msg_counts(item, msginfo, perm_flags_old);
1446 msginfo_update.msginfo = msginfo;
1447 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1448 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1449 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1452 /* Tmp flags hanlding */
1453 tmp_flags_old = msginfo->flags.tmp_flags;
1454 msginfo->flags.tmp_flags &= ~tmp_flags;
1456 /* update notification */
1457 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1458 msginfo_update.msginfo = msginfo;
1459 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1460 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1461 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1466 *\brief check for flags (e.g. mark) in prior msgs of current thread
1468 *\param info Current message
1469 *\param perm_flags Flags to be checked
1470 *\param parentmsgs Hash of prior msgs to avoid loops
1472 *\return gboolean TRUE if perm_flags are found
1474 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1475 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1479 g_return_val_if_fail(info != NULL, FALSE);
1481 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1482 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1484 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1485 procmsg_msginfo_free(tmp);
1487 } else if (tmp != NULL) {
1490 if (g_hash_table_lookup(parentmsgs, info)) {
1491 debug_print("loop detected: %s%c%d\n",
1492 folder_item_get_path(info->folder),
1493 G_DIR_SEPARATOR, info->msgnum);
1496 g_hash_table_insert(parentmsgs, info, "1");
1497 result = procmsg_msg_has_flagged_parent_real(
1498 tmp, perm_flags, parentmsgs);
1500 procmsg_msginfo_free(tmp);
1510 *\brief Callback for cleaning up hash of parentmsgs
1512 gboolean parentmsgs_hash_remove(gpointer key,
1520 *\brief Set up list of parentmsgs
1521 * See procmsg_msg_has_flagged_parent_real()
1523 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1526 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1528 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1529 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1530 g_hash_table_destroy(parentmsgs);
1535 *\brief Check if msgs prior in thread are marked
1536 * See procmsg_msg_has_flagged_parent_real()
1538 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1540 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1544 GSList *procmsg_find_children_func(MsgInfo *info,
1545 GSList *children, GSList *all)
1549 g_return_val_if_fail(info!=NULL, children);
1550 if (info->msgid == NULL)
1553 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1554 MsgInfo *tmp = (MsgInfo *)cur->data;
1555 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1556 /* Check if message is already in the list */
1557 if ((children == NULL) ||
1558 (g_slist_index(children, tmp) == -1)) {
1559 children = g_slist_prepend(children,
1560 procmsg_msginfo_new_ref(tmp));
1561 children = procmsg_find_children_func(tmp,
1570 GSList *procmsg_find_children (MsgInfo *info)
1575 g_return_val_if_fail(info!=NULL, NULL);
1576 all = folder_item_get_msg_list(info->folder);
1577 children = procmsg_find_children_func(info, NULL, all);
1578 if (children != NULL) {
1579 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1580 /* this will not free the used pointers
1581 created with procmsg_msginfo_new_ref */
1582 procmsg_msginfo_free((MsgInfo *)cur->data);
1590 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1592 GSList *children = procmsg_find_children(info);
1594 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1595 MsgInfo *tmp = (MsgInfo *)cur->data;
1596 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1598 info->folder->unreadmarked_msgs++;
1600 info->folder->unreadmarked_msgs--;
1601 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1603 procmsg_msginfo_free(tmp);
1605 g_slist_free(children);
1609 * Set the destination folder for a copy or move operation
1611 * \param msginfo The message which's destination folder is changed
1612 * \param to_folder The destination folder for the operation
1614 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1616 if(msginfo->to_folder != NULL) {
1617 msginfo->to_folder->op_count--;
1618 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1620 msginfo->to_folder = to_folder;
1621 if(to_folder != NULL) {
1622 to_folder->op_count++;
1623 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1628 * Apply filtering actions to the msginfo
1630 * \param msginfo The MsgInfo describing the message that should be filtered
1631 * \return TRUE if the message was moved and MsgInfo is now invalid,
1634 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1636 MailFilteringData mail_filtering_data;
1638 mail_filtering_data.msginfo = msginfo;
1639 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1642 /* filter if enabled in prefs or move to inbox if not */
1643 if((filtering_rules != NULL) &&
1644 filter_message_by_msginfo(filtering_rules, msginfo))
1650 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1652 MsgInfo *tmp_msginfo = NULL;
1653 MsgFlags flags = {0, 0};
1656 if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1657 g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1658 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1662 if (mimeinfo->content == MIMECONTENT_MEM) {
1663 gchar *tmpfile = get_tmp_file();
1664 str_write_to_file(mimeinfo->data.mem, tmpfile);
1665 g_free(mimeinfo->data.mem);
1666 mimeinfo->content == MIMECONTENT_FILE;
1667 mimeinfo->data.filename = g_strdup(tmpfile);
1671 tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1672 flags, TRUE, FALSE);
1674 if (tmp_msginfo != NULL) {
1675 tmp_msginfo->folder = src_msginfo->folder;
1676 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1678 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");