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 /* return the reversed thread tree */
156 GNode *procmsg_get_thread_tree(GSList *mlist)
158 GNode *root, *parent, *node, *next, *last;
159 GNode *prev; /* CLAWS */
160 GHashTable *msgid_table;
161 GHashTable *subject_table;
164 const gchar *subject;
166 root = g_node_new(NULL);
167 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
168 subject_table = g_hash_table_new(g_str_hash, g_str_equal);
170 for (; mlist != NULL; mlist = mlist->next) {
171 msginfo = (MsgInfo *)mlist->data;
174 if (msginfo->inreplyto) {
175 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
176 if (parent == NULL) {
179 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
180 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
181 procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
185 node = g_node_insert_data_before
186 (parent, parent == root ? parent->children : NULL,
188 if ((msgid = msginfo->msgid) &&
189 g_hash_table_lookup(msgid_table, msgid) == NULL)
190 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
192 /* CLAWS: add subject to table (without prefix) */
193 if (prefs_common.thread_by_subject) {
194 GNode *found_subject = NULL;
196 subject = msginfo->subject;
197 subject += subject_get_prefix_length(subject);
198 found_subject = subject_table_lookup_clean
199 (subject_table, (gchar *) subject);
201 if (found_subject == NULL)
202 subject_table_insert_clean(subject_table, (gchar *) subject,
204 else if ( ((MsgInfo*)(found_subject->data))->date_t >
205 ((MsgInfo*)(node->data))->date_t ) {
206 /* replace if msg in table is older than current one
207 TODO: should create a list of messages with same subject */
208 subject_table_remove_clean(subject_table, (gchar *) subject);
209 subject_table_insert_clean(subject_table, (gchar *) subject, node);
214 /* complete the unfinished threads */
215 for (node = root->children; node != NULL; ) {
216 prev = node->prev; /* CLAWS: need the last node */
219 msginfo = (MsgInfo *)node->data;
220 if (msginfo->inreplyto) {
221 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
222 /* node should not be the parent, and node should not
223 be an ancestor of parent (circular reference) */
224 if (parent && parent != node &&
225 !g_node_is_ancestor(node, parent)) {
228 (parent, parent->children, node);
229 /* CLAWS: ignore thread */
230 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
231 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
234 last = (next == NULL) ? prev : node;
238 if (prefs_common.thread_by_subject) {
239 for (node = last; node && node != NULL;) {
241 msginfo = (MsgInfo *) node->data;
242 subject = msginfo->subject + subject_get_prefix_length(msginfo->subject);
244 /* may not parentize if parent was delivered after childs */
245 if (subject != msginfo->subject)
246 parent = subject_table_lookup_clean(subject_table, (gchar *) subject);
250 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
251 find the parent node */
252 if (parent != NULL) {
253 if (g_node_is_ancestor(node, parent))
257 /* make new thread parent if too old compared to previous one; probably
258 breaks ignoring threads for subject threading. not accurate because
259 the tree isn't sorted by date. */
260 if (parent && abs(difftime(msginfo->date_t, ((MsgInfo *)parent->data)->date_t)) >
261 prefs_common.thread_by_subject_max_age * 3600 * 24) {
262 subject_table_remove_clean(subject_table, (gchar *) subject);
263 subject_table_insert_clean(subject_table, (gchar *) subject, node);
270 g_node_append(parent, node);
271 /* CLAWS: ignore thread */
272 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
273 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
281 g_hash_table_destroy(subject_table);
282 g_hash_table_destroy(msgid_table);
287 void procmsg_move_messages(GSList *mlist)
289 GSList *cur, *movelist = NULL;
291 FolderItem *dest = NULL;
295 folder_item_update_freeze();
297 for (cur = mlist; cur != NULL; cur = cur->next) {
298 msginfo = (MsgInfo *)cur->data;
300 dest = msginfo->to_folder;
301 movelist = g_slist_append(movelist, msginfo);
302 } else if (dest == msginfo->to_folder) {
303 movelist = g_slist_append(movelist, msginfo);
305 folder_item_move_msgs(dest, movelist);
306 g_slist_free(movelist);
308 dest = msginfo->to_folder;
309 movelist = g_slist_append(movelist, msginfo);
311 procmsg_msginfo_set_to_folder(msginfo, NULL);
315 folder_item_move_msgs(dest, movelist);
316 g_slist_free(movelist);
319 folder_item_update_thaw();
322 void procmsg_copy_messages(GSList *mlist)
324 GSList *cur, *copylist = NULL;
326 FolderItem *dest = NULL;
330 folder_item_update_freeze();
332 for (cur = mlist; cur != NULL; cur = cur->next) {
333 msginfo = (MsgInfo *)cur->data;
335 dest = msginfo->to_folder;
336 copylist = g_slist_append(copylist, msginfo);
337 } else if (dest == msginfo->to_folder) {
338 copylist = g_slist_append(copylist, msginfo);
340 folder_item_copy_msgs(dest, copylist);
341 g_slist_free(copylist);
343 dest = msginfo->to_folder;
344 copylist = g_slist_append(copylist, msginfo);
346 procmsg_msginfo_set_to_folder(msginfo, NULL);
350 folder_item_copy_msgs(dest, copylist);
351 g_slist_free(copylist);
354 folder_item_update_thaw();
357 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
361 g_return_val_if_fail(msginfo != NULL, NULL);
363 if (msginfo->plaintext_file)
364 file = g_strdup(msginfo->plaintext_file);
366 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
372 gchar *procmsg_get_message_file(MsgInfo *msginfo)
374 gchar *filename = NULL;
376 g_return_val_if_fail(msginfo != NULL, NULL);
378 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
380 g_warning("can't fetch message %d\n", msginfo->msgnum);
385 GSList *procmsg_get_message_file_list(GSList *mlist)
387 GSList *file_list = NULL;
389 MsgFileInfo *fileinfo;
392 while (mlist != NULL) {
393 msginfo = (MsgInfo *)mlist->data;
394 file = procmsg_get_message_file(msginfo);
396 procmsg_message_file_list_free(file_list);
399 fileinfo = g_new(MsgFileInfo, 1);
400 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
401 fileinfo->file = file;
402 fileinfo->flags = g_new(MsgFlags, 1);
403 *fileinfo->flags = msginfo->flags;
404 file_list = g_slist_prepend(file_list, fileinfo);
408 file_list = g_slist_reverse(file_list);
413 void procmsg_message_file_list_free(MsgInfoList *file_list)
416 MsgFileInfo *fileinfo;
418 for (cur = file_list; cur != NULL; cur = cur->next) {
419 fileinfo = (MsgFileInfo *)cur->data;
420 procmsg_msginfo_free(fileinfo->msginfo);
421 g_free(fileinfo->file);
422 g_free(fileinfo->flags);
426 g_slist_free(file_list);
429 FILE *procmsg_open_message(MsgInfo *msginfo)
434 g_return_val_if_fail(msginfo != NULL, NULL);
436 file = procmsg_get_message_file_path(msginfo);
437 g_return_val_if_fail(file != NULL, NULL);
439 if (!is_file_exist(file)) {
441 file = procmsg_get_message_file(msginfo);
442 g_return_val_if_fail(file != NULL, NULL);
445 if ((fp = fopen(file, "rb")) == NULL) {
446 FILE_OP_ERROR(file, "fopen");
453 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
456 while (fgets(buf, sizeof(buf), fp) != NULL)
457 if (buf[0] == '\r' || buf[0] == '\n') break;
464 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
470 g_return_val_if_fail(msginfo != NULL, NULL);
472 if (mimeinfo) *mimeinfo = NULL;
474 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
476 mimeinfo_ = procmime_scan_mime_header(fp);
482 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
483 rfc2015_is_encrypted(mimeinfo_)) {
484 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
487 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
488 !msginfo->plaintext_file &&
489 !msginfo->decryption_failed) {
491 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
492 if (msginfo->plaintext_file &&
493 !msginfo->decryption_failed) {
495 procmime_mimeinfo_free_all(mimeinfo_);
496 if ((fp = procmsg_open_message(msginfo)) == NULL)
498 mimeinfo_ = procmime_scan_mime_header(fp);
504 if (fseek(fp, fpos, SEEK_SET) < 0)
509 if (mimeinfo) *mimeinfo = mimeinfo_;
514 gboolean procmsg_msg_exist(MsgInfo *msginfo)
519 if (!msginfo) return FALSE;
521 path = folder_item_get_path(msginfo->folder);
523 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
529 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
530 PrefsFilterType type)
532 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
533 {"X-ML-Name:", NULL, TRUE},
534 {"X-List:", NULL, TRUE},
535 {"X-Mailing-list:", NULL, TRUE},
536 {"List-Id:", NULL, TRUE},
537 {"X-Sequence:", NULL, TRUE},
538 {NULL, NULL, FALSE}};
544 H_X_MAILING_LIST = 3,
551 g_return_if_fail(msginfo != NULL);
552 g_return_if_fail(header != NULL);
553 g_return_if_fail(key != NULL);
562 if ((fp = procmsg_open_message(msginfo)) == NULL)
564 procheader_get_header_fields(fp, hentry);
567 #define SET_FILTER_KEY(hstr, idx) \
569 *header = g_strdup(hstr); \
570 *key = hentry[idx].body; \
571 hentry[idx].body = NULL; \
574 if (hentry[H_X_BEENTHERE].body != NULL) {
575 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
576 } else if (hentry[H_X_ML_NAME].body != NULL) {
577 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
578 } else if (hentry[H_X_LIST].body != NULL) {
579 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
580 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
581 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
582 } else if (hentry[H_LIST_ID].body != NULL) {
583 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
584 extract_list_id_str(*key);
585 } else if (hentry[H_X_SEQUENCE].body != NULL) {
588 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
591 while (*p != '\0' && !isspace(*p)) p++;
592 while (isspace(*p)) p++;
599 } else if (msginfo->subject) {
600 *header = g_strdup("subject");
601 *key = g_strdup(msginfo->subject);
604 #undef SET_FILTER_KEY
606 g_free(hentry[H_X_BEENTHERE].body);
607 hentry[H_X_BEENTHERE].body = NULL;
608 g_free(hentry[H_X_ML_NAME].body);
609 hentry[H_X_ML_NAME].body = NULL;
610 g_free(hentry[H_X_LIST].body);
611 hentry[H_X_LIST].body = NULL;
612 g_free(hentry[H_X_MAILING_LIST].body);
613 hentry[H_X_MAILING_LIST].body = NULL;
614 g_free(hentry[H_LIST_ID].body);
615 hentry[H_LIST_ID].body = NULL;
619 *header = g_strdup("from");
620 *key = g_strdup(msginfo->from);
623 *header = g_strdup("to");
624 *key = g_strdup(msginfo->to);
626 case FILTER_BY_SUBJECT:
627 *header = g_strdup("subject");
628 *key = g_strdup(msginfo->subject);
635 void procmsg_empty_trash(void)
640 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
641 trash = FOLDER(cur->data)->trash;
642 if (trash && trash->total_msgs > 0)
643 folder_item_remove_all_msg(trash);
648 *\brief Send messages in queue
650 *\param queue Queue folder to process
651 *\param save_msgs Unused
653 *\return Number of messages sent, negative if an error occurred
654 * positive if no error occurred
656 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
658 gint ret = 1, count = 0;
662 queue = folder_get_default_queue();
663 g_return_val_if_fail(queue != NULL, -1);
665 folder_item_scan(queue);
666 list = folder_item_get_msg_list(queue);
668 for (elem = list; elem != NULL; elem = elem->next) {
672 msginfo = (MsgInfo *)(elem->data);
673 if (!MSG_IS_LOCKED(msginfo->flags)) {
674 file = folder_item_fetch_msg(queue, msginfo->msgnum);
676 if (procmsg_send_message_queue(file) < 0) {
677 g_warning("Sending queued message %d failed.\n",
682 * We save in procmsg_send_message_queue because
683 * we need the destination folder from the queue
687 procmsg_save_to_outbox
688 (queue->folder->outbox,
692 folder_item_remove_msg(queue, msginfo->msgnum);
697 /* FIXME: supposedly if only one message is locked, and queue
698 * is being flushed, the following free says something like
699 * "freeing msg ## in folder (nil)". */
700 procmsg_msginfo_free(msginfo);
706 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
711 if ((fp = fopen(in, "rb")) == NULL) {
712 FILE_OP_ERROR(in, "fopen");
715 if ((outfp = fopen(out, "wb")) == NULL) {
716 FILE_OP_ERROR(out, "fopen");
720 while (fgets(buf, sizeof(buf), fp) != NULL)
721 if (buf[0] == '\r' || buf[0] == '\n') break;
722 while (fgets(buf, sizeof(buf), fp) != NULL)
729 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
733 MsgInfo *msginfo, *tmp_msginfo;
735 debug_print("saving sent message...\n");
738 outbox = folder_get_default_outbox();
739 g_return_val_if_fail(outbox != NULL, -1);
741 /* remove queueing headers */
743 gchar tmp[MAXPATHLEN + 1];
745 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
746 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
748 if (procmsg_remove_special_headers(file, tmp) !=0)
751 folder_item_scan(outbox);
752 if ((num = folder_item_add_msg(outbox, tmp, NULL, TRUE)) < 0) {
753 g_warning("can't save message\n");
758 folder_item_scan(outbox);
759 if ((num = folder_item_add_msg(outbox, file, NULL, FALSE)) < 0) {
760 g_warning("can't save message\n");
765 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
766 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
767 if (msginfo != NULL) {
768 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
769 procmsg_msginfo_free(msginfo); /* refcnt-- */
770 /* tmp_msginfo == msginfo */
771 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
772 msginfo->returnreceiptto)) {
773 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
774 procmsg_msginfo_free(msginfo); /* refcnt-- */
777 folder_item_update(outbox, TRUE);
782 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
784 static const gchar *def_cmd = "lpr %s";
791 g_return_if_fail(msginfo);
793 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
794 g_warning("Can't get text part\n");
798 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
799 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
801 if ((prfp = fopen(prtmp, "wb")) == NULL) {
802 FILE_OP_ERROR(prtmp, "fopen");
808 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
809 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
810 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
811 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
812 if (msginfo->newsgroups)
813 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
814 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
817 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
823 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
825 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
828 g_warning("Print command line is invalid: `%s'\n",
830 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
836 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
840 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
847 MsgInfo *procmsg_msginfo_new(void)
851 newmsginfo = g_new0(MsgInfo, 1);
852 newmsginfo->refcnt = 1;
857 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
861 if (msginfo == NULL) return NULL;
863 newmsginfo = g_new0(MsgInfo, 1);
865 newmsginfo->refcnt = 1;
867 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
868 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
869 g_strdup(msginfo->mmb) : NULL
893 MEMBDUP(dispositionnotificationto);
894 MEMBDUP(returnreceiptto);
898 MEMBCOPY(threadscore);
903 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
905 MsgInfo *full_msginfo;
908 if (msginfo == NULL) return NULL;
910 file = procmsg_get_message_file(msginfo);
912 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
916 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
918 if (!full_msginfo) return NULL;
920 /* CLAWS: make sure we add the missing members; see:
921 * procheader.c::procheader_get_headernames() */
923 msginfo->xface = g_strdup(full_msginfo->xface);
924 if (!msginfo->dispositionnotificationto)
925 msginfo->dispositionnotificationto =
926 g_strdup(full_msginfo->dispositionnotificationto);
927 if (!msginfo->returnreceiptto)
928 msginfo->returnreceiptto = g_strdup
929 (full_msginfo->returnreceiptto);
930 procmsg_msginfo_free(full_msginfo);
932 return procmsg_msginfo_new_ref(msginfo);
934 full_msginfo->msgnum = msginfo->msgnum;
935 full_msginfo->size = msginfo->size;
936 full_msginfo->mtime = msginfo->mtime;
937 full_msginfo->folder = msginfo->folder;
939 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
940 full_msginfo->decryption_failed = msginfo->decryption_failed;
942 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
948 void procmsg_msginfo_free(MsgInfo *msginfo)
950 if (msginfo == NULL) return;
953 if (msginfo->refcnt > 0)
956 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
958 if (msginfo->to_folder) {
959 msginfo->to_folder->op_count--;
960 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
963 g_free(msginfo->fromspace);
964 g_free(msginfo->references);
965 g_free(msginfo->returnreceiptto);
966 g_free(msginfo->dispositionnotificationto);
967 g_free(msginfo->xface);
969 g_free(msginfo->fromname);
971 g_free(msginfo->date);
972 g_free(msginfo->from);
975 g_free(msginfo->newsgroups);
976 g_free(msginfo->subject);
977 g_free(msginfo->msgid);
978 g_free(msginfo->inreplyto);
979 g_free(msginfo->xref);
984 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
988 memusage += sizeof(MsgInfo);
989 if (msginfo->fromname)
990 memusage += strlen(msginfo->fromname);
992 memusage += strlen(msginfo->date);
994 memusage += strlen(msginfo->from);
996 memusage += strlen(msginfo->to);
998 memusage += strlen(msginfo->cc);
999 if (msginfo->newsgroups)
1000 memusage += strlen(msginfo->newsgroups);
1001 if (msginfo->subject)
1002 memusage += strlen(msginfo->subject);
1004 memusage += strlen(msginfo->msgid);
1005 if (msginfo->inreplyto)
1006 memusage += strlen(msginfo->inreplyto);
1008 memusage += strlen(msginfo->xface);
1009 if (msginfo->dispositionnotificationto)
1010 memusage += strlen(msginfo->dispositionnotificationto);
1011 if (msginfo->returnreceiptto)
1012 memusage += strlen(msginfo->returnreceiptto);
1013 if (msginfo->references)
1014 memusage += strlen(msginfo->references);
1015 if (msginfo->fromspace)
1016 memusage += strlen(msginfo->fromspace);
1021 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1023 const MsgInfo *msginfo1 = a;
1024 const MsgInfo *msginfo2 = b;
1031 return msginfo1->msgnum - msginfo2->msgnum;
1040 Q_MAIL_ACCOUNT_ID = 4,
1041 Q_NEWS_ACCOUNT_ID = 5,
1042 Q_SAVE_COPY_FOLDER = 6,
1043 Q_REPLY_MESSAGE_ID = 7,
1044 Q_FWD_MESSAGE_ID = 8
1047 gint procmsg_send_message_queue(const gchar *file)
1049 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1050 {"SSV:", NULL, FALSE},
1051 {"R:", NULL, FALSE},
1052 {"NG:", NULL, FALSE},
1053 {"MAID:", NULL, FALSE},
1054 {"NAID:", NULL, FALSE},
1055 {"SCF:", NULL, FALSE},
1056 {"RMID:", NULL, FALSE},
1057 {"FMID:", NULL, FALSE},
1058 {NULL, NULL, FALSE}};
1061 gint mailval = 0, newsval = 0;
1063 gchar *smtpserver = NULL;
1064 GSList *to_list = NULL;
1065 GSList *newsgroup_list = NULL;
1066 gchar *savecopyfolder = NULL;
1067 gchar *replymessageid = NULL;
1068 gchar *fwdmessageid = NULL;
1069 gchar buf[BUFFSIZE];
1071 PrefsAccount *mailac = NULL, *newsac = NULL;
1074 g_return_val_if_fail(file != NULL, -1);
1076 if ((fp = fopen(file, "rb")) == NULL) {
1077 FILE_OP_ERROR(file, "fopen");
1081 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1083 gchar *p = buf + strlen(qentry[hnum].name);
1087 if (!from) from = g_strdup(p);
1090 if (!smtpserver) smtpserver = g_strdup(p);
1093 to_list = address_list_append(to_list, p);
1096 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1098 case Q_MAIL_ACCOUNT_ID:
1099 mailac = account_find_from_id(atoi(p));
1101 case Q_NEWS_ACCOUNT_ID:
1102 newsac = account_find_from_id(atoi(p));
1104 case Q_SAVE_COPY_FOLDER:
1105 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1107 case Q_REPLY_MESSAGE_ID:
1108 if (!replymessageid) replymessageid = g_strdup(p);
1110 case Q_FWD_MESSAGE_ID:
1111 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1115 filepos = ftell(fp);
1118 debug_print("Sending message by mail\n");
1120 g_warning("Queued message header is broken.\n");
1122 } else if (mailac && mailac->use_mail_command &&
1123 mailac->mail_command && (* mailac->mail_command)) {
1124 mailval = send_message_local(mailac->mail_command, fp);
1126 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1127 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1131 mailac = account_find_from_smtp_server(from, smtpserver);
1133 g_warning("Account not found. "
1134 "Using current account...\n");
1135 mailac = cur_account;
1140 mailval = send_message_smtp(mailac, to_list, fp);
1142 PrefsAccount tmp_ac;
1144 g_warning("Account not found.\n");
1146 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1147 tmp_ac.address = from;
1148 tmp_ac.smtp_server = smtpserver;
1149 tmp_ac.smtpport = SMTP_PORT;
1150 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1155 fseek(fp, filepos, SEEK_SET);
1156 if (newsgroup_list && (newsval == 0)) {
1161 /* write to temporary file */
1162 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1163 G_DIR_SEPARATOR, (gint)file);
1164 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1165 FILE_OP_ERROR(tmp, "fopen");
1167 alertpanel_error(_("Could not create temporary file for news sending."));
1169 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1170 FILE_OP_ERROR(tmp, "chmod");
1171 g_warning("can't change file mode\n");
1174 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1175 if (fputs(buf, tmpfp) == EOF) {
1176 FILE_OP_ERROR(tmp, "fputs");
1178 alertpanel_error(_("Error when writing temporary file for news sending."));
1184 debug_print("Sending message by news\n");
1186 folder = FOLDER(newsac->folder);
1188 newsval = news_post(folder, tmp);
1190 alertpanel_error(_("Error occurred while posting the message to %s ."),
1191 newsac->nntp_server);
1199 slist_free_strings(to_list);
1200 g_slist_free(to_list);
1201 slist_free_strings(newsgroup_list);
1202 g_slist_free(newsgroup_list);
1207 /* save message to outbox */
1208 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1211 debug_print("saving sent message...\n");
1213 outbox = folder_find_item_from_identifier(savecopyfolder);
1215 outbox = folder_get_default_outbox();
1217 procmsg_save_to_outbox(outbox, file, TRUE);
1220 if (replymessageid != NULL || fwdmessageid != NULL) {
1224 if (replymessageid != NULL)
1225 tokens = g_strsplit(replymessageid, "\x7f", 0);
1227 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1228 item = folder_find_item_from_identifier(tokens[0]);
1230 /* check if queued message has valid folder and message id */
1231 if (item != NULL && tokens[2] != NULL) {
1234 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1236 /* check if referring message exists and has a message id */
1237 if ((msginfo != NULL) &&
1238 (msginfo->msgid != NULL) &&
1239 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1240 procmsg_msginfo_free(msginfo);
1244 if (msginfo == NULL) {
1245 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1248 if (msginfo != NULL) {
1249 if (replymessageid != NULL) {
1250 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1251 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1254 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1255 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1257 procmsg_msginfo_free(msginfo);
1263 g_free(savecopyfolder);
1264 g_free(replymessageid);
1265 g_free(fwdmessageid);
1267 return (newsval != 0 ? newsval : mailval);
1270 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1272 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1275 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1279 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1284 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1285 item->unread_msgs++;
1286 if (procmsg_msg_has_marked_parent(msginfo))
1287 item->unreadmarked_msgs++;
1290 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1291 item->unread_msgs--;
1292 if (procmsg_msg_has_marked_parent(msginfo))
1293 item->unreadmarked_msgs--;
1297 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1298 procmsg_update_unread_children(msginfo, TRUE);
1301 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1302 procmsg_update_unread_children(msginfo, FALSE);
1306 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1309 MsgInfoUpdate msginfo_update;
1310 MsgPermFlags perm_flags_new, perm_flags_old;
1312 g_return_if_fail(msginfo != NULL);
1313 item = msginfo->folder;
1314 g_return_if_fail(item != NULL);
1316 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1318 /* Perm Flags handling */
1319 perm_flags_old = msginfo->flags.perm_flags;
1320 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1321 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1322 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1325 if (perm_flags_old != perm_flags_new) {
1326 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1328 update_folder_msg_counts(item, msginfo, perm_flags_old);
1330 msginfo_update.msginfo = msginfo;
1331 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1332 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1335 /* Tmp flags hanlding */
1336 msginfo->flags.tmp_flags |= tmp_flags;
1339 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1342 MsgInfoUpdate msginfo_update;
1343 MsgPermFlags perm_flags_new, perm_flags_old;
1345 g_return_if_fail(msginfo != NULL);
1346 item = msginfo->folder;
1347 g_return_if_fail(item != NULL);
1349 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1351 /* Perm Flags handling */
1352 perm_flags_old = msginfo->flags.perm_flags;
1353 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1355 if (perm_flags_old != perm_flags_new) {
1356 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1358 update_folder_msg_counts(item, msginfo, perm_flags_old);
1360 msginfo_update.msginfo = msginfo;
1361 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1362 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1365 /* Tmp flags hanlding */
1366 msginfo->flags.tmp_flags &= ~tmp_flags;
1370 *\brief check for flags (e.g. mark) in prior msgs of current thread
1372 *\param info Current message
1373 *\param perm_flags Flags to be checked
1374 *\param parentmsgs Hash of prior msgs to avoid loops
1376 *\return gboolean TRUE if perm_flags are found
1378 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1379 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1383 g_return_val_if_fail(info != NULL, FALSE);
1385 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1386 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1388 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1389 procmsg_msginfo_free(tmp);
1391 } else if (tmp != NULL) {
1394 if (g_hash_table_lookup(parentmsgs, info)) {
1395 debug_print("loop detected: %s%c%d\n",
1396 folder_item_get_path(info->folder),
1397 G_DIR_SEPARATOR, info->msgnum);
1400 g_hash_table_insert(parentmsgs, info, "1");
1401 result = procmsg_msg_has_flagged_parent_real(
1402 tmp, perm_flags, parentmsgs);
1404 procmsg_msginfo_free(tmp);
1414 *\brief Callback for cleaning up hash of parentmsgs
1416 gboolean parentmsgs_hash_remove(gpointer key,
1424 *\brief Set up list of parentmsgs
1425 * See procmsg_msg_has_flagged_parent_real()
1427 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1430 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1432 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1433 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1434 g_hash_table_destroy(parentmsgs);
1439 *\brief Check if msgs prior in thread are marked
1440 * See procmsg_msg_has_flagged_parent_real()
1442 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1444 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1448 GSList *procmsg_find_children_func(MsgInfo *info,
1449 GSList *children, GSList *all)
1453 g_return_val_if_fail(info!=NULL, children);
1454 if (info->msgid == NULL)
1457 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1458 MsgInfo *tmp = (MsgInfo *)cur->data;
1459 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1460 /* Check if message is already in the list */
1461 if ((children == NULL) ||
1462 (g_slist_index(children, tmp) == -1)) {
1463 children = g_slist_prepend(children,
1464 procmsg_msginfo_new_ref(tmp));
1465 children = procmsg_find_children_func(tmp,
1474 GSList *procmsg_find_children (MsgInfo *info)
1479 g_return_val_if_fail(info!=NULL, NULL);
1480 all = folder_item_get_msg_list(info->folder);
1481 children = procmsg_find_children_func(info, NULL, all);
1482 if (children != NULL) {
1483 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1484 /* this will not free the used pointers
1485 created with procmsg_msginfo_new_ref */
1486 procmsg_msginfo_free((MsgInfo *)cur->data);
1494 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1496 GSList *children = procmsg_find_children(info);
1498 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1499 MsgInfo *tmp = (MsgInfo *)cur->data;
1500 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1502 info->folder->unreadmarked_msgs++;
1504 info->folder->unreadmarked_msgs--;
1505 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1507 procmsg_msginfo_free(tmp);
1509 g_slist_free(children);
1513 * Set the destination folder for a copy or move operation
1515 * \param msginfo The message which's destination folder is changed
1516 * \param to_folder The destination folder for the operation
1518 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1520 if(msginfo->to_folder != NULL) {
1521 msginfo->to_folder->op_count--;
1522 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1524 msginfo->to_folder = to_folder;
1525 if(to_folder != NULL) {
1526 to_folder->op_count++;
1527 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1532 * Apply filtering actions to the msginfo
1534 * \param msginfo The MsgInfo describing the message that should be filtered
1535 * \return TRUE if the message was moved and MsgInfo is now invalid,
1538 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1540 MailFilteringData mail_filtering_data;
1542 mail_filtering_data.msginfo = msginfo;
1543 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1546 /* filter if enabled in prefs or move to inbox if not */
1547 if((global_processing != NULL) &&
1548 filter_message_by_msginfo(global_processing, msginfo))