2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2005 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;
228 root = g_node_new(NULL);
229 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
230 subject_relation = g_relation_new(2);
231 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
233 for (; mlist != NULL; mlist = mlist->next) {
234 msginfo = (MsgInfo *)mlist->data;
237 if (msginfo->inreplyto) {
238 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
239 if (parent == NULL) {
243 node = g_node_insert_data_before
244 (parent, parent == root ? parent->children : NULL,
246 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
247 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
249 /* CLAWS: add subject to relation (without prefix) */
250 if (prefs_common.thread_by_subject) {
251 subject_relation_insert(subject_relation, node);
255 /* complete the unfinished threads */
256 for (node = root->children; node != NULL; ) {
258 msginfo = (MsgInfo *)node->data;
261 if (msginfo->inreplyto)
262 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
264 /* try looking for the indirect parent */
265 if (!parent && msginfo->references) {
266 for (reflist = msginfo->references;
267 reflist != NULL; reflist = reflist->next)
268 if ((parent = g_hash_table_lookup
269 (msgid_table, reflist->data)) != NULL)
273 /* node should not be the parent, and node should not
274 be an ancestor of parent (circular reference) */
275 if (parent && parent != node &&
276 !g_node_is_ancestor(node, parent)) {
279 (parent, parent->children, node);
285 if (prefs_common.thread_by_subject) {
286 for (node = root->children; node && node != NULL;) {
288 msginfo = (MsgInfo *) node->data;
290 parent = subject_relation_lookup(subject_relation, msginfo);
292 /* the node may already be threaded by IN-REPLY-TO, so go up
294 find the parent node */
295 if (parent != NULL) {
296 if (g_node_is_ancestor(node, parent))
304 g_node_append(parent, node);
311 g_relation_destroy(subject_relation);
312 g_hash_table_destroy(msgid_table);
317 void procmsg_move_messages(GSList *mlist)
319 GSList *cur, *movelist = NULL;
321 FolderItem *dest = NULL;
325 folder_item_update_freeze();
327 for (cur = mlist; cur != NULL; cur = cur->next) {
328 msginfo = (MsgInfo *)cur->data;
330 dest = msginfo->to_folder;
331 movelist = g_slist_append(movelist, msginfo);
332 } else if (dest == msginfo->to_folder) {
333 movelist = g_slist_append(movelist, msginfo);
335 folder_item_move_msgs(dest, movelist);
336 g_slist_free(movelist);
338 dest = msginfo->to_folder;
339 movelist = g_slist_append(movelist, msginfo);
341 procmsg_msginfo_set_to_folder(msginfo, NULL);
345 folder_item_move_msgs(dest, movelist);
346 g_slist_free(movelist);
349 folder_item_update_thaw();
352 void procmsg_copy_messages(GSList *mlist)
354 GSList *cur, *copylist = NULL;
356 FolderItem *dest = NULL;
360 folder_item_update_freeze();
362 for (cur = mlist; cur != NULL; cur = cur->next) {
363 msginfo = (MsgInfo *)cur->data;
365 dest = msginfo->to_folder;
366 copylist = g_slist_append(copylist, msginfo);
367 } else if (dest == msginfo->to_folder) {
368 copylist = g_slist_append(copylist, msginfo);
370 folder_item_copy_msgs(dest, copylist);
371 g_slist_free(copylist);
373 dest = msginfo->to_folder;
374 copylist = g_slist_append(copylist, msginfo);
376 procmsg_msginfo_set_to_folder(msginfo, NULL);
380 folder_item_copy_msgs(dest, copylist);
381 g_slist_free(copylist);
384 folder_item_update_thaw();
387 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
391 g_return_val_if_fail(msginfo != NULL, NULL);
393 if (msginfo->plaintext_file)
394 file = g_strdup(msginfo->plaintext_file);
396 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
402 gchar *procmsg_get_message_file(MsgInfo *msginfo)
404 gchar *filename = NULL;
406 g_return_val_if_fail(msginfo != NULL, NULL);
408 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
410 debug_print("can't fetch message %d\n", msginfo->msgnum);
415 GSList *procmsg_get_message_file_list(GSList *mlist)
417 GSList *file_list = NULL;
419 MsgFileInfo *fileinfo;
422 while (mlist != NULL) {
423 msginfo = (MsgInfo *)mlist->data;
424 file = procmsg_get_message_file(msginfo);
426 procmsg_message_file_list_free(file_list);
429 fileinfo = g_new(MsgFileInfo, 1);
430 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
431 fileinfo->file = file;
432 fileinfo->flags = g_new(MsgFlags, 1);
433 *fileinfo->flags = msginfo->flags;
434 file_list = g_slist_prepend(file_list, fileinfo);
438 file_list = g_slist_reverse(file_list);
443 void procmsg_message_file_list_free(MsgInfoList *file_list)
446 MsgFileInfo *fileinfo;
448 for (cur = file_list; cur != NULL; cur = cur->next) {
449 fileinfo = (MsgFileInfo *)cur->data;
450 procmsg_msginfo_free(fileinfo->msginfo);
451 g_free(fileinfo->file);
452 g_free(fileinfo->flags);
456 g_slist_free(file_list);
459 FILE *procmsg_open_message(MsgInfo *msginfo)
464 g_return_val_if_fail(msginfo != NULL, NULL);
466 file = procmsg_get_message_file_path(msginfo);
467 g_return_val_if_fail(file != NULL, NULL);
469 if (!is_file_exist(file)) {
471 file = procmsg_get_message_file(msginfo);
476 if ((fp = fopen(file, "rb")) == NULL) {
477 FILE_OP_ERROR(file, "fopen");
484 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
487 while (fgets(buf, sizeof(buf), fp) != NULL)
488 if (buf[0] == '\r' || buf[0] == '\n') break;
494 gboolean procmsg_msg_exist(MsgInfo *msginfo)
499 if (!msginfo) return FALSE;
501 path = folder_item_get_path(msginfo->folder);
503 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
509 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
510 PrefsFilterType type)
512 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
513 {"X-ML-Name:", NULL, TRUE},
514 {"X-List:", NULL, TRUE},
515 {"X-Mailing-list:", NULL, TRUE},
516 {"List-Id:", NULL, TRUE},
517 {"X-Sequence:", NULL, TRUE},
518 {NULL, NULL, FALSE}};
524 H_X_MAILING_LIST = 3,
531 g_return_if_fail(msginfo != NULL);
532 g_return_if_fail(header != NULL);
533 g_return_if_fail(key != NULL);
542 if ((fp = procmsg_open_message(msginfo)) == NULL)
544 procheader_get_header_fields(fp, hentry);
547 #define SET_FILTER_KEY(hstr, idx) \
549 *header = g_strdup(hstr); \
550 *key = hentry[idx].body; \
551 hentry[idx].body = NULL; \
554 if (hentry[H_X_BEENTHERE].body != NULL) {
555 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
556 } else if (hentry[H_X_ML_NAME].body != NULL) {
557 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
558 } else if (hentry[H_X_LIST].body != NULL) {
559 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
560 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
561 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
562 } else if (hentry[H_LIST_ID].body != NULL) {
563 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
564 extract_list_id_str(*key);
565 } else if (hentry[H_X_SEQUENCE].body != NULL) {
568 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
571 while (*p != '\0' && !isspace(*p)) p++;
572 while (isspace(*p)) p++;
579 } else if (msginfo->subject) {
580 *header = g_strdup("subject");
581 *key = g_strdup(msginfo->subject);
584 #undef SET_FILTER_KEY
586 g_free(hentry[H_X_BEENTHERE].body);
587 hentry[H_X_BEENTHERE].body = NULL;
588 g_free(hentry[H_X_ML_NAME].body);
589 hentry[H_X_ML_NAME].body = NULL;
590 g_free(hentry[H_X_LIST].body);
591 hentry[H_X_LIST].body = NULL;
592 g_free(hentry[H_X_MAILING_LIST].body);
593 hentry[H_X_MAILING_LIST].body = NULL;
594 g_free(hentry[H_LIST_ID].body);
595 hentry[H_LIST_ID].body = NULL;
599 *header = g_strdup("from");
600 *key = g_strdup(msginfo->from);
603 *header = g_strdup("to");
604 *key = g_strdup(msginfo->to);
606 case FILTER_BY_SUBJECT:
607 *header = g_strdup("subject");
608 *key = g_strdup(msginfo->subject);
615 void procmsg_empty_trash(FolderItem *trash)
617 if (trash && trash->total_msgs > 0) {
618 GSList *mlist = folder_item_get_msg_list(trash);
620 for (cur = mlist ; cur != NULL ; cur = cur->next) {
621 MsgInfo * msginfo = (MsgInfo *) cur->data;
622 partial_mark_for_delete(msginfo);
623 procmsg_msginfo_free(msginfo);
626 folder_item_remove_all_msg(trash);
630 void procmsg_empty_all_trash(void)
635 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
636 trash = FOLDER(cur->data)->trash;
637 procmsg_empty_trash(trash);
642 *\brief Send messages in queue
644 *\param queue Queue folder to process
645 *\param save_msgs Unused
647 *\return Number of messages sent, negative if an error occurred
648 * positive if no error occurred
650 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
652 gint sent = 0, err = 0;
656 queue = folder_get_default_queue();
657 g_return_val_if_fail(queue != NULL, -1);
659 folder_item_scan(queue);
660 list = folder_item_get_msg_list(queue);
662 for (elem = list; elem != NULL; elem = elem->next) {
666 msginfo = (MsgInfo *)(elem->data);
667 if (!MSG_IS_LOCKED(msginfo->flags)) {
668 file = folder_item_fetch_msg(queue, msginfo->msgnum);
670 if (procmsg_send_message_queue(file) < 0) {
671 g_warning("Sending queued message %d failed.\n",
676 * We save in procmsg_send_message_queue because
677 * we need the destination folder from the queue
681 procmsg_save_to_outbox
682 (queue->folder->outbox,
686 folder_item_remove_msg(queue, msginfo->msgnum);
691 /* FIXME: supposedly if only one message is locked, and queue
692 * is being flushed, the following free says something like
693 * "freeing msg ## in folder (nil)". */
694 procmsg_msginfo_free(msginfo);
697 return (err != 0 ? -err : sent);
700 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
705 if ((fp = fopen(in, "rb")) == NULL) {
706 FILE_OP_ERROR(in, "fopen");
709 if ((outfp = fopen(out, "wb")) == NULL) {
710 FILE_OP_ERROR(out, "fopen");
714 while (fgets(buf, sizeof(buf), fp) != NULL)
715 if (buf[0] == '\r' || buf[0] == '\n') break;
716 while (fgets(buf, sizeof(buf), fp) != NULL)
723 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
727 MsgInfo *msginfo, *tmp_msginfo;
728 MsgFlags flag = {0, 0};
730 debug_print("saving sent message...\n");
733 outbox = folder_get_default_outbox();
734 g_return_val_if_fail(outbox != NULL, -1);
736 /* remove queueing headers */
738 gchar tmp[MAXPATHLEN + 1];
740 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
741 get_rc_dir(), G_DIR_SEPARATOR, (guint) rand());
743 if (procmsg_remove_special_headers(file, tmp) !=0)
746 folder_item_scan(outbox);
747 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
748 g_warning("can't save message\n");
753 folder_item_scan(outbox);
754 if ((num = folder_item_add_msg
755 (outbox, file, &flag, FALSE)) < 0) {
756 g_warning("can't save message\n");
761 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
762 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
763 if (msginfo != NULL) {
764 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
765 procmsg_msginfo_free(msginfo); /* refcnt-- */
766 /* tmp_msginfo == msginfo */
767 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
768 msginfo->returnreceiptto)) {
769 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
770 procmsg_msginfo_free(msginfo); /* refcnt-- */
777 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
779 static const gchar *def_cmd = "lpr %s";
786 g_return_if_fail(msginfo);
788 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
789 g_warning("Can't get text part\n");
793 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
794 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
796 if ((prfp = fopen(prtmp, "wb")) == NULL) {
797 FILE_OP_ERROR(prtmp, "fopen");
803 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
804 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
805 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
806 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
807 if (msginfo->newsgroups)
808 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
809 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
812 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
818 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
820 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
823 g_warning("Print command line is invalid: `%s'\n",
825 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
831 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
835 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
842 MsgInfo *procmsg_msginfo_new(void)
846 newmsginfo = g_new0(MsgInfo, 1);
847 newmsginfo->refcnt = 1;
852 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
857 if (msginfo == NULL) return NULL;
859 newmsginfo = g_new0(MsgInfo, 1);
861 newmsginfo->refcnt = 1;
863 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
864 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
865 g_strdup(msginfo->mmb) : NULL
890 MEMBDUP(dispositionnotificationto);
891 MEMBDUP(returnreceiptto);
893 refs = msginfo->references;
894 for (refs = msginfo->references; refs != NULL; refs = refs->next) {
895 newmsginfo->references = g_slist_prepend
896 (newmsginfo->references, g_strdup(refs->data));
898 newmsginfo->references = g_slist_reverse(newmsginfo->references);
901 MEMBCOPY(threadscore);
902 MEMBDUP(plaintext_file);
907 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
909 MsgInfo *full_msginfo;
912 if (msginfo == NULL) return NULL;
914 file = procmsg_get_message_file_path(msginfo);
915 if (!file || !is_file_exist(file)) {
917 file = procmsg_get_message_file(msginfo);
919 if (!file || !is_file_exist(file)) {
920 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
924 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
926 if (!full_msginfo) return NULL;
928 /* CLAWS: make sure we add the missing members; see:
929 * procheader.c::procheader_get_headernames() */
931 msginfo->xface = g_strdup(full_msginfo->xface);
932 if (!msginfo->dispositionnotificationto)
933 msginfo->dispositionnotificationto =
934 g_strdup(full_msginfo->dispositionnotificationto);
935 if (!msginfo->returnreceiptto)
936 msginfo->returnreceiptto = g_strdup
937 (full_msginfo->returnreceiptto);
938 if (!msginfo->partial_recv && full_msginfo->partial_recv)
939 msginfo->partial_recv = g_strdup
940 (full_msginfo->partial_recv);
941 msginfo->total_size = full_msginfo->total_size;
942 if (!msginfo->account_server && full_msginfo->account_server)
943 msginfo->account_server = g_strdup
944 (full_msginfo->account_server);
945 if (!msginfo->account_login && full_msginfo->account_login)
946 msginfo->account_login = g_strdup
947 (full_msginfo->account_login);
948 msginfo->planned_download = full_msginfo->planned_download;
949 procmsg_msginfo_free(full_msginfo);
951 return procmsg_msginfo_new_ref(msginfo);
954 void procmsg_msginfo_free(MsgInfo *msginfo)
956 if (msginfo == NULL) return;
959 if (msginfo->refcnt > 0)
962 if (msginfo->to_folder) {
963 msginfo->to_folder->op_count--;
964 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
967 g_free(msginfo->fromspace);
968 g_free(msginfo->returnreceiptto);
969 g_free(msginfo->dispositionnotificationto);
970 g_free(msginfo->xface);
972 g_free(msginfo->fromname);
974 g_free(msginfo->date);
975 g_free(msginfo->from);
978 g_free(msginfo->newsgroups);
979 g_free(msginfo->subject);
980 g_free(msginfo->msgid);
981 g_free(msginfo->inreplyto);
982 g_free(msginfo->xref);
984 g_free(msginfo->partial_recv);
985 g_free(msginfo->account_server);
986 g_free(msginfo->account_login);
988 slist_free_strings(msginfo->references);
989 g_slist_free(msginfo->references);
991 g_free(msginfo->plaintext_file);
996 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1000 memusage += sizeof(MsgInfo);
1001 if (msginfo->fromname)
1002 memusage += strlen(msginfo->fromname);
1004 memusage += strlen(msginfo->date);
1006 memusage += strlen(msginfo->from);
1008 memusage += strlen(msginfo->to);
1010 memusage += strlen(msginfo->cc);
1011 if (msginfo->newsgroups)
1012 memusage += strlen(msginfo->newsgroups);
1013 if (msginfo->subject)
1014 memusage += strlen(msginfo->subject);
1016 memusage += strlen(msginfo->msgid);
1017 if (msginfo->inreplyto)
1018 memusage += strlen(msginfo->inreplyto);
1020 memusage += strlen(msginfo->xface);
1021 if (msginfo->dispositionnotificationto)
1022 memusage += strlen(msginfo->dispositionnotificationto);
1023 if (msginfo->returnreceiptto)
1024 memusage += strlen(msginfo->returnreceiptto);
1025 #warning FIXME: Calculate size of references list
1027 if (msginfo->references)
1028 memusage += strlen(msginfo->references);
1030 if (msginfo->fromspace)
1031 memusage += strlen(msginfo->fromspace);
1036 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1038 const MsgInfo *msginfo1 = a;
1039 const MsgInfo *msginfo2 = b;
1046 return msginfo1->msgnum - msginfo2->msgnum;
1055 Q_MAIL_ACCOUNT_ID = 4,
1056 Q_NEWS_ACCOUNT_ID = 5,
1057 Q_SAVE_COPY_FOLDER = 6,
1058 Q_REPLY_MESSAGE_ID = 7,
1059 Q_FWD_MESSAGE_ID = 8,
1060 Q_PRIVACY_SYSTEM = 9,
1062 Q_ENCRYPT_DATA = 11,
1065 gint procmsg_send_message_queue(const gchar *file)
1067 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1068 {"SSV:", NULL, FALSE},
1069 {"R:", NULL, FALSE},
1070 {"NG:", NULL, FALSE},
1071 {"MAID:", NULL, FALSE},
1072 {"NAID:", NULL, FALSE},
1073 {"SCF:", NULL, FALSE},
1074 {"RMID:", NULL, FALSE},
1075 {"FMID:", NULL, FALSE},
1076 {"X-Sylpheed-Privacy-System:", NULL, FALSE},
1077 {"X-Sylpheed-Encrypt:", NULL, FALSE},
1078 {"X-Sylpheed-Encrypt-Data:", NULL, FALSE},
1079 {NULL, NULL, FALSE}};
1082 gint mailval = 0, newsval = 0;
1084 gchar *smtpserver = NULL;
1085 GSList *to_list = NULL;
1086 GSList *newsgroup_list = NULL;
1087 gchar *savecopyfolder = NULL;
1088 gchar *replymessageid = NULL;
1089 gchar *fwdmessageid = NULL;
1090 gchar *privacy_system = NULL;
1091 gboolean encrypt = FALSE;
1092 gchar *encrypt_data = NULL;
1093 gchar buf[BUFFSIZE];
1095 PrefsAccount *mailac = NULL, *newsac = NULL;
1096 gboolean save_clear_text = TRUE;
1097 gchar *tmp_enc_file = NULL;
1101 g_return_val_if_fail(file != NULL, -1);
1103 if ((fp = fopen(file, "rb")) == NULL) {
1104 FILE_OP_ERROR(file, "fopen");
1108 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1110 gchar *p = buf + strlen(qentry[hnum].name);
1118 if (smtpserver == NULL)
1119 smtpserver = g_strdup(p);
1122 to_list = address_list_append(to_list, p);
1125 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1127 case Q_MAIL_ACCOUNT_ID:
1128 mailac = account_find_from_id(atoi(p));
1130 case Q_NEWS_ACCOUNT_ID:
1131 newsac = account_find_from_id(atoi(p));
1133 case Q_SAVE_COPY_FOLDER:
1134 if (savecopyfolder == NULL)
1135 savecopyfolder = g_strdup(p);
1137 case Q_REPLY_MESSAGE_ID:
1138 if (replymessageid == NULL)
1139 replymessageid = g_strdup(p);
1141 case Q_FWD_MESSAGE_ID:
1142 if (fwdmessageid == NULL)
1143 fwdmessageid = g_strdup(p);
1145 case Q_PRIVACY_SYSTEM:
1146 if (privacy_system == NULL)
1147 privacy_system = g_strdup(p);
1153 case Q_ENCRYPT_DATA:
1154 if (encrypt_data == NULL)
1155 encrypt_data = g_strdup(p);
1159 filepos = ftell(fp);
1164 save_clear_text = (mailac != NULL && mailac->save_encrypted_as_clear_text);
1169 mimeinfo = procmime_scan_queue_file(file);
1170 if (!privacy_encrypt(privacy_system, mimeinfo, encrypt_data)
1171 || (fp = my_tmpfile()) == NULL
1172 || procmime_write_mimeinfo(mimeinfo, fp) < 0) {
1175 procmime_mimeinfo_free_all(mimeinfo);
1178 slist_free_strings(to_list);
1179 g_slist_free(to_list);
1180 slist_free_strings(newsgroup_list);
1181 g_slist_free(newsgroup_list);
1182 g_free(savecopyfolder);
1183 g_free(replymessageid);
1184 g_free(fwdmessageid);
1185 g_free(privacy_system);
1186 g_free(encrypt_data);
1191 if (!save_clear_text) {
1192 gchar *content = NULL;
1193 FILE *tmpfp = get_tmpfile_in_dir(get_mime_tmp_dir(), &tmp_enc_file);
1197 content = file_read_stream_to_str(fp);
1200 str_write_to_file(content, tmp_enc_file);
1203 g_warning("couldn't get tempfile\n");
1207 procmime_mimeinfo_free_all(mimeinfo);
1213 debug_print("Sending message by mail\n");
1215 g_warning("Queued message header is broken.\n");
1217 } else if (mailac && mailac->use_mail_command &&
1218 mailac->mail_command && (* mailac->mail_command)) {
1219 mailval = send_message_local(mailac->mail_command, fp);
1223 mailac = account_find_from_smtp_server(from, smtpserver);
1225 g_warning("Account not found. "
1226 "Using current account...\n");
1227 mailac = cur_account;
1232 mailval = send_message_smtp(mailac, to_list, fp);
1234 PrefsAccount tmp_ac;
1236 g_warning("Account not found.\n");
1238 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1239 tmp_ac.address = from;
1240 tmp_ac.smtp_server = smtpserver;
1241 tmp_ac.smtpport = SMTP_PORT;
1242 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1247 fseek(fp, filepos, SEEK_SET);
1248 if (newsgroup_list && (mailval == 0)) {
1253 /* write to temporary file */
1254 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1255 G_DIR_SEPARATOR, (gint)file);
1256 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1257 FILE_OP_ERROR(tmp, "fopen");
1259 alertpanel_error(_("Could not create temporary file for news sending."));
1261 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1262 FILE_OP_ERROR(tmp, "chmod");
1263 g_warning("can't change file mode\n");
1266 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1267 if (fputs(buf, tmpfp) == EOF) {
1268 FILE_OP_ERROR(tmp, "fputs");
1270 alertpanel_error(_("Error when writing temporary file for news sending."));
1276 debug_print("Sending message by news\n");
1278 folder = FOLDER(newsac->folder);
1280 newsval = news_post(folder, tmp);
1282 alertpanel_error(_("Error occurred while posting the message to %s ."),
1283 newsac->nntp_server);
1293 /* save message to outbox */
1294 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1297 debug_print("saving sent message...\n");
1299 outbox = folder_find_item_from_identifier(savecopyfolder);
1301 outbox = folder_get_default_outbox();
1303 if (save_clear_text || tmp_enc_file == NULL) {
1304 procmsg_save_to_outbox(outbox, file, TRUE);
1306 procmsg_save_to_outbox(outbox, tmp_enc_file, FALSE);
1310 if (tmp_enc_file != NULL) {
1311 unlink(tmp_enc_file);
1313 tmp_enc_file = NULL;
1316 if (replymessageid != NULL || fwdmessageid != NULL) {
1320 if (replymessageid != NULL)
1321 tokens = g_strsplit(replymessageid, "\x7f", 0);
1323 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1324 item = folder_find_item_from_identifier(tokens[0]);
1326 /* check if queued message has valid folder and message id */
1327 if (item != NULL && tokens[2] != NULL) {
1330 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1332 /* check if referring message exists and has a message id */
1333 if ((msginfo != NULL) &&
1334 (msginfo->msgid != NULL) &&
1335 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1336 procmsg_msginfo_free(msginfo);
1340 if (msginfo == NULL) {
1341 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1344 if (msginfo != NULL) {
1345 if (replymessageid != NULL) {
1346 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1347 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1349 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1350 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1352 procmsg_msginfo_free(msginfo);
1360 slist_free_strings(to_list);
1361 g_slist_free(to_list);
1362 slist_free_strings(newsgroup_list);
1363 g_slist_free(newsgroup_list);
1364 g_free(savecopyfolder);
1365 g_free(replymessageid);
1366 g_free(fwdmessageid);
1367 g_free(privacy_system);
1368 g_free(encrypt_data);
1370 return (newsval != 0 ? newsval : mailval);
1373 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1375 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1378 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1382 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1387 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1388 item->unread_msgs++;
1389 if (procmsg_msg_has_marked_parent(msginfo))
1390 item->unreadmarked_msgs++;
1393 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1394 item->unread_msgs--;
1395 if (procmsg_msg_has_marked_parent(msginfo))
1396 item->unreadmarked_msgs--;
1400 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1401 procmsg_update_unread_children(msginfo, TRUE);
1404 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1405 procmsg_update_unread_children(msginfo, FALSE);
1409 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1412 MsgInfoUpdate msginfo_update;
1413 MsgPermFlags perm_flags_new, perm_flags_old;
1414 MsgTmpFlags tmp_flags_old;
1416 g_return_if_fail(msginfo != NULL);
1417 item = msginfo->folder;
1418 g_return_if_fail(item != NULL);
1420 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1422 /* Perm Flags handling */
1423 perm_flags_old = msginfo->flags.perm_flags;
1424 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1425 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1426 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1429 if (perm_flags_old != perm_flags_new) {
1430 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1432 update_folder_msg_counts(item, msginfo, perm_flags_old);
1436 /* Tmp flags handling */
1437 tmp_flags_old = msginfo->flags.tmp_flags;
1438 msginfo->flags.tmp_flags |= tmp_flags;
1440 /* update notification */
1441 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1442 msginfo_update.msginfo = msginfo;
1443 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1444 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1445 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1449 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1452 MsgInfoUpdate msginfo_update;
1453 MsgPermFlags perm_flags_new, perm_flags_old;
1454 MsgTmpFlags tmp_flags_old;
1456 g_return_if_fail(msginfo != NULL);
1457 item = msginfo->folder;
1458 g_return_if_fail(item != NULL);
1460 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1462 /* Perm Flags handling */
1463 perm_flags_old = msginfo->flags.perm_flags;
1464 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1466 if (perm_flags_old != perm_flags_new) {
1467 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1469 update_folder_msg_counts(item, msginfo, perm_flags_old);
1471 msginfo_update.msginfo = msginfo;
1472 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1473 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1474 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1477 /* Tmp flags hanlding */
1478 tmp_flags_old = msginfo->flags.tmp_flags;
1479 msginfo->flags.tmp_flags &= ~tmp_flags;
1481 /* update notification */
1482 if ((perm_flags_old != perm_flags_new) || (tmp_flags_old != msginfo->flags.tmp_flags)) {
1483 msginfo_update.msginfo = msginfo;
1484 msginfo_update.flags = MSGINFO_UPDATE_FLAGS;
1485 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1486 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1491 *\brief check for flags (e.g. mark) in prior msgs of current thread
1493 *\param info Current message
1494 *\param perm_flags Flags to be checked
1495 *\param parentmsgs Hash of prior msgs to avoid loops
1497 *\return gboolean TRUE if perm_flags are found
1499 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1500 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1504 g_return_val_if_fail(info != NULL, FALSE);
1506 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1507 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1509 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1510 procmsg_msginfo_free(tmp);
1512 } else if (tmp != NULL) {
1515 if (g_hash_table_lookup(parentmsgs, info)) {
1516 debug_print("loop detected: %s%c%d\n",
1517 folder_item_get_path(info->folder),
1518 G_DIR_SEPARATOR, info->msgnum);
1521 g_hash_table_insert(parentmsgs, info, "1");
1522 result = procmsg_msg_has_flagged_parent_real(
1523 tmp, perm_flags, parentmsgs);
1525 procmsg_msginfo_free(tmp);
1535 *\brief Callback for cleaning up hash of parentmsgs
1537 gboolean parentmsgs_hash_remove(gpointer key,
1545 *\brief Set up list of parentmsgs
1546 * See procmsg_msg_has_flagged_parent_real()
1548 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1551 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1553 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1554 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1555 g_hash_table_destroy(parentmsgs);
1560 *\brief Check if msgs prior in thread are marked
1561 * See procmsg_msg_has_flagged_parent_real()
1563 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1565 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1569 GSList *procmsg_find_children_func(MsgInfo *info,
1570 GSList *children, GSList *all)
1574 g_return_val_if_fail(info!=NULL, children);
1575 if (info->msgid == NULL)
1578 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1579 MsgInfo *tmp = (MsgInfo *)cur->data;
1580 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1581 /* Check if message is already in the list */
1582 if ((children == NULL) ||
1583 (g_slist_index(children, tmp) == -1)) {
1584 children = g_slist_prepend(children,
1585 procmsg_msginfo_new_ref(tmp));
1586 children = procmsg_find_children_func(tmp,
1595 GSList *procmsg_find_children (MsgInfo *info)
1600 g_return_val_if_fail(info!=NULL, NULL);
1601 all = folder_item_get_msg_list(info->folder);
1602 children = procmsg_find_children_func(info, NULL, all);
1603 if (children != NULL) {
1604 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1605 /* this will not free the used pointers
1606 created with procmsg_msginfo_new_ref */
1607 procmsg_msginfo_free((MsgInfo *)cur->data);
1615 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1617 GSList *children = procmsg_find_children(info);
1619 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1620 MsgInfo *tmp = (MsgInfo *)cur->data;
1621 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1623 info->folder->unreadmarked_msgs++;
1625 info->folder->unreadmarked_msgs--;
1626 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1628 procmsg_msginfo_free(tmp);
1630 g_slist_free(children);
1634 * Set the destination folder for a copy or move operation
1636 * \param msginfo The message which's destination folder is changed
1637 * \param to_folder The destination folder for the operation
1639 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1641 if(msginfo->to_folder != NULL) {
1642 msginfo->to_folder->op_count--;
1643 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1645 msginfo->to_folder = to_folder;
1646 if(to_folder != NULL) {
1647 to_folder->op_count++;
1648 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1653 * Apply filtering actions to the msginfo
1655 * \param msginfo The MsgInfo describing the message that should be filtered
1656 * \return TRUE if the message was moved and MsgInfo is now invalid,
1659 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1661 MailFilteringData mail_filtering_data;
1663 mail_filtering_data.msginfo = msginfo;
1664 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1667 /* filter if enabled in prefs or move to inbox if not */
1668 if((filtering_rules != NULL) &&
1669 filter_message_by_msginfo(filtering_rules, msginfo))
1675 MsgInfo *procmsg_msginfo_new_from_mimeinfo(MsgInfo *src_msginfo, MimeInfo *mimeinfo)
1677 MsgInfo *tmp_msginfo = NULL;
1678 MsgFlags flags = {0, 0};
1681 if (!mimeinfo || mimeinfo->type != MIMETYPE_MESSAGE ||
1682 g_ascii_strcasecmp(mimeinfo->subtype, "rfc822")) {
1683 g_warning("procmsg_msginfo_new_from_mimeinfo(): unsuitable mimeinfo");
1687 if (mimeinfo->content == MIMECONTENT_MEM) {
1688 gchar *tmpfile = get_tmp_file();
1689 str_write_to_file(mimeinfo->data.mem, tmpfile);
1690 g_free(mimeinfo->data.mem);
1691 mimeinfo->content == MIMECONTENT_FILE;
1692 mimeinfo->data.filename = g_strdup(tmpfile);
1696 tmp_msginfo = procheader_parse_file(mimeinfo->data.filename,
1697 flags, TRUE, FALSE);
1699 if (tmp_msginfo != NULL) {
1700 tmp_msginfo->folder = src_msginfo->folder;
1701 tmp_msginfo->plaintext_file = g_strdup(mimeinfo->data.filename);
1703 g_warning("procmsg_msginfo_new_from_mimeinfo(): Can't generate new msginfo");