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(MsgInfoList *mlist)
387 GSList *file_list = NULL;
391 while (mlist != NULL) {
392 msginfo = (MsgInfo *)mlist->data;
393 file = procmsg_get_message_file(msginfo);
395 slist_free_strings(file_list);
396 g_slist_free(file_list);
399 file_list = g_slist_prepend(file_list, file);
403 file_list = g_slist_reverse(file_list);
408 void procmsg_message_file_list_free(MsgInfoList *file_list)
411 MsgFileInfo *fileinfo;
413 for (cur = file_list; cur != NULL; cur = cur->next) {
414 fileinfo = (MsgFileInfo *)cur->data;
415 g_free(fileinfo->file);
416 g_free(fileinfo->flags);
420 g_slist_free(file_list);
423 FILE *procmsg_open_message(MsgInfo *msginfo)
428 g_return_val_if_fail(msginfo != NULL, NULL);
430 file = procmsg_get_message_file_path(msginfo);
431 g_return_val_if_fail(file != NULL, NULL);
433 if (!is_file_exist(file)) {
435 file = procmsg_get_message_file(msginfo);
436 g_return_val_if_fail(file != NULL, NULL);
439 if ((fp = fopen(file, "rb")) == NULL) {
440 FILE_OP_ERROR(file, "fopen");
447 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
450 while (fgets(buf, sizeof(buf), fp) != NULL)
451 if (buf[0] == '\r' || buf[0] == '\n') break;
458 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
464 g_return_val_if_fail(msginfo != NULL, NULL);
466 if (mimeinfo) *mimeinfo = NULL;
468 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
470 mimeinfo_ = procmime_scan_mime_header(fp);
476 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
477 rfc2015_is_encrypted(mimeinfo_)) {
478 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
481 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
482 !msginfo->plaintext_file &&
483 !msginfo->decryption_failed) {
485 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
486 if (msginfo->plaintext_file &&
487 !msginfo->decryption_failed) {
489 procmime_mimeinfo_free_all(mimeinfo_);
490 if ((fp = procmsg_open_message(msginfo)) == NULL)
492 mimeinfo_ = procmime_scan_mime_header(fp);
498 if (fseek(fp, fpos, SEEK_SET) < 0)
503 if (mimeinfo) *mimeinfo = mimeinfo_;
508 gboolean procmsg_msg_exist(MsgInfo *msginfo)
513 if (!msginfo) return FALSE;
515 path = folder_item_get_path(msginfo->folder);
517 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
523 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
524 PrefsFilterType type)
526 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
527 {"X-ML-Name:", NULL, TRUE},
528 {"X-List:", NULL, TRUE},
529 {"X-Mailing-list:", NULL, TRUE},
530 {"List-Id:", NULL, TRUE},
531 {"X-Sequence:", NULL, TRUE},
532 {NULL, NULL, FALSE}};
538 H_X_MAILING_LIST = 3,
545 g_return_if_fail(msginfo != NULL);
546 g_return_if_fail(header != NULL);
547 g_return_if_fail(key != NULL);
556 if ((fp = procmsg_open_message(msginfo)) == NULL)
558 procheader_get_header_fields(fp, hentry);
561 #define SET_FILTER_KEY(hstr, idx) \
563 *header = g_strdup(hstr); \
564 *key = hentry[idx].body; \
565 hentry[idx].body = NULL; \
568 if (hentry[H_X_BEENTHERE].body != NULL) {
569 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
570 } else if (hentry[H_X_ML_NAME].body != NULL) {
571 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
572 } else if (hentry[H_X_LIST].body != NULL) {
573 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
574 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
575 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
576 } else if (hentry[H_LIST_ID].body != NULL) {
577 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
578 extract_list_id_str(*key);
579 } else if (hentry[H_X_SEQUENCE].body != NULL) {
582 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
585 while (*p != '\0' && !isspace(*p)) p++;
586 while (isspace(*p)) p++;
593 } else if (msginfo->subject) {
594 *header = g_strdup("subject");
595 *key = g_strdup(msginfo->subject);
598 #undef SET_FILTER_KEY
600 g_free(hentry[H_X_BEENTHERE].body);
601 hentry[H_X_BEENTHERE].body = NULL;
602 g_free(hentry[H_X_ML_NAME].body);
603 hentry[H_X_ML_NAME].body = NULL;
604 g_free(hentry[H_X_LIST].body);
605 hentry[H_X_LIST].body = NULL;
606 g_free(hentry[H_X_MAILING_LIST].body);
607 hentry[H_X_MAILING_LIST].body = NULL;
608 g_free(hentry[H_LIST_ID].body);
609 hentry[H_LIST_ID].body = NULL;
613 *header = g_strdup("from");
614 *key = g_strdup(msginfo->from);
617 *header = g_strdup("to");
618 *key = g_strdup(msginfo->to);
620 case FILTER_BY_SUBJECT:
621 *header = g_strdup("subject");
622 *key = g_strdup(msginfo->subject);
629 void procmsg_empty_trash(void)
634 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
635 trash = FOLDER(cur->data)->trash;
636 if (trash && trash->total_msgs > 0)
637 folder_item_remove_all_msg(trash);
642 *\brief Send messages in queue
644 *\param queue Queue folder to process
645 *\param save_msgs Unused
647 *\return Number of messages sent, negative if an error occurred
648 * positive if no error occurred
650 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
652 gint ret = 1, count = 0;
656 queue = folder_get_default_queue();
657 g_return_val_if_fail(queue != NULL, -1);
659 folder_item_scan(queue);
660 list = folder_item_get_msg_list(queue);
662 for (elem = list; elem != NULL; elem = elem->next) {
666 msginfo = (MsgInfo *)(elem->data);
667 if (!MSG_IS_LOCKED(msginfo->flags)) {
668 file = folder_item_fetch_msg(queue, msginfo->msgnum);
670 if (procmsg_send_message_queue(file) < 0) {
671 g_warning("Sending queued message %d failed.\n",
676 * We save in procmsg_send_message_queue because
677 * we need the destination folder from the queue
681 procmsg_save_to_outbox
682 (queue->folder->outbox,
686 folder_item_remove_msg(queue, msginfo->msgnum);
691 /* FIXME: supposedly if only one message is locked, and queue
692 * is being flushed, the following free says something like
693 * "freeing msg ## in folder (nil)". */
694 procmsg_msginfo_free(msginfo);
700 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
705 if ((fp = fopen(in, "rb")) == NULL) {
706 FILE_OP_ERROR(in, "fopen");
709 if ((outfp = fopen(out, "wb")) == NULL) {
710 FILE_OP_ERROR(out, "fopen");
714 while (fgets(buf, sizeof(buf), fp) != NULL)
715 if (buf[0] == '\r' || buf[0] == '\n') break;
716 while (fgets(buf, sizeof(buf), fp) != NULL)
723 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
729 debug_print("saving sent message...\n");
732 outbox = folder_get_default_outbox();
733 g_return_val_if_fail(outbox != NULL, -1);
735 /* remove queueing headers */
737 gchar tmp[MAXPATHLEN + 1];
739 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
740 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
742 if (procmsg_remove_special_headers(file, tmp) !=0)
745 folder_item_scan(outbox);
746 if ((num = folder_item_add_msg(outbox, tmp, NULL, TRUE)) < 0) {
747 g_warning("can't save message\n");
752 folder_item_scan(outbox);
753 if ((num = folder_item_add_msg(outbox, file, NULL, FALSE)) < 0) {
754 g_warning("can't save message\n");
759 msginfo = folder_item_get_msginfo(outbox, num);
760 if (msginfo != NULL) {
761 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
762 procmsg_msginfo_free(msginfo);
764 folder_item_update(outbox, TRUE);
769 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
771 static const gchar *def_cmd = "lpr %s";
778 g_return_if_fail(msginfo);
780 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
781 g_warning("Can't get text part\n");
785 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
786 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
788 if ((prfp = fopen(prtmp, "wb")) == NULL) {
789 FILE_OP_ERROR(prtmp, "fopen");
795 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
796 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
797 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
798 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
799 if (msginfo->newsgroups)
800 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
801 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
804 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
810 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
812 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
815 g_warning("Print command line is invalid: `%s'\n",
817 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
823 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
827 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
834 MsgInfo *procmsg_msginfo_new(void)
838 newmsginfo = g_new0(MsgInfo, 1);
839 newmsginfo->refcnt = 1;
844 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
848 if (msginfo == NULL) return NULL;
850 newmsginfo = g_new0(MsgInfo, 1);
852 newmsginfo->refcnt = 1;
854 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
855 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
856 g_strdup(msginfo->mmb) : NULL
880 MEMBDUP(dispositionnotificationto);
881 MEMBDUP(returnreceiptto);
885 MEMBCOPY(threadscore);
890 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
893 MsgInfo *full_msginfo;
897 if (msginfo == NULL) return NULL;
900 * In Claws we simply return a new reference to the same msginfo.
901 * otherwise the new msginfo has wrong flags and causes incorrect
902 * msgcounts... TODO: fill in data from full_msginfo into msginfo,
903 * we can then keep the new data in the cache
905 return procmsg_msginfo_new_ref(msginfo);
907 file = procmsg_get_message_file(msginfo);
909 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
913 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
915 if (!full_msginfo) return NULL;
917 full_msginfo->msgnum = msginfo->msgnum;
918 full_msginfo->size = msginfo->size;
919 full_msginfo->mtime = msginfo->mtime;
920 full_msginfo->folder = msginfo->folder;
922 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
923 full_msginfo->decryption_failed = msginfo->decryption_failed;
925 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
931 void procmsg_msginfo_free(MsgInfo *msginfo)
933 if (msginfo == NULL) return;
936 if (msginfo->refcnt > 0)
939 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
941 if (msginfo->to_folder) {
942 msginfo->to_folder->op_count--;
943 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
946 g_free(msginfo->fromspace);
947 g_free(msginfo->references);
948 g_free(msginfo->returnreceiptto);
949 g_free(msginfo->dispositionnotificationto);
950 g_free(msginfo->xface);
952 g_free(msginfo->fromname);
954 g_free(msginfo->date);
955 g_free(msginfo->from);
958 g_free(msginfo->newsgroups);
959 g_free(msginfo->subject);
960 g_free(msginfo->msgid);
961 g_free(msginfo->inreplyto);
962 g_free(msginfo->xref);
967 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
971 memusage += sizeof(MsgInfo);
972 if (msginfo->fromname)
973 memusage += strlen(msginfo->fromname);
975 memusage += strlen(msginfo->date);
977 memusage += strlen(msginfo->from);
979 memusage += strlen(msginfo->to);
981 memusage += strlen(msginfo->cc);
982 if (msginfo->newsgroups)
983 memusage += strlen(msginfo->newsgroups);
984 if (msginfo->subject)
985 memusage += strlen(msginfo->subject);
987 memusage += strlen(msginfo->msgid);
988 if (msginfo->inreplyto)
989 memusage += strlen(msginfo->inreplyto);
991 memusage += strlen(msginfo->xface);
992 if (msginfo->dispositionnotificationto)
993 memusage += strlen(msginfo->dispositionnotificationto);
994 if (msginfo->returnreceiptto)
995 memusage += strlen(msginfo->returnreceiptto);
996 if (msginfo->references)
997 memusage += strlen(msginfo->references);
998 if (msginfo->fromspace)
999 memusage += strlen(msginfo->fromspace);
1004 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1006 const MsgInfo *msginfo1 = a;
1007 const MsgInfo *msginfo2 = b;
1014 return msginfo1->msgnum - msginfo2->msgnum;
1023 Q_MAIL_ACCOUNT_ID = 4,
1024 Q_NEWS_ACCOUNT_ID = 5,
1025 Q_SAVE_COPY_FOLDER = 6,
1026 Q_REPLY_MESSAGE_ID = 7,
1027 Q_FWD_MESSAGE_ID = 8
1030 gint procmsg_send_message_queue(const gchar *file)
1032 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1033 {"SSV:", NULL, FALSE},
1034 {"R:", NULL, FALSE},
1035 {"NG:", NULL, FALSE},
1036 {"MAID:", NULL, FALSE},
1037 {"NAID:", NULL, FALSE},
1038 {"SCF:", NULL, FALSE},
1039 {"RMID:", NULL, FALSE},
1040 {"FMID:", NULL, FALSE},
1041 {NULL, NULL, FALSE}};
1044 gint mailval = 0, newsval = 0;
1046 gchar *smtpserver = NULL;
1047 GSList *to_list = NULL;
1048 GSList *newsgroup_list = NULL;
1049 gchar *savecopyfolder = NULL;
1050 gchar *replymessageid = NULL;
1051 gchar *fwdmessageid = NULL;
1052 gchar buf[BUFFSIZE];
1054 PrefsAccount *mailac = NULL, *newsac = NULL;
1057 g_return_val_if_fail(file != NULL, -1);
1059 if ((fp = fopen(file, "rb")) == NULL) {
1060 FILE_OP_ERROR(file, "fopen");
1064 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1066 gchar *p = buf + strlen(qentry[hnum].name);
1070 if (!from) from = g_strdup(p);
1073 if (!smtpserver) smtpserver = g_strdup(p);
1076 to_list = address_list_append(to_list, p);
1079 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1081 case Q_MAIL_ACCOUNT_ID:
1082 mailac = account_find_from_id(atoi(p));
1084 case Q_NEWS_ACCOUNT_ID:
1085 newsac = account_find_from_id(atoi(p));
1087 case Q_SAVE_COPY_FOLDER:
1088 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1090 case Q_REPLY_MESSAGE_ID:
1091 if (!replymessageid) replymessageid = g_strdup(p);
1093 case Q_FWD_MESSAGE_ID:
1094 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1098 filepos = ftell(fp);
1101 debug_print("Sending message by mail\n");
1103 g_warning("Queued message header is broken.\n");
1105 } else if (mailac && mailac->use_mail_command &&
1106 mailac->mail_command && (* mailac->mail_command)) {
1107 mailval = send_message_local(mailac->mail_command, fp);
1109 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1110 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1114 mailac = account_find_from_smtp_server(from, smtpserver);
1116 g_warning("Account not found. "
1117 "Using current account...\n");
1118 mailac = cur_account;
1123 mailval = send_message_smtp(mailac, to_list, fp);
1125 PrefsAccount tmp_ac;
1127 g_warning("Account not found.\n");
1129 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1130 tmp_ac.address = from;
1131 tmp_ac.smtp_server = smtpserver;
1132 tmp_ac.smtpport = SMTP_PORT;
1133 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1138 fseek(fp, filepos, SEEK_SET);
1139 if (newsgroup_list && (newsval == 0)) {
1144 /* write to temporary file */
1145 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1146 G_DIR_SEPARATOR, (gint)file);
1147 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1148 FILE_OP_ERROR(tmp, "fopen");
1150 alertpanel_error(_("Could not create temporary file for news sending."));
1152 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1153 FILE_OP_ERROR(tmp, "chmod");
1154 g_warning("can't change file mode\n");
1157 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1158 if (fputs(buf, tmpfp) == EOF) {
1159 FILE_OP_ERROR(tmp, "fputs");
1161 alertpanel_error(_("Error when writing temporary file for news sending."));
1167 debug_print("Sending message by news\n");
1169 folder = FOLDER(newsac->folder);
1171 newsval = news_post(folder, tmp);
1173 alertpanel_error(_("Error occurred while posting the message to %s ."),
1174 newsac->nntp_server);
1182 slist_free_strings(to_list);
1183 g_slist_free(to_list);
1184 slist_free_strings(newsgroup_list);
1185 g_slist_free(newsgroup_list);
1190 /* save message to outbox */
1191 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1194 debug_print("saving sent message...\n");
1196 outbox = folder_find_item_from_identifier(savecopyfolder);
1198 outbox = folder_get_default_outbox();
1200 procmsg_save_to_outbox(outbox, file, TRUE);
1203 if (replymessageid != NULL || fwdmessageid != NULL) {
1207 if (replymessageid != NULL)
1208 tokens = g_strsplit(replymessageid, "\x7f", 0);
1210 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1211 item = folder_find_item_from_identifier(tokens[0]);
1213 /* check if queued message has valid folder and message id */
1214 if (item != NULL && tokens[2] != NULL) {
1217 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1219 /* check if referring message exists and has a message id */
1220 if ((msginfo != NULL) &&
1221 (msginfo->msgid != NULL) &&
1222 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1223 procmsg_msginfo_free(msginfo);
1227 if (msginfo == NULL) {
1228 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1231 if (msginfo != NULL) {
1232 if (replymessageid != NULL) {
1233 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1234 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1237 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1238 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1240 procmsg_msginfo_free(msginfo);
1246 g_free(savecopyfolder);
1247 g_free(replymessageid);
1248 g_free(fwdmessageid);
1250 return (newsval != 0 ? newsval : mailval);
1253 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1255 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1258 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1262 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1267 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1268 item->unread_msgs++;
1269 if (procmsg_msg_has_marked_parent(msginfo))
1270 item->unreadmarked_msgs++;
1273 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1274 item->unread_msgs--;
1275 if (procmsg_msg_has_marked_parent(msginfo))
1276 item->unreadmarked_msgs--;
1280 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1281 procmsg_update_unread_children(msginfo, TRUE);
1284 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1285 procmsg_update_unread_children(msginfo, FALSE);
1289 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1292 MsgInfoUpdate msginfo_update;
1293 MsgPermFlags perm_flags_new, perm_flags_old;
1295 g_return_if_fail(msginfo != NULL);
1296 item = msginfo->folder;
1297 g_return_if_fail(item != NULL);
1299 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1301 /* Perm Flags handling */
1302 perm_flags_old = msginfo->flags.perm_flags;
1303 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1304 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1305 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1308 if (perm_flags_old != perm_flags_new) {
1309 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1311 update_folder_msg_counts(item, msginfo, perm_flags_old);
1313 msginfo_update.msginfo = msginfo;
1314 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1315 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1318 /* Tmp flags hanlding */
1319 msginfo->flags.tmp_flags |= tmp_flags;
1322 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1325 MsgInfoUpdate msginfo_update;
1326 MsgPermFlags perm_flags_new, perm_flags_old;
1328 g_return_if_fail(msginfo != NULL);
1329 item = msginfo->folder;
1330 g_return_if_fail(item != NULL);
1332 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1334 /* Perm Flags handling */
1335 perm_flags_old = msginfo->flags.perm_flags;
1336 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1338 if (perm_flags_old != perm_flags_new) {
1339 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1341 update_folder_msg_counts(item, msginfo, perm_flags_old);
1343 msginfo_update.msginfo = msginfo;
1344 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1345 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1348 /* Tmp flags hanlding */
1349 msginfo->flags.tmp_flags &= ~tmp_flags;
1353 *\brief check for flags (e.g. mark) in prior msgs of current thread
1355 *\param info Current message
1356 *\param perm_flags Flags to be checked
1357 *\param parentmsgs Hash of prior msgs to avoid loops
1359 *\return gboolean TRUE if perm_flags are found
1361 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1362 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1366 g_return_val_if_fail(info != NULL, FALSE);
1368 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1369 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1371 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1372 procmsg_msginfo_free(tmp);
1374 } else if (tmp != NULL) {
1377 if (g_hash_table_lookup(parentmsgs, info)) {
1378 debug_print("loop detected: %s%c%d\n",
1379 folder_item_get_path(info->folder),
1380 G_DIR_SEPARATOR, info->msgnum);
1383 g_hash_table_insert(parentmsgs, info, "1");
1384 result = procmsg_msg_has_flagged_parent_real(
1385 tmp, perm_flags, parentmsgs);
1387 procmsg_msginfo_free(tmp);
1397 *\brief Callback for cleaning up hash of parentmsgs
1399 gboolean parentmsgs_hash_remove(gpointer key,
1407 *\brief Set up list of parentmsgs
1408 * See procmsg_msg_has_flagged_parent_real()
1410 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1413 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1415 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1416 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1417 g_hash_table_destroy(parentmsgs);
1422 *\brief Check if msgs prior in thread are marked
1423 * See procmsg_msg_has_flagged_parent_real()
1425 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1427 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1431 GSList *procmsg_find_children_func(MsgInfo *info,
1432 GSList *children, GSList *all)
1436 g_return_val_if_fail(info!=NULL, children);
1437 if (info->msgid == NULL)
1440 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1441 MsgInfo *tmp = (MsgInfo *)cur->data;
1442 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1443 /* Check if message is already in the list */
1444 if ((children == NULL) ||
1445 (g_slist_index(children, tmp) == -1)) {
1446 children = g_slist_prepend(children,
1447 procmsg_msginfo_new_ref(tmp));
1448 children = procmsg_find_children_func(tmp,
1457 GSList *procmsg_find_children (MsgInfo *info)
1462 g_return_val_if_fail(info!=NULL, NULL);
1463 all = folder_item_get_msg_list(info->folder);
1464 children = procmsg_find_children_func(info, NULL, all);
1465 if (children != NULL) {
1466 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1467 /* this will not free the used pointers
1468 created with procmsg_msginfo_new_ref */
1469 procmsg_msginfo_free((MsgInfo *)cur->data);
1477 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1479 GSList *children = procmsg_find_children(info);
1481 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1482 MsgInfo *tmp = (MsgInfo *)cur->data;
1483 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1485 info->folder->unreadmarked_msgs++;
1487 info->folder->unreadmarked_msgs--;
1488 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1490 procmsg_msginfo_free(tmp);
1492 g_slist_free(children);
1496 * Set the destination folder for a copy or move operation
1498 * \param msginfo The message which's destination folder is changed
1499 * \param to_folder The destination folder for the operation
1501 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1503 if(msginfo->to_folder != NULL) {
1504 msginfo->to_folder->op_count--;
1505 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1507 msginfo->to_folder = to_folder;
1508 if(to_folder != NULL) {
1509 to_folder->op_count++;
1510 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1515 * Apply filtering actions to the msginfo
1517 * \param msginfo The MsgInfo describing the message that should be filtered
1518 * \return TRUE if the message was moved and MsgInfo is now invalid,
1521 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1523 MailFilteringData mail_filtering_data;
1525 mail_filtering_data.msginfo = msginfo;
1526 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1529 /* filter if enabled in prefs or move to inbox if not */
1530 if((global_processing != NULL) &&
1531 filter_message_by_msginfo(global_processing, msginfo))