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_with_dest(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_with_dest(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_with_dest(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_with_dest(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,
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);
766 if (msginfo != NULL) {
767 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
768 procmsg_msginfo_free(msginfo);
770 folder_item_update(outbox, TRUE);
775 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
777 static const gchar *def_cmd = "lpr %s";
784 g_return_if_fail(msginfo);
786 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
787 g_warning("Can't get text part\n");
791 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
792 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
794 if ((prfp = fopen(prtmp, "wb")) == NULL) {
795 FILE_OP_ERROR(prtmp, "fopen");
801 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
802 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
803 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
804 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
805 if (msginfo->newsgroups)
806 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
807 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
810 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
816 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
818 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
821 g_warning("Print command line is invalid: `%s'\n",
823 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
829 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
833 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
840 MsgInfo *procmsg_msginfo_new(void)
844 newmsginfo = g_new0(MsgInfo, 1);
845 newmsginfo->refcnt = 1;
850 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
854 if (msginfo == NULL) return NULL;
856 newmsginfo = g_new0(MsgInfo, 1);
858 newmsginfo->refcnt = 1;
860 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
861 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
862 g_strdup(msginfo->mmb) : NULL
886 MEMBDUP(dispositionnotificationto);
887 MEMBDUP(returnreceiptto);
891 MEMBCOPY(threadscore);
896 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
898 MsgInfo *full_msginfo;
901 if (msginfo == NULL) return NULL;
903 file = procmsg_get_message_file(msginfo);
905 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
909 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
911 if (!full_msginfo) return NULL;
913 /* CLAWS: make sure we add the missing members; see:
914 * procheader.c::procheader_get_headernames() */
916 msginfo->xface = g_strdup(full_msginfo->xface);
917 if (!msginfo->dispositionnotificationto)
918 msginfo->dispositionnotificationto =
919 g_strdup(full_msginfo->dispositionnotificationto);
920 if (!msginfo->returnreceiptto)
921 msginfo->returnreceiptto = g_strdup
922 (full_msginfo->returnreceiptto);
923 procmsg_msginfo_free(full_msginfo);
925 return procmsg_msginfo_new_ref(msginfo);
927 full_msginfo->msgnum = msginfo->msgnum;
928 full_msginfo->size = msginfo->size;
929 full_msginfo->mtime = msginfo->mtime;
930 full_msginfo->folder = msginfo->folder;
932 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
933 full_msginfo->decryption_failed = msginfo->decryption_failed;
935 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
941 void procmsg_msginfo_free(MsgInfo *msginfo)
943 if (msginfo == NULL) return;
946 if (msginfo->refcnt > 0)
949 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
951 if (msginfo->to_folder) {
952 msginfo->to_folder->op_count--;
953 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
956 g_free(msginfo->fromspace);
957 g_free(msginfo->references);
958 g_free(msginfo->returnreceiptto);
959 g_free(msginfo->dispositionnotificationto);
960 g_free(msginfo->xface);
962 g_free(msginfo->fromname);
964 g_free(msginfo->date);
965 g_free(msginfo->from);
968 g_free(msginfo->newsgroups);
969 g_free(msginfo->subject);
970 g_free(msginfo->msgid);
971 g_free(msginfo->inreplyto);
972 g_free(msginfo->xref);
977 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
981 memusage += sizeof(MsgInfo);
982 if (msginfo->fromname)
983 memusage += strlen(msginfo->fromname);
985 memusage += strlen(msginfo->date);
987 memusage += strlen(msginfo->from);
989 memusage += strlen(msginfo->to);
991 memusage += strlen(msginfo->cc);
992 if (msginfo->newsgroups)
993 memusage += strlen(msginfo->newsgroups);
994 if (msginfo->subject)
995 memusage += strlen(msginfo->subject);
997 memusage += strlen(msginfo->msgid);
998 if (msginfo->inreplyto)
999 memusage += strlen(msginfo->inreplyto);
1001 memusage += strlen(msginfo->xface);
1002 if (msginfo->dispositionnotificationto)
1003 memusage += strlen(msginfo->dispositionnotificationto);
1004 if (msginfo->returnreceiptto)
1005 memusage += strlen(msginfo->returnreceiptto);
1006 if (msginfo->references)
1007 memusage += strlen(msginfo->references);
1008 if (msginfo->fromspace)
1009 memusage += strlen(msginfo->fromspace);
1014 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1016 const MsgInfo *msginfo1 = a;
1017 const MsgInfo *msginfo2 = b;
1024 return msginfo1->msgnum - msginfo2->msgnum;
1033 Q_MAIL_ACCOUNT_ID = 4,
1034 Q_NEWS_ACCOUNT_ID = 5,
1035 Q_SAVE_COPY_FOLDER = 6,
1036 Q_REPLY_MESSAGE_ID = 7,
1037 Q_FWD_MESSAGE_ID = 8
1040 gint procmsg_send_message_queue(const gchar *file)
1042 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1043 {"SSV:", NULL, FALSE},
1044 {"R:", NULL, FALSE},
1045 {"NG:", NULL, FALSE},
1046 {"MAID:", NULL, FALSE},
1047 {"NAID:", NULL, FALSE},
1048 {"SCF:", NULL, FALSE},
1049 {"RMID:", NULL, FALSE},
1050 {"FMID:", NULL, FALSE},
1051 {NULL, NULL, FALSE}};
1054 gint mailval = 0, newsval = 0;
1056 gchar *smtpserver = NULL;
1057 GSList *to_list = NULL;
1058 GSList *newsgroup_list = NULL;
1059 gchar *savecopyfolder = NULL;
1060 gchar *replymessageid = NULL;
1061 gchar *fwdmessageid = NULL;
1062 gchar buf[BUFFSIZE];
1064 PrefsAccount *mailac = NULL, *newsac = NULL;
1067 g_return_val_if_fail(file != NULL, -1);
1069 if ((fp = fopen(file, "rb")) == NULL) {
1070 FILE_OP_ERROR(file, "fopen");
1074 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1076 gchar *p = buf + strlen(qentry[hnum].name);
1080 if (!from) from = g_strdup(p);
1083 if (!smtpserver) smtpserver = g_strdup(p);
1086 to_list = address_list_append(to_list, p);
1089 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1091 case Q_MAIL_ACCOUNT_ID:
1092 mailac = account_find_from_id(atoi(p));
1094 case Q_NEWS_ACCOUNT_ID:
1095 newsac = account_find_from_id(atoi(p));
1097 case Q_SAVE_COPY_FOLDER:
1098 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1100 case Q_REPLY_MESSAGE_ID:
1101 if (!replymessageid) replymessageid = g_strdup(p);
1103 case Q_FWD_MESSAGE_ID:
1104 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1108 filepos = ftell(fp);
1111 debug_print("Sending message by mail\n");
1113 g_warning("Queued message header is broken.\n");
1115 } else if (mailac && mailac->use_mail_command &&
1116 mailac->mail_command && (* mailac->mail_command)) {
1117 mailval = send_message_local(mailac->mail_command, fp);
1119 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1120 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1124 mailac = account_find_from_smtp_server(from, smtpserver);
1126 g_warning("Account not found. "
1127 "Using current account...\n");
1128 mailac = cur_account;
1133 mailval = send_message_smtp(mailac, to_list, fp);
1135 PrefsAccount tmp_ac;
1137 g_warning("Account not found.\n");
1139 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1140 tmp_ac.address = from;
1141 tmp_ac.smtp_server = smtpserver;
1142 tmp_ac.smtpport = SMTP_PORT;
1143 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1148 fseek(fp, filepos, SEEK_SET);
1149 if (newsgroup_list && (newsval == 0)) {
1154 /* write to temporary file */
1155 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1156 G_DIR_SEPARATOR, (gint)file);
1157 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1158 FILE_OP_ERROR(tmp, "fopen");
1160 alertpanel_error(_("Could not create temporary file for news sending."));
1162 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1163 FILE_OP_ERROR(tmp, "chmod");
1164 g_warning("can't change file mode\n");
1167 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1168 if (fputs(buf, tmpfp) == EOF) {
1169 FILE_OP_ERROR(tmp, "fputs");
1171 alertpanel_error(_("Error when writing temporary file for news sending."));
1177 debug_print("Sending message by news\n");
1179 folder = FOLDER(newsac->folder);
1181 newsval = news_post(folder, tmp);
1183 alertpanel_error(_("Error occurred while posting the message to %s ."),
1184 newsac->nntp_server);
1192 slist_free_strings(to_list);
1193 g_slist_free(to_list);
1194 slist_free_strings(newsgroup_list);
1195 g_slist_free(newsgroup_list);
1200 /* save message to outbox */
1201 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1204 debug_print("saving sent message...\n");
1206 outbox = folder_find_item_from_identifier(savecopyfolder);
1208 outbox = folder_get_default_outbox();
1210 procmsg_save_to_outbox(outbox, file, TRUE);
1213 if (replymessageid != NULL || fwdmessageid != NULL) {
1217 if (replymessageid != NULL)
1218 tokens = g_strsplit(replymessageid, "\x7f", 0);
1220 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1221 item = folder_find_item_from_identifier(tokens[0]);
1223 /* check if queued message has valid folder and message id */
1224 if (item != NULL && tokens[2] != NULL) {
1227 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1229 /* check if referring message exists and has a message id */
1230 if ((msginfo != NULL) &&
1231 (msginfo->msgid != NULL) &&
1232 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1233 procmsg_msginfo_free(msginfo);
1237 if (msginfo == NULL) {
1238 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1241 if (msginfo != NULL) {
1242 if (replymessageid != NULL) {
1243 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1244 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1247 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1248 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1250 procmsg_msginfo_free(msginfo);
1256 g_free(savecopyfolder);
1257 g_free(replymessageid);
1258 g_free(fwdmessageid);
1260 return (newsval != 0 ? newsval : mailval);
1263 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1265 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1268 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1272 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1277 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1278 item->unread_msgs++;
1279 if (procmsg_msg_has_marked_parent(msginfo))
1280 item->unreadmarked_msgs++;
1283 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1284 item->unread_msgs--;
1285 if (procmsg_msg_has_marked_parent(msginfo))
1286 item->unreadmarked_msgs--;
1290 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1291 procmsg_update_unread_children(msginfo, TRUE);
1294 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1295 procmsg_update_unread_children(msginfo, FALSE);
1299 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1302 MsgInfoUpdate msginfo_update;
1303 MsgPermFlags perm_flags_new, perm_flags_old;
1305 g_return_if_fail(msginfo != NULL);
1306 item = msginfo->folder;
1307 g_return_if_fail(item != NULL);
1309 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1311 /* Perm Flags handling */
1312 perm_flags_old = msginfo->flags.perm_flags;
1313 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1314 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1315 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1318 if (perm_flags_old != perm_flags_new) {
1319 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1321 update_folder_msg_counts(item, msginfo, perm_flags_old);
1323 msginfo_update.msginfo = msginfo;
1324 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1325 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1328 /* Tmp flags hanlding */
1329 msginfo->flags.tmp_flags |= tmp_flags;
1332 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1335 MsgInfoUpdate msginfo_update;
1336 MsgPermFlags perm_flags_new, perm_flags_old;
1338 g_return_if_fail(msginfo != NULL);
1339 item = msginfo->folder;
1340 g_return_if_fail(item != NULL);
1342 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1344 /* Perm Flags handling */
1345 perm_flags_old = msginfo->flags.perm_flags;
1346 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1348 if (perm_flags_old != perm_flags_new) {
1349 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1351 update_folder_msg_counts(item, msginfo, perm_flags_old);
1353 msginfo_update.msginfo = msginfo;
1354 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1355 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1358 /* Tmp flags hanlding */
1359 msginfo->flags.tmp_flags &= ~tmp_flags;
1363 *\brief check for flags (e.g. mark) in prior msgs of current thread
1365 *\param info Current message
1366 *\param perm_flags Flags to be checked
1367 *\param parentmsgs Hash of prior msgs to avoid loops
1369 *\return gboolean TRUE if perm_flags are found
1371 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1372 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1376 g_return_val_if_fail(info != NULL, FALSE);
1378 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1379 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1381 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1382 procmsg_msginfo_free(tmp);
1384 } else if (tmp != NULL) {
1387 if (g_hash_table_lookup(parentmsgs, info)) {
1388 debug_print("loop detected: %s%c%d\n",
1389 folder_item_get_path(info->folder),
1390 G_DIR_SEPARATOR, info->msgnum);
1393 g_hash_table_insert(parentmsgs, info, "1");
1394 result = procmsg_msg_has_flagged_parent_real(
1395 tmp, perm_flags, parentmsgs);
1397 procmsg_msginfo_free(tmp);
1407 *\brief Callback for cleaning up hash of parentmsgs
1409 gboolean parentmsgs_hash_remove(gpointer key,
1417 *\brief Set up list of parentmsgs
1418 * See procmsg_msg_has_flagged_parent_real()
1420 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1423 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1425 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1426 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1427 g_hash_table_destroy(parentmsgs);
1432 *\brief Check if msgs prior in thread are marked
1433 * See procmsg_msg_has_flagged_parent_real()
1435 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1437 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1441 GSList *procmsg_find_children_func(MsgInfo *info,
1442 GSList *children, GSList *all)
1446 g_return_val_if_fail(info!=NULL, children);
1447 if (info->msgid == NULL)
1450 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1451 MsgInfo *tmp = (MsgInfo *)cur->data;
1452 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1453 /* Check if message is already in the list */
1454 if ((children == NULL) ||
1455 (g_slist_index(children, tmp) == -1)) {
1456 children = g_slist_prepend(children,
1457 procmsg_msginfo_new_ref(tmp));
1458 children = procmsg_find_children_func(tmp,
1467 GSList *procmsg_find_children (MsgInfo *info)
1472 g_return_val_if_fail(info!=NULL, NULL);
1473 all = folder_item_get_msg_list(info->folder);
1474 children = procmsg_find_children_func(info, NULL, all);
1475 if (children != NULL) {
1476 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1477 /* this will not free the used pointers
1478 created with procmsg_msginfo_new_ref */
1479 procmsg_msginfo_free((MsgInfo *)cur->data);
1487 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1489 GSList *children = procmsg_find_children(info);
1491 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1492 MsgInfo *tmp = (MsgInfo *)cur->data;
1493 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1495 info->folder->unreadmarked_msgs++;
1497 info->folder->unreadmarked_msgs--;
1498 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1500 procmsg_msginfo_free(tmp);
1502 g_slist_free(children);
1506 * Set the destination folder for a copy or move operation
1508 * \param msginfo The message which's destination folder is changed
1509 * \param to_folder The destination folder for the operation
1511 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1513 if(msginfo->to_folder != NULL) {
1514 msginfo->to_folder->op_count--;
1515 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1517 msginfo->to_folder = to_folder;
1518 if(to_folder != NULL) {
1519 to_folder->op_count++;
1520 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1525 * Apply filtering actions to the msginfo
1527 * \param msginfo The MsgInfo describing the message that should be filtered
1528 * \return TRUE if the message was moved and MsgInfo is now invalid,
1531 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1533 MailFilteringData mail_filtering_data;
1535 mail_filtering_data.msginfo = msginfo;
1536 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1539 /* filter if enabled in prefs or move to inbox if not */
1540 if((global_processing != NULL) &&
1541 filter_message_by_msginfo(global_processing, msginfo))