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 hash
143 table. a duplicate subject line replaces the one in
144 the table only if its older. (this round should actually
145 create a list of all duplicate subject lines)
147 the second round finishes the threads by attaching
148 duplicate subject lines to the one found in the
149 hash table. as soon as a subject line is found that
150 is too old, that one becomes the new parent for
151 the next iteration. (this fails when a parent arrived
152 later than its child.)
155 static void subject_relation_insert(GRelation *relation, GNode *node)
160 g_return_if_fail(relation != NULL);
161 g_return_if_fail(node != NULL);
162 msginfo = (MsgInfo *) node->data;
163 g_return_if_fail(msginfo != NULL);
165 subject = msginfo->subject;
168 subject += subject_get_prefix_length(subject);
170 g_relation_insert(relation, subject, node);
173 static GNode *subject_relation_lookup(GRelation *relation, MsgInfo *msginfo)
179 g_return_val_if_fail(relation != NULL, NULL);
181 subject = msginfo->subject;
184 subject += subject_get_prefix_length(subject);
186 tuples = g_relation_select(relation, subject, 0);
190 if (tuples->len > 0) {
192 GNode *relation_node;
193 MsgInfo *relation_msginfo = NULL, *best_msginfo = NULL;
196 /* check all nodes with the same subject to find the best parent */
197 for (i = 0; i < tuples->len; i++) {
198 relation_node = (GNode *) g_tuples_index(tuples, i, 1);
199 relation_msginfo = (MsgInfo *) relation_node->data;
202 /* best node should be the oldest in the found nodes */
203 /* parent node must not be older then msginfo */
204 if ((relation_msginfo->date_t < msginfo->date_t) &&
205 ((best_msginfo == NULL) ||
206 (best_msginfo->date_t > relation_msginfo->date_t)))
209 /* parent node must not be more then thread_by_subject_max_age
210 days older then msginfo */
211 if (abs(difftime(msginfo->date_t, relation_msginfo->date_t)) >
212 prefs_common.thread_by_subject_max_age * 3600 * 24)
215 /* can add new tests for all matching
216 nodes found by subject */
219 node = relation_node;
220 best_msginfo = relation_msginfo;
225 g_tuples_destroy(tuples);
229 /* return the reversed thread tree */
230 GNode *procmsg_get_thread_tree(GSList *mlist)
232 GNode *root, *parent, *node, *next, *last;
233 GNode *prev; /* CLAWS */
234 GHashTable *msgid_table;
235 GRelation *subject_relation;
238 const gchar *subject;
240 root = g_node_new(NULL);
241 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
242 subject_relation = g_relation_new(2);
243 g_relation_index(subject_relation, 0, g_str_hash, g_str_equal);
245 for (; mlist != NULL; mlist = mlist->next) {
246 msginfo = (MsgInfo *)mlist->data;
249 if (msginfo->inreplyto) {
250 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
251 if (parent == NULL) {
255 node = g_node_insert_data_before
256 (parent, parent == root ? parent->children : NULL,
258 if ((msgid = msginfo->msgid) && g_hash_table_lookup(msgid_table, msgid) == NULL)
259 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
261 /* CLAWS: add subject to relation (without prefix) */
262 if (prefs_common.thread_by_subject) {
263 subject_relation_insert(subject_relation, node);
267 /* complete the unfinished threads */
268 for (node = root->children; node != NULL; ) {
269 prev = node->prev; /* CLAWS: need the last node */
272 msginfo = (MsgInfo *)node->data;
273 if (msginfo->inreplyto) {
274 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
275 /* node should not be the parent, and node should not
276 be an ancestor of parent (circular reference) */
277 if (parent && parent != node &&
278 !g_node_is_ancestor(node, parent)) {
281 (parent, parent->children, node);
284 last = (next == NULL) ? prev : node;
288 if (prefs_common.thread_by_subject) {
289 for (node = last; node && node != NULL;) {
291 msginfo = (MsgInfo *) node->data;
293 /* may not parentize if parent was delivered after childs */
294 if (subject != msginfo->subject)
295 parent = subject_relation_lookup(subject_relation, msginfo);
299 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
300 find the parent node */
301 if (parent != NULL) {
302 if (g_node_is_ancestor(node, parent))
310 g_node_append(parent, node);
317 g_relation_destroy(subject_relation);
318 g_hash_table_destroy(msgid_table);
323 void procmsg_move_messages(GSList *mlist)
325 GSList *cur, *movelist = NULL;
327 FolderItem *dest = NULL;
331 folder_item_update_freeze();
333 for (cur = mlist; cur != NULL; cur = cur->next) {
334 msginfo = (MsgInfo *)cur->data;
336 dest = msginfo->to_folder;
337 movelist = g_slist_append(movelist, msginfo);
338 } else if (dest == msginfo->to_folder) {
339 movelist = g_slist_append(movelist, msginfo);
341 folder_item_move_msgs(dest, movelist);
342 g_slist_free(movelist);
344 dest = msginfo->to_folder;
345 movelist = g_slist_append(movelist, msginfo);
347 procmsg_msginfo_set_to_folder(msginfo, NULL);
351 folder_item_move_msgs(dest, movelist);
352 g_slist_free(movelist);
355 folder_item_update_thaw();
358 void procmsg_copy_messages(GSList *mlist)
360 GSList *cur, *copylist = NULL;
362 FolderItem *dest = NULL;
366 folder_item_update_freeze();
368 for (cur = mlist; cur != NULL; cur = cur->next) {
369 msginfo = (MsgInfo *)cur->data;
371 dest = msginfo->to_folder;
372 copylist = g_slist_append(copylist, msginfo);
373 } else if (dest == msginfo->to_folder) {
374 copylist = g_slist_append(copylist, msginfo);
376 folder_item_copy_msgs(dest, copylist);
377 g_slist_free(copylist);
379 dest = msginfo->to_folder;
380 copylist = g_slist_append(copylist, msginfo);
382 procmsg_msginfo_set_to_folder(msginfo, NULL);
386 folder_item_copy_msgs(dest, copylist);
387 g_slist_free(copylist);
390 folder_item_update_thaw();
393 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
397 g_return_val_if_fail(msginfo != NULL, NULL);
399 if (msginfo->plaintext_file)
400 file = g_strdup(msginfo->plaintext_file);
402 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
408 gchar *procmsg_get_message_file(MsgInfo *msginfo)
410 gchar *filename = NULL;
412 g_return_val_if_fail(msginfo != NULL, NULL);
414 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
416 g_warning("can't fetch message %d\n", msginfo->msgnum);
421 GSList *procmsg_get_message_file_list(GSList *mlist)
423 GSList *file_list = NULL;
425 MsgFileInfo *fileinfo;
428 while (mlist != NULL) {
429 msginfo = (MsgInfo *)mlist->data;
430 file = procmsg_get_message_file(msginfo);
432 procmsg_message_file_list_free(file_list);
435 fileinfo = g_new(MsgFileInfo, 1);
436 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
437 fileinfo->file = file;
438 fileinfo->flags = g_new(MsgFlags, 1);
439 *fileinfo->flags = msginfo->flags;
440 file_list = g_slist_prepend(file_list, fileinfo);
444 file_list = g_slist_reverse(file_list);
449 void procmsg_message_file_list_free(MsgInfoList *file_list)
452 MsgFileInfo *fileinfo;
454 for (cur = file_list; cur != NULL; cur = cur->next) {
455 fileinfo = (MsgFileInfo *)cur->data;
456 procmsg_msginfo_free(fileinfo->msginfo);
457 g_free(fileinfo->file);
458 g_free(fileinfo->flags);
462 g_slist_free(file_list);
465 FILE *procmsg_open_message(MsgInfo *msginfo)
470 g_return_val_if_fail(msginfo != NULL, NULL);
472 file = procmsg_get_message_file_path(msginfo);
473 g_return_val_if_fail(file != NULL, NULL);
475 if (!is_file_exist(file)) {
477 file = procmsg_get_message_file(msginfo);
478 g_return_val_if_fail(file != NULL, NULL);
481 if ((fp = fopen(file, "rb")) == NULL) {
482 FILE_OP_ERROR(file, "fopen");
489 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
492 while (fgets(buf, sizeof(buf), fp) != NULL)
493 if (buf[0] == '\r' || buf[0] == '\n') break;
500 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
506 g_return_val_if_fail(msginfo != NULL, NULL);
508 if (mimeinfo) *mimeinfo = NULL;
510 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
512 mimeinfo_ = procmime_scan_mime_header(fp);
518 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
519 rfc2015_is_encrypted(mimeinfo_)) {
520 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
523 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
524 !msginfo->plaintext_file &&
525 !msginfo->decryption_failed) {
527 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
528 if (msginfo->plaintext_file &&
529 !msginfo->decryption_failed) {
531 procmime_mimeinfo_free_all(mimeinfo_);
532 if ((fp = procmsg_open_message(msginfo)) == NULL)
534 mimeinfo_ = procmime_scan_mime_header(fp);
540 if (fseek(fp, fpos, SEEK_SET) < 0)
545 if (mimeinfo) *mimeinfo = mimeinfo_;
550 gboolean procmsg_msg_exist(MsgInfo *msginfo)
555 if (!msginfo) return FALSE;
557 path = folder_item_get_path(msginfo->folder);
559 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
565 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
566 PrefsFilterType type)
568 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
569 {"X-ML-Name:", NULL, TRUE},
570 {"X-List:", NULL, TRUE},
571 {"X-Mailing-list:", NULL, TRUE},
572 {"List-Id:", NULL, TRUE},
573 {"X-Sequence:", NULL, TRUE},
574 {NULL, NULL, FALSE}};
580 H_X_MAILING_LIST = 3,
587 g_return_if_fail(msginfo != NULL);
588 g_return_if_fail(header != NULL);
589 g_return_if_fail(key != NULL);
598 if ((fp = procmsg_open_message(msginfo)) == NULL)
600 procheader_get_header_fields(fp, hentry);
603 #define SET_FILTER_KEY(hstr, idx) \
605 *header = g_strdup(hstr); \
606 *key = hentry[idx].body; \
607 hentry[idx].body = NULL; \
610 if (hentry[H_X_BEENTHERE].body != NULL) {
611 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
612 } else if (hentry[H_X_ML_NAME].body != NULL) {
613 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
614 } else if (hentry[H_X_LIST].body != NULL) {
615 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
616 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
617 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
618 } else if (hentry[H_LIST_ID].body != NULL) {
619 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
620 extract_list_id_str(*key);
621 } else if (hentry[H_X_SEQUENCE].body != NULL) {
624 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
627 while (*p != '\0' && !isspace(*p)) p++;
628 while (isspace(*p)) p++;
635 } else if (msginfo->subject) {
636 *header = g_strdup("subject");
637 *key = g_strdup(msginfo->subject);
640 #undef SET_FILTER_KEY
642 g_free(hentry[H_X_BEENTHERE].body);
643 hentry[H_X_BEENTHERE].body = NULL;
644 g_free(hentry[H_X_ML_NAME].body);
645 hentry[H_X_ML_NAME].body = NULL;
646 g_free(hentry[H_X_LIST].body);
647 hentry[H_X_LIST].body = NULL;
648 g_free(hentry[H_X_MAILING_LIST].body);
649 hentry[H_X_MAILING_LIST].body = NULL;
650 g_free(hentry[H_LIST_ID].body);
651 hentry[H_LIST_ID].body = NULL;
655 *header = g_strdup("from");
656 *key = g_strdup(msginfo->from);
659 *header = g_strdup("to");
660 *key = g_strdup(msginfo->to);
662 case FILTER_BY_SUBJECT:
663 *header = g_strdup("subject");
664 *key = g_strdup(msginfo->subject);
671 void procmsg_empty_trash(void)
676 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
677 trash = FOLDER(cur->data)->trash;
678 if (trash && trash->total_msgs > 0)
679 folder_item_remove_all_msg(trash);
684 *\brief Send messages in queue
686 *\param queue Queue folder to process
687 *\param save_msgs Unused
689 *\return Number of messages sent, negative if an error occurred
690 * positive if no error occurred
692 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
694 gint ret = 1, count = 0;
698 queue = folder_get_default_queue();
699 g_return_val_if_fail(queue != NULL, -1);
701 folder_item_scan(queue);
702 list = folder_item_get_msg_list(queue);
704 for (elem = list; elem != NULL; elem = elem->next) {
708 msginfo = (MsgInfo *)(elem->data);
709 if (!MSG_IS_LOCKED(msginfo->flags)) {
710 file = folder_item_fetch_msg(queue, msginfo->msgnum);
712 if (procmsg_send_message_queue(file) < 0) {
713 g_warning("Sending queued message %d failed.\n",
718 * We save in procmsg_send_message_queue because
719 * we need the destination folder from the queue
723 procmsg_save_to_outbox
724 (queue->folder->outbox,
728 folder_item_remove_msg(queue, msginfo->msgnum);
733 /* FIXME: supposedly if only one message is locked, and queue
734 * is being flushed, the following free says something like
735 * "freeing msg ## in folder (nil)". */
736 procmsg_msginfo_free(msginfo);
742 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
747 if ((fp = fopen(in, "rb")) == NULL) {
748 FILE_OP_ERROR(in, "fopen");
751 if ((outfp = fopen(out, "wb")) == NULL) {
752 FILE_OP_ERROR(out, "fopen");
756 while (fgets(buf, sizeof(buf), fp) != NULL)
757 if (buf[0] == '\r' || buf[0] == '\n') break;
758 while (fgets(buf, sizeof(buf), fp) != NULL)
765 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
769 MsgInfo *msginfo, *tmp_msginfo;
770 MsgFlags flag = {0, 0};
772 debug_print("saving sent message...\n");
775 outbox = folder_get_default_outbox();
776 g_return_val_if_fail(outbox != NULL, -1);
778 /* remove queueing headers */
780 gchar tmp[MAXPATHLEN + 1];
782 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
783 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
785 if (procmsg_remove_special_headers(file, tmp) !=0)
788 folder_item_scan(outbox);
789 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
790 g_warning("can't save message\n");
795 folder_item_scan(outbox);
796 if ((num = folder_item_add_msg
797 (outbox, file, &flag, FALSE)) < 0) {
798 g_warning("can't save message\n");
803 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
804 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
805 if (msginfo != NULL) {
806 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
807 procmsg_msginfo_free(msginfo); /* refcnt-- */
808 /* tmp_msginfo == msginfo */
809 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
810 msginfo->returnreceiptto)) {
811 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
812 procmsg_msginfo_free(msginfo); /* refcnt-- */
815 folder_item_update(outbox, TRUE);
820 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
822 static const gchar *def_cmd = "lpr %s";
829 g_return_if_fail(msginfo);
831 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
832 g_warning("Can't get text part\n");
836 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
837 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
839 if ((prfp = fopen(prtmp, "wb")) == NULL) {
840 FILE_OP_ERROR(prtmp, "fopen");
846 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
847 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
848 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
849 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
850 if (msginfo->newsgroups)
851 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
852 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
855 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
861 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
863 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
866 g_warning("Print command line is invalid: `%s'\n",
868 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
874 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
878 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
885 MsgInfo *procmsg_msginfo_new(void)
889 newmsginfo = g_new0(MsgInfo, 1);
890 newmsginfo->refcnt = 1;
895 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
899 if (msginfo == NULL) return NULL;
901 newmsginfo = g_new0(MsgInfo, 1);
903 newmsginfo->refcnt = 1;
905 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
906 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
907 g_strdup(msginfo->mmb) : NULL
931 MEMBDUP(dispositionnotificationto);
932 MEMBDUP(returnreceiptto);
936 MEMBCOPY(threadscore);
941 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
943 MsgInfo *full_msginfo;
946 if (msginfo == NULL) return NULL;
948 file = procmsg_get_message_file(msginfo);
950 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
954 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
956 if (!full_msginfo) return NULL;
958 /* CLAWS: make sure we add the missing members; see:
959 * procheader.c::procheader_get_headernames() */
961 msginfo->xface = g_strdup(full_msginfo->xface);
962 if (!msginfo->dispositionnotificationto)
963 msginfo->dispositionnotificationto =
964 g_strdup(full_msginfo->dispositionnotificationto);
965 if (!msginfo->returnreceiptto)
966 msginfo->returnreceiptto = g_strdup
967 (full_msginfo->returnreceiptto);
968 procmsg_msginfo_free(full_msginfo);
970 return procmsg_msginfo_new_ref(msginfo);
972 full_msginfo->msgnum = msginfo->msgnum;
973 full_msginfo->size = msginfo->size;
974 full_msginfo->mtime = msginfo->mtime;
975 full_msginfo->folder = msginfo->folder;
977 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
978 full_msginfo->decryption_failed = msginfo->decryption_failed;
980 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
986 void procmsg_msginfo_free(MsgInfo *msginfo)
988 if (msginfo == NULL) return;
991 if (msginfo->refcnt > 0)
994 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
996 if (msginfo->to_folder) {
997 msginfo->to_folder->op_count--;
998 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1001 g_free(msginfo->fromspace);
1002 g_free(msginfo->references);
1003 g_free(msginfo->returnreceiptto);
1004 g_free(msginfo->dispositionnotificationto);
1005 g_free(msginfo->xface);
1007 g_free(msginfo->fromname);
1009 g_free(msginfo->date);
1010 g_free(msginfo->from);
1011 g_free(msginfo->to);
1012 g_free(msginfo->cc);
1013 g_free(msginfo->newsgroups);
1014 g_free(msginfo->subject);
1015 g_free(msginfo->msgid);
1016 g_free(msginfo->inreplyto);
1017 g_free(msginfo->xref);
1022 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
1026 memusage += sizeof(MsgInfo);
1027 if (msginfo->fromname)
1028 memusage += strlen(msginfo->fromname);
1030 memusage += strlen(msginfo->date);
1032 memusage += strlen(msginfo->from);
1034 memusage += strlen(msginfo->to);
1036 memusage += strlen(msginfo->cc);
1037 if (msginfo->newsgroups)
1038 memusage += strlen(msginfo->newsgroups);
1039 if (msginfo->subject)
1040 memusage += strlen(msginfo->subject);
1042 memusage += strlen(msginfo->msgid);
1043 if (msginfo->inreplyto)
1044 memusage += strlen(msginfo->inreplyto);
1046 memusage += strlen(msginfo->xface);
1047 if (msginfo->dispositionnotificationto)
1048 memusage += strlen(msginfo->dispositionnotificationto);
1049 if (msginfo->returnreceiptto)
1050 memusage += strlen(msginfo->returnreceiptto);
1051 if (msginfo->references)
1052 memusage += strlen(msginfo->references);
1053 if (msginfo->fromspace)
1054 memusage += strlen(msginfo->fromspace);
1059 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1061 const MsgInfo *msginfo1 = a;
1062 const MsgInfo *msginfo2 = b;
1069 return msginfo1->msgnum - msginfo2->msgnum;
1078 Q_MAIL_ACCOUNT_ID = 4,
1079 Q_NEWS_ACCOUNT_ID = 5,
1080 Q_SAVE_COPY_FOLDER = 6,
1081 Q_REPLY_MESSAGE_ID = 7,
1082 Q_FWD_MESSAGE_ID = 8
1085 gint procmsg_send_message_queue(const gchar *file)
1087 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1088 {"SSV:", NULL, FALSE},
1089 {"R:", NULL, FALSE},
1090 {"NG:", NULL, FALSE},
1091 {"MAID:", NULL, FALSE},
1092 {"NAID:", NULL, FALSE},
1093 {"SCF:", NULL, FALSE},
1094 {"RMID:", NULL, FALSE},
1095 {"FMID:", NULL, FALSE},
1096 {NULL, NULL, FALSE}};
1099 gint mailval = 0, newsval = 0;
1101 gchar *smtpserver = NULL;
1102 GSList *to_list = NULL;
1103 GSList *newsgroup_list = NULL;
1104 gchar *savecopyfolder = NULL;
1105 gchar *replymessageid = NULL;
1106 gchar *fwdmessageid = NULL;
1107 gchar buf[BUFFSIZE];
1109 PrefsAccount *mailac = NULL, *newsac = NULL;
1112 g_return_val_if_fail(file != NULL, -1);
1114 if ((fp = fopen(file, "rb")) == NULL) {
1115 FILE_OP_ERROR(file, "fopen");
1119 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1121 gchar *p = buf + strlen(qentry[hnum].name);
1125 if (!from) from = g_strdup(p);
1128 if (!smtpserver) smtpserver = g_strdup(p);
1131 to_list = address_list_append(to_list, p);
1134 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1136 case Q_MAIL_ACCOUNT_ID:
1137 mailac = account_find_from_id(atoi(p));
1139 case Q_NEWS_ACCOUNT_ID:
1140 newsac = account_find_from_id(atoi(p));
1142 case Q_SAVE_COPY_FOLDER:
1143 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1145 case Q_REPLY_MESSAGE_ID:
1146 if (!replymessageid) replymessageid = g_strdup(p);
1148 case Q_FWD_MESSAGE_ID:
1149 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1153 filepos = ftell(fp);
1156 debug_print("Sending message by mail\n");
1158 g_warning("Queued message header is broken.\n");
1160 } else if (mailac && mailac->use_mail_command &&
1161 mailac->mail_command && (* mailac->mail_command)) {
1162 mailval = send_message_local(mailac->mail_command, fp);
1164 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1165 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1169 mailac = account_find_from_smtp_server(from, smtpserver);
1171 g_warning("Account not found. "
1172 "Using current account...\n");
1173 mailac = cur_account;
1178 mailval = send_message_smtp(mailac, to_list, fp);
1180 PrefsAccount tmp_ac;
1182 g_warning("Account not found.\n");
1184 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1185 tmp_ac.address = from;
1186 tmp_ac.smtp_server = smtpserver;
1187 tmp_ac.smtpport = SMTP_PORT;
1188 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1193 fseek(fp, filepos, SEEK_SET);
1194 if (newsgroup_list && (newsval == 0)) {
1199 /* write to temporary file */
1200 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1201 G_DIR_SEPARATOR, (gint)file);
1202 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1203 FILE_OP_ERROR(tmp, "fopen");
1205 alertpanel_error(_("Could not create temporary file for news sending."));
1207 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1208 FILE_OP_ERROR(tmp, "chmod");
1209 g_warning("can't change file mode\n");
1212 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1213 if (fputs(buf, tmpfp) == EOF) {
1214 FILE_OP_ERROR(tmp, "fputs");
1216 alertpanel_error(_("Error when writing temporary file for news sending."));
1222 debug_print("Sending message by news\n");
1224 folder = FOLDER(newsac->folder);
1226 newsval = news_post(folder, tmp);
1228 alertpanel_error(_("Error occurred while posting the message to %s ."),
1229 newsac->nntp_server);
1237 slist_free_strings(to_list);
1238 g_slist_free(to_list);
1239 slist_free_strings(newsgroup_list);
1240 g_slist_free(newsgroup_list);
1245 /* save message to outbox */
1246 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1249 debug_print("saving sent message...\n");
1251 outbox = folder_find_item_from_identifier(savecopyfolder);
1253 outbox = folder_get_default_outbox();
1255 procmsg_save_to_outbox(outbox, file, TRUE);
1258 if (replymessageid != NULL || fwdmessageid != NULL) {
1262 if (replymessageid != NULL)
1263 tokens = g_strsplit(replymessageid, "\x7f", 0);
1265 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1266 item = folder_find_item_from_identifier(tokens[0]);
1268 /* check if queued message has valid folder and message id */
1269 if (item != NULL && tokens[2] != NULL) {
1272 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1274 /* check if referring message exists and has a message id */
1275 if ((msginfo != NULL) &&
1276 (msginfo->msgid != NULL) &&
1277 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1278 procmsg_msginfo_free(msginfo);
1282 if (msginfo == NULL) {
1283 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1286 if (msginfo != NULL) {
1287 if (replymessageid != NULL) {
1288 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1289 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1292 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1293 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1295 procmsg_msginfo_free(msginfo);
1301 g_free(savecopyfolder);
1302 g_free(replymessageid);
1303 g_free(fwdmessageid);
1305 return (newsval != 0 ? newsval : mailval);
1308 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1310 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1313 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1317 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1322 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1323 item->unread_msgs++;
1324 if (procmsg_msg_has_marked_parent(msginfo))
1325 item->unreadmarked_msgs++;
1328 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1329 item->unread_msgs--;
1330 if (procmsg_msg_has_marked_parent(msginfo))
1331 item->unreadmarked_msgs--;
1335 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1336 procmsg_update_unread_children(msginfo, TRUE);
1339 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1340 procmsg_update_unread_children(msginfo, FALSE);
1344 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1347 MsgInfoUpdate msginfo_update;
1348 MsgPermFlags perm_flags_new, perm_flags_old;
1350 g_return_if_fail(msginfo != NULL);
1351 item = msginfo->folder;
1352 g_return_if_fail(item != NULL);
1354 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1356 /* Perm Flags handling */
1357 perm_flags_old = msginfo->flags.perm_flags;
1358 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1359 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1360 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1363 if (perm_flags_old != perm_flags_new) {
1364 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1366 update_folder_msg_counts(item, msginfo, perm_flags_old);
1368 msginfo_update.msginfo = msginfo;
1369 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1370 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1373 /* Tmp flags hanlding */
1374 msginfo->flags.tmp_flags |= tmp_flags;
1377 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1380 MsgInfoUpdate msginfo_update;
1381 MsgPermFlags perm_flags_new, perm_flags_old;
1383 g_return_if_fail(msginfo != NULL);
1384 item = msginfo->folder;
1385 g_return_if_fail(item != NULL);
1387 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1389 /* Perm Flags handling */
1390 perm_flags_old = msginfo->flags.perm_flags;
1391 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1393 if (perm_flags_old != perm_flags_new) {
1394 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1396 update_folder_msg_counts(item, msginfo, perm_flags_old);
1398 msginfo_update.msginfo = msginfo;
1399 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1400 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1403 /* Tmp flags hanlding */
1404 msginfo->flags.tmp_flags &= ~tmp_flags;
1408 *\brief check for flags (e.g. mark) in prior msgs of current thread
1410 *\param info Current message
1411 *\param perm_flags Flags to be checked
1412 *\param parentmsgs Hash of prior msgs to avoid loops
1414 *\return gboolean TRUE if perm_flags are found
1416 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1417 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1421 g_return_val_if_fail(info != NULL, FALSE);
1423 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1424 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1426 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1427 procmsg_msginfo_free(tmp);
1429 } else if (tmp != NULL) {
1432 if (g_hash_table_lookup(parentmsgs, info)) {
1433 debug_print("loop detected: %s%c%d\n",
1434 folder_item_get_path(info->folder),
1435 G_DIR_SEPARATOR, info->msgnum);
1438 g_hash_table_insert(parentmsgs, info, "1");
1439 result = procmsg_msg_has_flagged_parent_real(
1440 tmp, perm_flags, parentmsgs);
1442 procmsg_msginfo_free(tmp);
1452 *\brief Callback for cleaning up hash of parentmsgs
1454 gboolean parentmsgs_hash_remove(gpointer key,
1462 *\brief Set up list of parentmsgs
1463 * See procmsg_msg_has_flagged_parent_real()
1465 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1468 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1470 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1471 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1472 g_hash_table_destroy(parentmsgs);
1477 *\brief Check if msgs prior in thread are marked
1478 * See procmsg_msg_has_flagged_parent_real()
1480 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1482 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1486 GSList *procmsg_find_children_func(MsgInfo *info,
1487 GSList *children, GSList *all)
1491 g_return_val_if_fail(info!=NULL, children);
1492 if (info->msgid == NULL)
1495 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1496 MsgInfo *tmp = (MsgInfo *)cur->data;
1497 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1498 /* Check if message is already in the list */
1499 if ((children == NULL) ||
1500 (g_slist_index(children, tmp) == -1)) {
1501 children = g_slist_prepend(children,
1502 procmsg_msginfo_new_ref(tmp));
1503 children = procmsg_find_children_func(tmp,
1512 GSList *procmsg_find_children (MsgInfo *info)
1517 g_return_val_if_fail(info!=NULL, NULL);
1518 all = folder_item_get_msg_list(info->folder);
1519 children = procmsg_find_children_func(info, NULL, all);
1520 if (children != NULL) {
1521 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1522 /* this will not free the used pointers
1523 created with procmsg_msginfo_new_ref */
1524 procmsg_msginfo_free((MsgInfo *)cur->data);
1532 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1534 GSList *children = procmsg_find_children(info);
1536 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1537 MsgInfo *tmp = (MsgInfo *)cur->data;
1538 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1540 info->folder->unreadmarked_msgs++;
1542 info->folder->unreadmarked_msgs--;
1543 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1545 procmsg_msginfo_free(tmp);
1547 g_slist_free(children);
1551 * Set the destination folder for a copy or move operation
1553 * \param msginfo The message which's destination folder is changed
1554 * \param to_folder The destination folder for the operation
1556 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1558 if(msginfo->to_folder != NULL) {
1559 msginfo->to_folder->op_count--;
1560 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1562 msginfo->to_folder = to_folder;
1563 if(to_folder != NULL) {
1564 to_folder->op_count++;
1565 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1570 * Apply filtering actions to the msginfo
1572 * \param msginfo The MsgInfo describing the message that should be filtered
1573 * \return TRUE if the message was moved and MsgInfo is now invalid,
1576 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1578 MailFilteringData mail_filtering_data;
1580 mail_filtering_data.msginfo = msginfo;
1581 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1584 /* filter if enabled in prefs or move to inbox if not */
1585 if((global_processing != NULL) &&
1586 filter_message_by_msginfo(global_processing, msginfo))