2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include "procheader.h"
32 #include "send_message.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
38 #include "prefs_common.h"
43 #include "alertpanel.h"
48 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
50 GHashTable *msg_table;
52 if (mlist == NULL) return NULL;
54 msg_table = g_hash_table_new(NULL, g_direct_equal);
55 procmsg_msg_hash_table_append(msg_table, mlist);
60 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
65 if (msg_table == NULL || mlist == NULL) return;
67 for (cur = mlist; cur != NULL; cur = cur->next) {
68 msginfo = (MsgInfo *)cur->data;
70 g_hash_table_insert(msg_table,
71 GUINT_TO_POINTER(msginfo->msgnum),
76 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
78 GHashTable *msg_table;
82 if (mlist == NULL) return NULL;
84 msg_table = g_hash_table_new(NULL, g_direct_equal);
86 for (cur = mlist; cur != NULL; cur = cur->next) {
87 msginfo = (MsgInfo *)cur->data;
88 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
94 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
100 for (cur = mlist; cur != NULL; cur = cur->next) {
101 msginfo = (MsgInfo *)cur->data;
102 if (msginfo && msginfo->msgnum > last)
103 last = msginfo->msgnum;
109 void procmsg_msg_list_free(GSList *mlist)
114 for (cur = mlist; cur != NULL; cur = cur->next) {
115 msginfo = (MsgInfo *)cur->data;
116 procmsg_msginfo_free(msginfo);
130 static gboolean procmsg_ignore_node(GNode *node, gpointer data)
132 MsgInfo *msginfo = (MsgInfo *)node->data;
134 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
135 procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
140 /* CLAWS subject threading:
142 in the first round it inserts subject lines in a
143 relation (subject <-> node)
145 the second round finishes the threads by attaching
146 matching subject lines to the one found in the
147 relation. will use the oldest node with the same
148 subject that is not more then thread_by_subject_max_age
149 days old (see subject_relation_lookup)
152 static void subject_relation_insert(GRelation *relation, GNode *node)
157 g_return_if_fail(relation != NULL);
158 g_return_if_fail(node != NULL);
159 msginfo = (MsgInfo *) node->data;
160 g_return_if_fail(msginfo != NULL);
162 subject = msginfo->subject;
165 subject += subject_get_prefix_length(subject);
167 g_relation_insert(relation, subject, node);
170 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
176 g_return_val_if_fail(relation != NULL, NULL);
178 subject = msginfo->subject;
181 subject += subject_get_prefix_length(subject);
183 tuples = g_relation_select(relation, subject, 0);
187 if (tuples->len > 0) {
189 GNode *relation_node;
190 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
193 /* check all nodes with the same subject to find the best parent */
194 for (i = 0; i < tuples->len; i++) {
195 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
196 relation_msginfo = (MsgInfo *) relation_node->data;
199 /* best node should be the oldest in the found nodes */
200 /* parent node must not be older then msginfo */
201 if ((relation_msginfo->date_t < msginfo->date_t) &&
202 ((best_msginfo == NULL) ||
203 (best_msginfo->date_t > relation_msginfo->date_t)))
206 /* parent node must not be more then thread_by_subject_max_age
207 days older then msginfo */
208 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
209 prefs_common.thread_by_subject_max_age * 3600 * 24)
212 /* can add new tests for all matching
213 nodes found by subject */
216 node = relation_node;
217 best_msginfo = relation_msginfo;
222 g_tuples_destroy(tuples);
226 /* return the reversed thread tree */
227 GNode *procmsg_get_thread_tree(GSList *mlist)
229 GNode *root, *parent, *node, *next, *last;
230 GNode *prev; /* CLAWS */
231 GHashTable *msgid_table;
232 GRelation *subject_relation;
235 const gchar *subject;
237 root = g_node_new(NULL);
238 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
239 subject_relation = g_relation_new(2);
240 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
242 for (; mlist != NULL; mlist = mlist->next) {
243 msginfo = (MsgInfo *)mlist->data;
246 if (msginfo->inreplyto) {
247 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
248 if (parent == NULL) {
252 node = g_node_insert_data_before
253 (parent, parent == root ? parent->children : NULL,
255 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
256 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
258 /* CLAWS: add subject to relation (without prefix) */
259 if (prefs_common.thread_by_subject) {
260 subject_relation_insert(subject_relation, node);
264 /* complete the unfinished threads */
265 for (node = root->children; node != NULL; ) {
266 prev = node->prev; /* CLAWS: need the last node */
269 msginfo = (MsgInfo *)node->data;
270 if (msginfo->inreplyto) {
271 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
272 /* node should not be the parent, and node should not
273 be an ancestor of parent (circular reference) */
274 if (parent && parent != node &&
275 !g_node_is_ancestor(node, parent)) {
278 (parent, parent->children, node);
281 last = (next == NULL) ? prev : node;
285 if (prefs_common.thread_by_subject) {
286 for (node = last; node && node != NULL;) {
288 msginfo = (MsgInfo *) node->data;
290 /* may not parentize if parent was delivered after childs */
291 if (subject != msginfo->subject)
292 parent = subject_relation_lookup(subject_relation, msginfo);
296 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
297 find the parent node */
298 if (parent != NULL) {
299 if (g_node_is_ancestor(node, parent))
307 g_node_append(parent, node);
314 g_relation_destroy(subject_relation);
315 g_hash_table_destroy(msgid_table);
320 void procmsg_move_messages(GSList *mlist)
322 GSList *cur, *movelist = NULL;
324 FolderItem *dest = NULL;
328 folder_item_update_freeze();
330 for (cur = mlist; cur != NULL; cur = cur->next) {
331 msginfo = (MsgInfo *)cur->data;
333 dest = msginfo->to_folder;
334 movelist = g_slist_append(movelist, msginfo);
335 } else if (dest == msginfo->to_folder) {
336 movelist = g_slist_append(movelist, msginfo);
338 folder_item_move_msgs(dest, movelist);
339 g_slist_free(movelist);
341 dest = msginfo->to_folder;
342 movelist = g_slist_append(movelist, msginfo);
344 procmsg_msginfo_set_to_folder(msginfo, NULL);
348 folder_item_move_msgs(dest, movelist);
349 g_slist_free(movelist);
352 folder_item_update_thaw();
355 void procmsg_copy_messages(GSList *mlist)
357 GSList *cur, *copylist = NULL;
359 FolderItem *dest = NULL;
363 folder_item_update_freeze();
365 for (cur = mlist; cur != NULL; cur = cur->next) {
366 msginfo = (MsgInfo *)cur->data;
368 dest = msginfo->to_folder;
369 copylist = g_slist_append(copylist, msginfo);
370 } else if (dest == msginfo->to_folder) {
371 copylist = g_slist_append(copylist, msginfo);
373 folder_item_copy_msgs(dest, copylist);
374 g_slist_free(copylist);
376 dest = msginfo->to_folder;
377 copylist = g_slist_append(copylist, msginfo);
379 procmsg_msginfo_set_to_folder(msginfo, NULL);
383 folder_item_copy_msgs(dest, copylist);
384 g_slist_free(copylist);
387 folder_item_update_thaw();
390 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
394 g_return_val_if_fail(msginfo != NULL, NULL);
396 if (msginfo->plaintext_file)
397 file = g_strdup(msginfo->plaintext_file);
399 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
405 gchar *procmsg_get_message_file(MsgInfo *msginfo)
407 gchar *filename = NULL;
409 g_return_val_if_fail(msginfo != NULL, NULL);
411 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
413 g_warning("can't fetch message %d\n", msginfo->msgnum);
418 GSList *procmsg_get_message_file_list(GSList *mlist)
420 GSList *file_list = NULL;
422 MsgFileInfo *fileinfo;
425 while (mlist != NULL) {
426 msginfo = (MsgInfo *)mlist->data;
427 file = procmsg_get_message_file(msginfo);
429 procmsg_message_file_list_free(file_list);
432 fileinfo = g_new(MsgFileInfo, 1);
433 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
434 fileinfo->file = file;
435 fileinfo->flags = g_new(MsgFlags, 1);
436 *fileinfo->flags = msginfo->flags;
437 file_list = g_slist_prepend(file_list, fileinfo);
441 file_list = g_slist_reverse(file_list);
446 void procmsg_message_file_list_free(MsgInfoList *file_list)
449 MsgFileInfo *fileinfo;
451 for (cur = file_list; cur != NULL; cur = cur->next) {
452 fileinfo = (MsgFileInfo *)cur->data;
453 procmsg_msginfo_free(fileinfo->msginfo);
454 g_free(fileinfo->file);
455 g_free(fileinfo->flags);
459 g_slist_free(file_list);
462 FILE *procmsg_open_message(MsgInfo *msginfo)
467 g_return_val_if_fail(msginfo != NULL, NULL);
469 file = procmsg_get_message_file_path(msginfo);
470 g_return_val_if_fail(file != NULL, NULL);
472 if (!is_file_exist(file)) {
474 file = procmsg_get_message_file(msginfo);
475 g_return_val_if_fail(file != NULL, NULL);
478 if ((fp = fopen(file, "rb")) == NULL) {
479 FILE_OP_ERROR(file, "fopen");
486 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
489 while (fgets(buf, sizeof(buf), fp) != NULL)
490 if (buf[0] == '\r' || buf[0] == '\n') break;
497 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
503 g_return_val_if_fail(msginfo != NULL, NULL);
505 if (mimeinfo) *mimeinfo = NULL;
507 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
509 mimeinfo_ = procmime_scan_mime_header(fp);
515 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
516 rfc2015_is_encrypted(mimeinfo_)) {
517 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
520 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
521 !msginfo->plaintext_file &&
522 !msginfo->decryption_failed) {
524 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
525 if (msginfo->plaintext_file &&
526 !msginfo->decryption_failed) {
528 procmime_mimeinfo_free_all(mimeinfo_);
529 if ((fp = procmsg_open_message(msginfo)) == NULL)
531 mimeinfo_ = procmime_scan_mime_header(fp);
537 if (fseek(fp, fpos, SEEK_SET) < 0)
542 if (mimeinfo) *mimeinfo = mimeinfo_;
547 gboolean procmsg_msg_exist(MsgInfo *msginfo)
552 if (!msginfo) return FALSE;
554 path = folder_item_get_path(msginfo->folder);
556 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
562 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
563 PrefsFilterType type)
565 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
566 {"X-ML-Name:", NULL, TRUE},
567 {"X-List:", NULL, TRUE},
568 {"X-Mailing-list:", NULL, TRUE},
569 {"List-Id:", NULL, TRUE},
570 {"X-Sequence:", NULL, TRUE},
571 {NULL, NULL, FALSE}};
577 H_X_MAILING_LIST = 3,
584 g_return_if_fail(msginfo != NULL);
585 g_return_if_fail(header != NULL);
586 g_return_if_fail(key != NULL);
595 if ((fp = procmsg_open_message(msginfo)) == NULL)
597 procheader_get_header_fields(fp, hentry);
600 #define SET_FILTER_KEY(hstr, idx) \
602 *header = g_strdup(hstr); \
603 *key = hentry[idx].body; \
604 hentry[idx].body = NULL; \
607 if (hentry[H_X_BEENTHERE].body != NULL) {
608 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
609 } else if (hentry[H_X_ML_NAME].body != NULL) {
610 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
611 } else if (hentry[H_X_LIST].body != NULL) {
612 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
613 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
614 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
615 } else if (hentry[H_LIST_ID].body != NULL) {
616 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
617 extract_list_id_str(*key);
618 } else if (hentry[H_X_SEQUENCE].body != NULL) {
621 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
624 while (*p != '\0' && !isspace(*p)) p++;
625 while (isspace(*p)) p++;
632 } else if (msginfo->subject) {
633 *header = g_strdup("subject");
634 *key = g_strdup(msginfo->subject);
637 #undef SET_FILTER_KEY
639 g_free(hentry[H_X_BEENTHERE].body);
640 hentry[H_X_BEENTHERE].body = NULL;
641 g_free(hentry[H_X_ML_NAME].body);
642 hentry[H_X_ML_NAME].body = NULL;
643 g_free(hentry[H_X_LIST].body);
644 hentry[H_X_LIST].body = NULL;
645 g_free(hentry[H_X_MAILING_LIST].body);
646 hentry[H_X_MAILING_LIST].body = NULL;
647 g_free(hentry[H_LIST_ID].body);
648 hentry[H_LIST_ID].body = NULL;
652 *header = g_strdup("from");
653 *key = g_strdup(msginfo->from);
656 *header = g_strdup("to");
657 *key = g_strdup(msginfo->to);
659 case FILTER_BY_SUBJECT:
660 *header = g_strdup("subject");
661 *key = g_strdup(msginfo->subject);
668 void procmsg_empty_trash(void)
673 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
674 trash = FOLDER(cur->data)->trash;
675 if (trash && trash->total_msgs > 0)
676 folder_item_remove_all_msg(trash);
681 *\brief Send messages in queue
683 *\param queue Queue folder to process
684 *\param save_msgs Unused
686 *\return Number of messages sent, negative if an error occurred
687 * positive if no error occurred
689 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
691 gint ret = 1, count = 0;
695 queue = folder_get_default_queue();
696 g_return_val_if_fail(queue != NULL, -1);
698 folder_item_scan(queue);
699 list = folder_item_get_msg_list(queue);
701 for (elem = list; elem != NULL; elem = elem->next) {
705 msginfo = (MsgInfo *)(elem->data);
706 if (!MSG_IS_LOCKED(msginfo->flags)) {
707 file = folder_item_fetch_msg(queue, msginfo->msgnum);
709 if (procmsg_send_message_queue(file) < 0) {
710 g_warning("Sending queued message %d failed.\n",
715 * We save in procmsg_send_message_queue because
716 * we need the destination folder from the queue
720 procmsg_save_to_outbox
721 (queue->folder->outbox,
725 folder_item_remove_msg(queue, msginfo->msgnum);
730 /* FIXME: supposedly if only one message is locked, and queue
731 * is being flushed, the following free says something like
732 * "freeing msg ## in folder (nil)". */
733 procmsg_msginfo_free(msginfo);
739 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
744 if ((fp = fopen(in, "rb")) == NULL) {
745 FILE_OP_ERROR(in, "fopen");
748 if ((outfp = fopen(out, "wb")) == NULL) {
749 FILE_OP_ERROR(out, "fopen");
753 while (fgets(buf, sizeof(buf), fp) != NULL)
754 if (buf[0] == '\r' || buf[0] == '\n') break;
755 while (fgets(buf, sizeof(buf), fp) != NULL)
762 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
766 MsgInfo *msginfo, *tmp_msginfo;
767 MsgFlags flag = {0, 0};
769 debug_print("saving sent message...\n");
772 outbox = folder_get_default_outbox();
773 g_return_val_if_fail(outbox != NULL, -1);
775 /* remove queueing headers */
777 gchar tmp[MAXPATHLEN + 1];
779 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
780 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
782 if (procmsg_remove_special_headers(file, tmp) !=0)
785 folder_item_scan(outbox);
786 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
787 g_warning("can't save message\n");
792 folder_item_scan(outbox);
793 if ((num = folder_item_add_msg
794 (outbox, file, &flag, FALSE)) < 0) {
795 g_warning("can't save message\n");
800 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
801 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
802 if (msginfo != NULL) {
803 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
804 procmsg_msginfo_free(msginfo); /* refcnt-- */
805 /* tmp_msginfo == msginfo */
806 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
807 msginfo->returnreceiptto)) {
808 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
809 procmsg_msginfo_free(msginfo); /* refcnt-- */
812 folder_item_update(outbox, TRUE);
817 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
819 static const gchar *def_cmd = "lpr %s";
826 g_return_if_fail(msginfo);
828 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
829 g_warning("Can't get text part\n");
833 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
834 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
836 if ((prfp = fopen(prtmp, "wb")) == NULL) {
837 FILE_OP_ERROR(prtmp, "fopen");
843 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
844 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
845 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
846 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
847 if (msginfo->newsgroups)
848 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
849 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
852 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
858 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
860 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
863 g_warning("Print command line is invalid: `%s'\n",
865 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
871 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
875 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
882 MsgInfo *procmsg_msginfo_new(void)
886 newmsginfo = g_new0(MsgInfo, 1);
887 newmsginfo->refcnt = 1;
892 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
896 if (msginfo == NULL) return NULL;
898 newmsginfo = g_new0(MsgInfo, 1);
900 newmsginfo->refcnt = 1;
902 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
903 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
904 g_strdup(msginfo->mmb) : NULL
928 MEMBDUP(dispositionnotificationto);
929 MEMBDUP(returnreceiptto);
933 MEMBCOPY(threadscore);
938 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
940 MsgInfo *full_msginfo;
943 if (msginfo == NULL) return NULL;
945 file = procmsg_get_message_file(msginfo);
947 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
951 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
953 if (!full_msginfo) return NULL;
955 /* CLAWS: make sure we add the missing members; see:
956 * procheader.c::procheader_get_headernames() */
958 msginfo->xface = g_strdup(full_msginfo->xface);
959 if (!msginfo->dispositionnotificationto)
960 msginfo->dispositionnotificationto =
961 g_strdup(full_msginfo->dispositionnotificationto);
962 if (!msginfo->returnreceiptto)
963 msginfo->returnreceiptto = g_strdup
964 (full_msginfo->returnreceiptto);
965 procmsg_msginfo_free(full_msginfo);
967 return procmsg_msginfo_new_ref(msginfo);
969 full_msginfo->msgnum = msginfo->msgnum;
970 full_msginfo->size = msginfo->size;
971 full_msginfo->mtime = msginfo->mtime;
972 full_msginfo->folder = msginfo->folder;
974 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
975 full_msginfo->decryption_failed = msginfo->decryption_failed;
977 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
983 void procmsg_msginfo_free(MsgInfo *msginfo)
985 if (msginfo == NULL) return;
988 if (msginfo->refcnt > 0)
991 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
993 if (msginfo->to_folder) {
994 msginfo->to_folder->op_count--;
995 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
998 g_free(msginfo->fromspace);
999 g_free(msginfo->references);
1000 g_free(msginfo->returnreceiptto);
1001 g_free(msginfo->dispositionnotificationto);
1002 g_free(msginfo->xface);
1004 g_free(msginfo->fromname);
1006 g_free(msginfo->date);
1007 g_free(msginfo->from);
1008 g_free(msginfo->to);
1009 g_free(msginfo->cc);
1010 g_free(msginfo->newsgroups);
1011 g_free(msginfo->subject);
1012 g_free(msginfo->msgid);
1013 g_free(msginfo->inreplyto);
1014 g_free(msginfo->xref);
1019 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1023 memusage += sizeof(MsgInfo);
1024 if (msginfo->fromname)
1025 memusage += strlen(msginfo->fromname);
1027 memusage += strlen(msginfo->date);
1029 memusage += strlen(msginfo->from);
1031 memusage += strlen(msginfo->to);
1033 memusage += strlen(msginfo->cc);
1034 if (msginfo->newsgroups)
1035 memusage += strlen(msginfo->newsgroups);
1036 if (msginfo->subject)
1037 memusage += strlen(msginfo->subject);
1039 memusage += strlen(msginfo->msgid);
1040 if (msginfo->inreplyto)
1041 memusage += strlen(msginfo->inreplyto);
1043 memusage += strlen(msginfo->xface);
1044 if (msginfo->dispositionnotificationto)
1045 memusage += strlen(msginfo->dispositionnotificationto);
1046 if (msginfo->returnreceiptto)
1047 memusage += strlen(msginfo->returnreceiptto);
1048 if (msginfo->references)
1049 memusage += strlen(msginfo->references);
1050 if (msginfo->fromspace)
1051 memusage += strlen(msginfo->fromspace);
1056 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1058 const MsgInfo *msginfo1 = a;
1059 const MsgInfo *msginfo2 = b;
1066 return msginfo1->msgnum - msginfo2->msgnum;
1075 Q_MAIL_ACCOUNT_ID = 4,
1076 Q_NEWS_ACCOUNT_ID = 5,
1077 Q_SAVE_COPY_FOLDER = 6,
1078 Q_REPLY_MESSAGE_ID = 7,
1079 Q_FWD_MESSAGE_ID = 8
1082 gint procmsg_send_message_queue(const gchar *file)
1084 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1085 {"SSV:", NULL, FALSE},
1086 {"R:", NULL, FALSE},
1087 {"NG:", NULL, FALSE},
1088 {"MAID:", NULL, FALSE},
1089 {"NAID:", NULL, FALSE},
1090 {"SCF:", NULL, FALSE},
1091 {"RMID:", NULL, FALSE},
1092 {"FMID:", NULL, FALSE},
1093 {NULL, NULL, FALSE}};
1096 gint mailval = 0, newsval = 0;
1098 gchar *smtpserver = NULL;
1099 GSList *to_list = NULL;
1100 GSList *newsgroup_list = NULL;
1101 gchar *savecopyfolder = NULL;
1102 gchar *replymessageid = NULL;
1103 gchar *fwdmessageid = NULL;
1104 gchar buf[BUFFSIZE];
1106 PrefsAccount *mailac = NULL, *newsac = NULL;
1109 g_return_val_if_fail(file != NULL, -1);
1111 if ((fp = fopen(file, "rb")) == NULL) {
1112 FILE_OP_ERROR(file, "fopen");
1116 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1118 gchar *p = buf + strlen(qentry[hnum].name);
1122 if (!from) from = g_strdup(p);
1125 if (!smtpserver) smtpserver = g_strdup(p);
1128 to_list = address_list_append(to_list, p);
1131 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1133 case Q_MAIL_ACCOUNT_ID:
1134 mailac = account_find_from_id(atoi(p));
1136 case Q_NEWS_ACCOUNT_ID:
1137 newsac = account_find_from_id(atoi(p));
1139 case Q_SAVE_COPY_FOLDER:
1140 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1142 case Q_REPLY_MESSAGE_ID:
1143 if (!replymessageid) replymessageid = g_strdup(p);
1145 case Q_FWD_MESSAGE_ID:
1146 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1150 filepos = ftell(fp);
1153 debug_print("Sending message by mail\n");
1155 g_warning("Queued message header is broken.\n");
1157 } else if (mailac && mailac->use_mail_command &&
1158 mailac->mail_command && (* mailac->mail_command)) {
1159 mailval = send_message_local(mailac->mail_command, fp);
1161 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1162 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1166 mailac = account_find_from_smtp_server(from, smtpserver);
1168 g_warning("Account not found. "
1169 "Using current account...\n");
1170 mailac = cur_account;
1175 mailval = send_message_smtp(mailac, to_list, fp);
1177 PrefsAccount tmp_ac;
1179 g_warning("Account not found.\n");
1181 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1182 tmp_ac.address = from;
1183 tmp_ac.smtp_server = smtpserver;
1184 tmp_ac.smtpport = SMTP_PORT;
1185 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1190 fseek(fp, filepos, SEEK_SET);
1191 if (newsgroup_list && (newsval == 0)) {
1196 /* write to temporary file */
1197 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1198 G_DIR_SEPARATOR, (gint)file);
1199 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1200 FILE_OP_ERROR(tmp, "fopen");
1202 alertpanel_error(_("Could not create temporary file for news sending."));
1204 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1205 FILE_OP_ERROR(tmp, "chmod");
1206 g_warning("can't change file mode\n");
1209 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1210 if (fputs(buf, tmpfp) == EOF) {
1211 FILE_OP_ERROR(tmp, "fputs");
1213 alertpanel_error(_("Error when writing temporary file for news sending."));
1219 debug_print("Sending message by news\n");
1221 folder = FOLDER(newsac->folder);
1223 newsval = news_post(folder, tmp);
1225 alertpanel_error(_("Error occurred while posting the message to %s ."),
1226 newsac->nntp_server);
1234 slist_free_strings(to_list);
1235 g_slist_free(to_list);
1236 slist_free_strings(newsgroup_list);
1237 g_slist_free(newsgroup_list);
1242 /* save message to outbox */
1243 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1246 debug_print("saving sent message...\n");
1248 outbox = folder_find_item_from_identifier(savecopyfolder);
1250 outbox = folder_get_default_outbox();
1252 procmsg_save_to_outbox(outbox, file, TRUE);
1255 if (replymessageid != NULL || fwdmessageid != NULL) {
1259 if (replymessageid != NULL)
1260 tokens = g_strsplit(replymessageid, "\x7f", 0);
1262 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1263 item = folder_find_item_from_identifier(tokens[0]);
1265 /* check if queued message has valid folder and message id */
1266 if (item != NULL && tokens[2] != NULL) {
1269 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1271 /* check if referring message exists and has a message id */
1272 if ((msginfo != NULL) &&
1273 (msginfo->msgid != NULL) &&
1274 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1275 procmsg_msginfo_free(msginfo);
1279 if (msginfo == NULL) {
1280 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1283 if (msginfo != NULL) {
1284 if (replymessageid != NULL) {
1285 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1286 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1289 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1290 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1292 procmsg_msginfo_free(msginfo);
1298 g_free(savecopyfolder);
1299 g_free(replymessageid);
1300 g_free(fwdmessageid);
1302 return (newsval != 0 ? newsval : mailval);
1305 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1307 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1310 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1314 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1319 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1320 item->unread_msgs++;
1321 if (procmsg_msg_has_marked_parent(msginfo))
1322 item->unreadmarked_msgs++;
1325 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1326 item->unread_msgs--;
1327 if (procmsg_msg_has_marked_parent(msginfo))
1328 item->unreadmarked_msgs--;
1332 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1333 procmsg_update_unread_children(msginfo, TRUE);
1336 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1337 procmsg_update_unread_children(msginfo, FALSE);
1341 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1344 MsgInfoUpdate msginfo_update;
1345 MsgPermFlags perm_flags_new, perm_flags_old;
1347 g_return_if_fail(msginfo != NULL);
1348 item = msginfo->folder;
1349 g_return_if_fail(item != NULL);
1351 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1353 /* Perm Flags handling */
1354 perm_flags_old = msginfo->flags.perm_flags;
1355 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1356 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1357 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1360 if (perm_flags_old != perm_flags_new) {
1361 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1363 update_folder_msg_counts(item, msginfo, perm_flags_old);
1365 msginfo_update.msginfo = msginfo;
1366 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1367 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1370 /* Tmp flags hanlding */
1371 msginfo->flags.tmp_flags |= tmp_flags;
1374 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1377 MsgInfoUpdate msginfo_update;
1378 MsgPermFlags perm_flags_new, perm_flags_old;
1380 g_return_if_fail(msginfo != NULL);
1381 item = msginfo->folder;
1382 g_return_if_fail(item != NULL);
1384 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1386 /* Perm Flags handling */
1387 perm_flags_old = msginfo->flags.perm_flags;
1388 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1390 if (perm_flags_old != perm_flags_new) {
1391 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1393 update_folder_msg_counts(item, msginfo, perm_flags_old);
1395 msginfo_update.msginfo = msginfo;
1396 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1397 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1400 /* Tmp flags hanlding */
1401 msginfo->flags.tmp_flags &= ~tmp_flags;
1405 *\brief check for flags (e.g. mark) in prior msgs of current thread
1407 *\param info Current message
1408 *\param perm_flags Flags to be checked
1409 *\param parentmsgs Hash of prior msgs to avoid loops
1411 *\return gboolean TRUE if perm_flags are found
1413 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1414 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1418 g_return_val_if_fail(info != NULL, FALSE);
1420 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1421 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1423 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1424 procmsg_msginfo_free(tmp);
1426 } else if (tmp != NULL) {
1429 if (g_hash_table_lookup(parentmsgs, info)) {
1430 debug_print("loop detected: %s%c%d\n",
1431 folder_item_get_path(info->folder),
1432 G_DIR_SEPARATOR, info->msgnum);
1435 g_hash_table_insert(parentmsgs, info, "1");
1436 result = procmsg_msg_has_flagged_parent_real(
1437 tmp, perm_flags, parentmsgs);
1439 procmsg_msginfo_free(tmp);
1449 *\brief Callback for cleaning up hash of parentmsgs
1451 gboolean parentmsgs_hash_remove(gpointer key,
1459 *\brief Set up list of parentmsgs
1460 * See procmsg_msg_has_flagged_parent_real()
1462 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1465 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1467 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1468 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1469 g_hash_table_destroy(parentmsgs);
1474 *\brief Check if msgs prior in thread are marked
1475 * See procmsg_msg_has_flagged_parent_real()
1477 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1479 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1483 GSList *procmsg_find_children_func(MsgInfo *info,
1484 GSList *children, GSList *all)
1488 g_return_val_if_fail(info!=NULL, children);
1489 if (info->msgid == NULL)
1492 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1493 MsgInfo *tmp = (MsgInfo *)cur->data;
1494 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1495 /* Check if message is already in the list */
1496 if ((children == NULL) ||
1497 (g_slist_index(children, tmp) == -1)) {
1498 children = g_slist_prepend(children,
1499 procmsg_msginfo_new_ref(tmp));
1500 children = procmsg_find_children_func(tmp,
1509 GSList *procmsg_find_children (MsgInfo *info)
1514 g_return_val_if_fail(info!=NULL, NULL);
1515 all = folder_item_get_msg_list(info->folder);
1516 children = procmsg_find_children_func(info, NULL, all);
1517 if (children != NULL) {
1518 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1519 /* this will not free the used pointers
1520 created with procmsg_msginfo_new_ref */
1521 procmsg_msginfo_free((MsgInfo *)cur->data);
1529 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1531 GSList *children = procmsg_find_children(info);
1533 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1534 MsgInfo *tmp = (MsgInfo *)cur->data;
1535 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1537 info->folder->unreadmarked_msgs++;
1539 info->folder->unreadmarked_msgs--;
1540 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1542 procmsg_msginfo_free(tmp);
1544 g_slist_free(children);
1548 * Set the destination folder for a copy or move operation
1550 * \param msginfo The message which's destination folder is changed
1551 * \param to_folder The destination folder for the operation
1553 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1555 if(msginfo->to_folder != NULL) {
1556 msginfo->to_folder->op_count--;
1557 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1559 msginfo->to_folder = to_folder;
1560 if(to_folder != NULL) {
1561 to_folder->op_count++;
1562 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1567 * Apply filtering actions to the msginfo
1569 * \param msginfo The MsgInfo describing the message that should be filtered
1570 * \return TRUE if the message was moved and MsgInfo is now invalid,
1573 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1575 MailFilteringData mail_filtering_data;
1577 mail_filtering_data.msginfo = msginfo;
1578 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1581 /* filter if enabled in prefs or move to inbox if not */
1582 if((global_processing != NULL) &&
1583 filter_message_by_msginfo(global_processing, msginfo))