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 FILE *procmsg_open_message(MsgInfo *msginfo)
390 g_return_val_if_fail(msginfo != NULL, NULL);
392 file = procmsg_get_message_file_path(msginfo);
393 g_return_val_if_fail(file != NULL, NULL);
395 if (!is_file_exist(file)) {
397 file = procmsg_get_message_file(msginfo);
398 g_return_val_if_fail(file != NULL, NULL);
401 if ((fp = fopen(file, "rb")) == NULL) {
402 FILE_OP_ERROR(file, "fopen");
409 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
412 while (fgets(buf, sizeof(buf), fp) != NULL)
413 if (buf[0] == '\r' || buf[0] == '\n') break;
420 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
426 g_return_val_if_fail(msginfo != NULL, NULL);
428 if (mimeinfo) *mimeinfo = NULL;
430 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
432 mimeinfo_ = procmime_scan_mime_header(fp);
438 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
439 rfc2015_is_encrypted(mimeinfo_)) {
440 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
443 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
444 !msginfo->plaintext_file &&
445 !msginfo->decryption_failed) {
447 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
448 if (msginfo->plaintext_file &&
449 !msginfo->decryption_failed) {
451 procmime_mimeinfo_free_all(mimeinfo_);
452 if ((fp = procmsg_open_message(msginfo)) == NULL)
454 mimeinfo_ = procmime_scan_mime_header(fp);
460 if (fseek(fp, fpos, SEEK_SET) < 0)
465 if (mimeinfo) *mimeinfo = mimeinfo_;
470 gboolean procmsg_msg_exist(MsgInfo *msginfo)
475 if (!msginfo) return FALSE;
477 path = folder_item_get_path(msginfo->folder);
479 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
485 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
486 PrefsFilterType type)
488 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
489 {"X-ML-Name:", NULL, TRUE},
490 {"X-List:", NULL, TRUE},
491 {"X-Mailing-list:", NULL, TRUE},
492 {"List-Id:", NULL, TRUE},
493 {"X-Sequence:", NULL, TRUE},
494 {NULL, NULL, FALSE}};
500 H_X_MAILING_LIST = 3,
507 g_return_if_fail(msginfo != NULL);
508 g_return_if_fail(header != NULL);
509 g_return_if_fail(key != NULL);
518 if ((fp = procmsg_open_message(msginfo)) == NULL)
520 procheader_get_header_fields(fp, hentry);
523 #define SET_FILTER_KEY(hstr, idx) \
525 *header = g_strdup(hstr); \
526 *key = hentry[idx].body; \
527 hentry[idx].body = NULL; \
530 if (hentry[H_X_BEENTHERE].body != NULL) {
531 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
532 } else if (hentry[H_X_ML_NAME].body != NULL) {
533 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
534 } else if (hentry[H_X_LIST].body != NULL) {
535 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
536 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
537 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
538 } else if (hentry[H_LIST_ID].body != NULL) {
539 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
540 extract_list_id_str(*key);
541 } else if (hentry[H_X_SEQUENCE].body != NULL) {
544 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
547 while (*p != '\0' && !isspace(*p)) p++;
548 while (isspace(*p)) p++;
555 } else if (msginfo->subject) {
556 *header = g_strdup("subject");
557 *key = g_strdup(msginfo->subject);
560 #undef SET_FILTER_KEY
562 g_free(hentry[H_X_BEENTHERE].body);
563 hentry[H_X_BEENTHERE].body = NULL;
564 g_free(hentry[H_X_ML_NAME].body);
565 hentry[H_X_ML_NAME].body = NULL;
566 g_free(hentry[H_X_LIST].body);
567 hentry[H_X_LIST].body = NULL;
568 g_free(hentry[H_X_MAILING_LIST].body);
569 hentry[H_X_MAILING_LIST].body = NULL;
570 g_free(hentry[H_LIST_ID].body);
571 hentry[H_LIST_ID].body = NULL;
575 *header = g_strdup("from");
576 *key = g_strdup(msginfo->from);
579 *header = g_strdup("to");
580 *key = g_strdup(msginfo->to);
582 case FILTER_BY_SUBJECT:
583 *header = g_strdup("subject");
584 *key = g_strdup(msginfo->subject);
591 void procmsg_empty_trash(void)
596 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
597 trash = FOLDER(cur->data)->trash;
598 if (trash && trash->total_msgs > 0)
599 folder_item_remove_all_msg(trash);
604 *\brief Send messages in queue
606 *\param queue Queue folder to process
607 *\param save_msgs Unused
609 *\return Number of messages sent, negative if an error occurred
610 * positive if no error occurred
612 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
614 gint ret = 1, count = 0;
618 queue = folder_get_default_queue();
619 g_return_val_if_fail(queue != NULL, -1);
621 folder_item_scan(queue);
622 list = folder_item_get_msg_list(queue);
624 for (elem = list; elem != NULL; elem = elem->next) {
628 msginfo = (MsgInfo *)(elem->data);
629 if (!MSG_IS_LOCKED(msginfo->flags)) {
630 file = folder_item_fetch_msg(queue, msginfo->msgnum);
632 if (procmsg_send_message_queue(file) < 0) {
633 g_warning("Sending queued message %d failed.\n",
638 * We save in procmsg_send_message_queue because
639 * we need the destination folder from the queue
643 procmsg_save_to_outbox
644 (queue->folder->outbox,
648 folder_item_remove_msg(queue, msginfo->msgnum);
653 /* FIXME: supposedly if only one message is locked, and queue
654 * is being flushed, the following free says something like
655 * "freeing msg ## in folder (nil)". */
656 procmsg_msginfo_free(msginfo);
662 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
667 if ((fp = fopen(in, "rb")) == NULL) {
668 FILE_OP_ERROR(in, "fopen");
671 if ((outfp = fopen(out, "wb")) == NULL) {
672 FILE_OP_ERROR(out, "fopen");
676 while (fgets(buf, sizeof(buf), fp) != NULL)
677 if (buf[0] == '\r' || buf[0] == '\n') break;
678 while (fgets(buf, sizeof(buf), fp) != NULL)
685 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
691 debug_print("saving sent message...\n");
694 outbox = folder_get_default_outbox();
695 g_return_val_if_fail(outbox != NULL, -1);
697 /* remove queueing headers */
699 gchar tmp[MAXPATHLEN + 1];
701 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
702 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
704 if (procmsg_remove_special_headers(file, tmp) !=0)
707 folder_item_scan(outbox);
708 if ((num = folder_item_add_msg(outbox, tmp, TRUE)) < 0) {
709 g_warning("can't save message\n");
714 folder_item_scan(outbox);
715 if ((num = folder_item_add_msg(outbox, file, FALSE)) < 0) {
716 g_warning("can't save message\n");
721 msginfo = folder_item_get_msginfo(outbox, num);
722 if (msginfo != NULL) {
723 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
724 procmsg_msginfo_free(msginfo);
726 folder_item_update(outbox, TRUE);
731 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
733 static const gchar *def_cmd = "lpr %s";
740 g_return_if_fail(msginfo);
742 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
743 g_warning("Can't get text part\n");
747 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
748 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
750 if ((prfp = fopen(prtmp, "wb")) == NULL) {
751 FILE_OP_ERROR(prtmp, "fopen");
757 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
758 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
759 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
760 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
761 if (msginfo->newsgroups)
762 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
763 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
766 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
772 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
774 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
777 g_warning("Print command line is invalid: `%s'\n",
779 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
785 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
789 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
796 MsgInfo *procmsg_msginfo_new(void)
800 newmsginfo = g_new0(MsgInfo, 1);
801 newmsginfo->refcnt = 1;
806 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
810 if (msginfo == NULL) return NULL;
812 newmsginfo = g_new0(MsgInfo, 1);
814 newmsginfo->refcnt = 1;
816 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
817 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
818 g_strdup(msginfo->mmb) : NULL
842 MEMBDUP(dispositionnotificationto);
843 MEMBDUP(returnreceiptto);
847 MEMBCOPY(threadscore);
852 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
855 MsgInfo *full_msginfo;
859 if (msginfo == NULL) return NULL;
862 * In Claws we simply return a new reference to the same msginfo.
863 * otherwise the new msginfo has wrong flags and causes incorrect
864 * msgcounts... TODO: fill in data from full_msginfo into msginfo,
865 * we can then keep the new data in the cache
867 return procmsg_msginfo_new_ref(msginfo);
869 file = procmsg_get_message_file(msginfo);
871 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
875 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
877 if (!full_msginfo) return NULL;
879 full_msginfo->msgnum = msginfo->msgnum;
880 full_msginfo->size = msginfo->size;
881 full_msginfo->mtime = msginfo->mtime;
882 full_msginfo->folder = msginfo->folder;
884 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
885 full_msginfo->decryption_failed = msginfo->decryption_failed;
887 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
893 void procmsg_msginfo_free(MsgInfo *msginfo)
895 if (msginfo == NULL) return;
898 if (msginfo->refcnt > 0)
901 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
903 if (msginfo->to_folder) {
904 msginfo->to_folder->op_count--;
905 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
908 g_free(msginfo->fromspace);
909 g_free(msginfo->references);
910 g_free(msginfo->returnreceiptto);
911 g_free(msginfo->dispositionnotificationto);
912 g_free(msginfo->xface);
914 g_free(msginfo->fromname);
916 g_free(msginfo->date);
917 g_free(msginfo->from);
920 g_free(msginfo->newsgroups);
921 g_free(msginfo->subject);
922 g_free(msginfo->msgid);
923 g_free(msginfo->inreplyto);
924 g_free(msginfo->xref);
929 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
933 memusage += sizeof(MsgInfo);
934 if (msginfo->fromname)
935 memusage += strlen(msginfo->fromname);
937 memusage += strlen(msginfo->date);
939 memusage += strlen(msginfo->from);
941 memusage += strlen(msginfo->to);
943 memusage += strlen(msginfo->cc);
944 if (msginfo->newsgroups)
945 memusage += strlen(msginfo->newsgroups);
946 if (msginfo->subject)
947 memusage += strlen(msginfo->subject);
949 memusage += strlen(msginfo->msgid);
950 if (msginfo->inreplyto)
951 memusage += strlen(msginfo->inreplyto);
953 memusage += strlen(msginfo->xface);
954 if (msginfo->dispositionnotificationto)
955 memusage += strlen(msginfo->dispositionnotificationto);
956 if (msginfo->returnreceiptto)
957 memusage += strlen(msginfo->returnreceiptto);
958 if (msginfo->references)
959 memusage += strlen(msginfo->references);
960 if (msginfo->fromspace)
961 memusage += strlen(msginfo->fromspace);
966 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
968 const MsgInfo *msginfo1 = a;
969 const MsgInfo *msginfo2 = b;
976 return msginfo1->msgnum - msginfo2->msgnum;
985 Q_MAIL_ACCOUNT_ID = 4,
986 Q_NEWS_ACCOUNT_ID = 5,
987 Q_SAVE_COPY_FOLDER = 6,
988 Q_REPLY_MESSAGE_ID = 7,
992 gint procmsg_send_message_queue(const gchar *file)
994 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
995 {"SSV:", NULL, FALSE},
997 {"NG:", NULL, FALSE},
998 {"MAID:", NULL, FALSE},
999 {"NAID:", NULL, FALSE},
1000 {"SCF:", NULL, FALSE},
1001 {"RMID:", NULL, FALSE},
1002 {"FMID:", NULL, FALSE},
1003 {NULL, NULL, FALSE}};
1006 gint mailval = 0, newsval = 0;
1008 gchar *smtpserver = NULL;
1009 GSList *to_list = NULL;
1010 GSList *newsgroup_list = NULL;
1011 gchar *savecopyfolder = NULL;
1012 gchar *replymessageid = NULL;
1013 gchar *fwdmessageid = NULL;
1014 gchar buf[BUFFSIZE];
1016 PrefsAccount *mailac = NULL, *newsac = NULL;
1019 g_return_val_if_fail(file != NULL, -1);
1021 if ((fp = fopen(file, "rb")) == NULL) {
1022 FILE_OP_ERROR(file, "fopen");
1026 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1028 gchar *p = buf + strlen(qentry[hnum].name);
1032 if (!from) from = g_strdup(p);
1035 if (!smtpserver) smtpserver = g_strdup(p);
1038 to_list = address_list_append(to_list, p);
1041 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1043 case Q_MAIL_ACCOUNT_ID:
1044 mailac = account_find_from_id(atoi(p));
1046 case Q_NEWS_ACCOUNT_ID:
1047 newsac = account_find_from_id(atoi(p));
1049 case Q_SAVE_COPY_FOLDER:
1050 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1052 case Q_REPLY_MESSAGE_ID:
1053 if (!replymessageid) replymessageid = g_strdup(p);
1055 case Q_FWD_MESSAGE_ID:
1056 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1060 filepos = ftell(fp);
1063 debug_print("Sending message by mail\n");
1065 g_warning("Queued message header is broken.\n");
1067 } else if (mailac && mailac->use_mail_command &&
1068 mailac->mail_command && (* mailac->mail_command)) {
1069 mailval = send_message_local(mailac->mail_command, fp);
1071 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1072 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1076 mailac = account_find_from_smtp_server(from, smtpserver);
1078 g_warning("Account not found. "
1079 "Using current account...\n");
1080 mailac = cur_account;
1085 mailval = send_message_smtp(mailac, to_list, fp);
1087 PrefsAccount tmp_ac;
1089 g_warning("Account not found.\n");
1091 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1092 tmp_ac.address = from;
1093 tmp_ac.smtp_server = smtpserver;
1094 tmp_ac.smtpport = SMTP_PORT;
1095 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1100 fseek(fp, filepos, SEEK_SET);
1101 if (newsgroup_list && (newsval == 0)) {
1106 /* write to temporary file */
1107 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1108 G_DIR_SEPARATOR, (gint)file);
1109 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1110 FILE_OP_ERROR(tmp, "fopen");
1112 alertpanel_error(_("Could not create temporary file for news sending."));
1114 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1115 FILE_OP_ERROR(tmp, "chmod");
1116 g_warning("can't change file mode\n");
1119 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1120 if (fputs(buf, tmpfp) == EOF) {
1121 FILE_OP_ERROR(tmp, "fputs");
1123 alertpanel_error(_("Error when writing temporary file for news sending."));
1129 debug_print("Sending message by news\n");
1131 folder = FOLDER(newsac->folder);
1133 newsval = news_post(folder, tmp);
1135 alertpanel_error(_("Error occurred while posting the message to %s ."),
1136 newsac->nntp_server);
1144 slist_free_strings(to_list);
1145 g_slist_free(to_list);
1146 slist_free_strings(newsgroup_list);
1147 g_slist_free(newsgroup_list);
1152 /* save message to outbox */
1153 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1156 debug_print("saving sent message...\n");
1158 outbox = folder_find_item_from_identifier(savecopyfolder);
1160 outbox = folder_get_default_outbox();
1162 procmsg_save_to_outbox(outbox, file, TRUE);
1165 if (replymessageid != NULL || fwdmessageid != NULL) {
1169 if (replymessageid != NULL)
1170 tokens = g_strsplit(replymessageid, "\x7f", 0);
1172 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1173 item = folder_find_item_from_identifier(tokens[0]);
1175 /* check if queued message has valid folder and message id */
1176 if (item != NULL && tokens[2] != NULL) {
1179 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1181 /* check if referring message exists and has a message id */
1182 if ((msginfo != NULL) &&
1183 (msginfo->msgid != NULL) &&
1184 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1185 procmsg_msginfo_free(msginfo);
1189 if (msginfo == NULL) {
1190 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1193 if (msginfo != NULL) {
1194 if (replymessageid != NULL) {
1195 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1196 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1199 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1200 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1202 procmsg_msginfo_free(msginfo);
1208 g_free(savecopyfolder);
1209 g_free(replymessageid);
1210 g_free(fwdmessageid);
1212 return (newsval != 0 ? newsval : mailval);
1215 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1217 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1220 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1224 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1229 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1230 item->unread_msgs++;
1231 if (procmsg_msg_has_marked_parent(msginfo))
1232 item->unreadmarked_msgs++;
1235 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1236 item->unread_msgs--;
1237 if (procmsg_msg_has_marked_parent(msginfo))
1238 item->unreadmarked_msgs--;
1242 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1243 procmsg_update_unread_children(msginfo, TRUE);
1246 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1247 procmsg_update_unread_children(msginfo, FALSE);
1251 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1254 MsgInfoUpdate msginfo_update;
1255 MsgPermFlags perm_flags_new, perm_flags_old;
1257 g_return_if_fail(msginfo != NULL);
1258 item = msginfo->folder;
1259 g_return_if_fail(item != NULL);
1261 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1263 /* Perm Flags handling */
1264 perm_flags_old = msginfo->flags.perm_flags;
1265 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1266 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1267 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1270 if (perm_flags_old != perm_flags_new) {
1271 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1273 update_folder_msg_counts(item, msginfo, perm_flags_old);
1275 msginfo_update.msginfo = msginfo;
1276 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1277 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1280 /* Tmp flags hanlding */
1281 msginfo->flags.tmp_flags |= tmp_flags;
1284 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1287 MsgInfoUpdate msginfo_update;
1288 MsgPermFlags perm_flags_new, perm_flags_old;
1290 g_return_if_fail(msginfo != NULL);
1291 item = msginfo->folder;
1292 g_return_if_fail(item != NULL);
1294 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1296 /* Perm Flags handling */
1297 perm_flags_old = msginfo->flags.perm_flags;
1298 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1300 if (perm_flags_old != perm_flags_new) {
1301 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1303 update_folder_msg_counts(item, msginfo, perm_flags_old);
1305 msginfo_update.msginfo = msginfo;
1306 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1307 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1310 /* Tmp flags hanlding */
1311 msginfo->flags.tmp_flags &= ~tmp_flags;
1315 *\brief check for flags (e.g. mark) in prior msgs of current thread
1317 *\param info Current message
1318 *\param perm_flags Flags to be checked
1319 *\param parentmsgs Hash of prior msgs to avoid loops
1321 *\return gboolean TRUE if perm_flags are found
1323 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1324 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1328 g_return_val_if_fail(info != NULL, FALSE);
1330 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1331 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1333 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1334 procmsg_msginfo_free(tmp);
1336 } else if (tmp != NULL) {
1339 if (g_hash_table_lookup(parentmsgs, info)) {
1340 debug_print("loop detected: %s%c%d\n",
1341 folder_item_get_path(info->folder),
1342 G_DIR_SEPARATOR, info->msgnum);
1345 g_hash_table_insert(parentmsgs, info, "1");
1346 result = procmsg_msg_has_flagged_parent_real(
1347 tmp, perm_flags, parentmsgs);
1349 procmsg_msginfo_free(tmp);
1359 *\brief Callback for cleaning up hash of parentmsgs
1361 gboolean parentmsgs_hash_remove(gpointer key,
1369 *\brief Set up list of parentmsgs
1370 * See procmsg_msg_has_flagged_parent_real()
1372 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1375 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1377 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1378 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1379 g_hash_table_destroy(parentmsgs);
1384 *\brief Check if msgs prior in thread are marked
1385 * See procmsg_msg_has_flagged_parent_real()
1387 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1389 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1393 GSList *procmsg_find_children_func(MsgInfo *info,
1394 GSList *children, GSList *all)
1398 g_return_val_if_fail(info!=NULL, children);
1399 if (info->msgid == NULL)
1402 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1403 MsgInfo *tmp = (MsgInfo *)cur->data;
1404 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1405 /* Check if message is already in the list */
1406 if ((children == NULL) ||
1407 (g_slist_index(children, tmp) == -1)) {
1408 children = g_slist_prepend(children,
1409 procmsg_msginfo_new_ref(tmp));
1410 children = procmsg_find_children_func(tmp,
1419 GSList *procmsg_find_children (MsgInfo *info)
1424 g_return_val_if_fail(info!=NULL, NULL);
1425 all = folder_item_get_msg_list(info->folder);
1426 children = procmsg_find_children_func(info, NULL, all);
1427 if (children != NULL) {
1428 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1429 /* this will not free the used pointers
1430 created with procmsg_msginfo_new_ref */
1431 procmsg_msginfo_free((MsgInfo *)cur->data);
1439 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1441 GSList *children = procmsg_find_children(info);
1443 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1444 MsgInfo *tmp = (MsgInfo *)cur->data;
1445 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1447 info->folder->unreadmarked_msgs++;
1449 info->folder->unreadmarked_msgs--;
1450 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1452 procmsg_msginfo_free(tmp);
1454 g_slist_free(children);
1458 * Set the destination folder for a copy or move operation
1460 * \param msginfo The message which's destination folder is changed
1461 * \param to_folder The destination folder for the operation
1463 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1465 if(msginfo->to_folder != NULL) {
1466 msginfo->to_folder->op_count--;
1467 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1469 msginfo->to_folder = to_folder;
1470 if(to_folder != NULL) {
1471 to_folder->op_count++;
1472 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1477 * Apply filtering actions to the msginfo
1479 * \param msginfo The MsgInfo describing the message that should be filtered
1480 * \return TRUE if the message was moved and MsgInfo is now invalid,
1483 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1485 MailFilteringData mail_filtering_data;
1487 mail_filtering_data.msginfo = msginfo;
1488 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1491 /* filter if enabled in prefs or move to inbox if not */
1492 if((global_processing != NULL) &&
1493 filter_message_by_msginfo(global_processing, msginfo))