2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 #include "procheader.h"
32 #include "send_message.h"
34 #include "statusbar.h"
35 #include "prefs_filtering.h"
36 #include "filtering.h"
38 #include "prefs_common.h"
43 #include "alertpanel.h"
48 GHashTable *procmsg_msg_hash_table_create(GSList *mlist)
50 GHashTable *msg_table;
52 if (mlist == NULL) return NULL;
54 msg_table = g_hash_table_new(NULL, g_direct_equal);
55 procmsg_msg_hash_table_append(msg_table, mlist);
60 void procmsg_msg_hash_table_append(GHashTable *msg_table, GSList *mlist)
65 if (msg_table == NULL || mlist == NULL) return;
67 for (cur = mlist; cur != NULL; cur = cur->next) {
68 msginfo = (MsgInfo *)cur->data;
70 g_hash_table_insert(msg_table,
71 GUINT_TO_POINTER(msginfo->msgnum),
76 GHashTable *procmsg_to_folder_hash_table_create(GSList *mlist)
78 GHashTable *msg_table;
82 if (mlist == NULL) return NULL;
84 msg_table = g_hash_table_new(NULL, g_direct_equal);
86 for (cur = mlist; cur != NULL; cur = cur->next) {
87 msginfo = (MsgInfo *)cur->data;
88 g_hash_table_insert(msg_table, msginfo->to_folder, msginfo);
94 gint procmsg_get_last_num_in_msg_list(GSList *mlist)
100 for (cur = mlist; cur != NULL; cur = cur->next) {
101 msginfo = (MsgInfo *)cur->data;
102 if (msginfo && msginfo->msgnum > last)
103 last = msginfo->msgnum;
109 void procmsg_msg_list_free(GSList *mlist)
114 for (cur = mlist; cur != NULL; cur = cur->next) {
115 msginfo = (MsgInfo *)cur->data;
116 procmsg_msginfo_free(msginfo);
130 static gboolean procmsg_ignore_node(GNode *node, gpointer data)
132 MsgInfo *msginfo = (MsgInfo *)node->data;
134 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
135 procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
140 /* CLAWS subject threading:
142 in the first round it inserts subject lines in a hash
143 table. a duplicate subject line replaces the one in
144 the table only if its older. (this round should actually
145 create a list of all duplicate subject lines)
147 the second round finishes the threads by attaching
148 duplicate subject lines to the one found in the
149 hash table. as soon as a subject line is found that
150 is too old, that one becomes the new parent for
151 the next iteration. (this fails when a parent arrived
152 later than its child.)
155 /* return the reversed thread tree */
156 GNode *procmsg_get_thread_tree(GSList *mlist)
158 GNode *root, *parent, *node, *next, *last;
159 GNode *prev; /* CLAWS */
160 GHashTable *msgid_table;
161 GHashTable *subject_table;
164 const gchar *subject;
166 root = g_node_new(NULL);
167 msgid_table = g_hash_table_new(g_str_hash, g_str_equal);
168 subject_table = g_hash_table_new(g_str_hash, g_str_equal);
170 for (; mlist != NULL; mlist = mlist->next) {
171 msginfo = (MsgInfo *)mlist->data;
174 if (msginfo->inreplyto) {
175 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
176 if (parent == NULL) {
179 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
180 procmsg_msginfo_unset_flags(msginfo, MSG_NEW | MSG_UNREAD, 0);
181 procmsg_msginfo_set_flags(msginfo, MSG_IGNORE_THREAD, 0);
185 node = g_node_insert_data_before
186 (parent, parent == root ? parent->children : NULL,
188 if ((msgid = msginfo->msgid) &&
189 g_hash_table_lookup(msgid_table, msgid) == NULL)
190 g_hash_table_insert(msgid_table, (gchar *)msgid, node);
192 /* CLAWS: add subject to table (without prefix) */
193 if (prefs_common.thread_by_subject) {
194 GNode *found_subject = NULL;
196 subject = msginfo->subject;
197 subject += subject_get_prefix_length(subject);
198 found_subject = subject_table_lookup_clean
199 (subject_table, (gchar *) subject);
201 if (found_subject == NULL)
202 subject_table_insert_clean(subject_table, (gchar *) subject,
204 else if ( ((MsgInfo*)(found_subject->data))->date_t >
205 ((MsgInfo*)(node->data))->date_t ) {
206 /* replace if msg in table is older than current one
207 TODO: should create a list of messages with same subject */
208 subject_table_remove_clean(subject_table, (gchar *) subject);
209 subject_table_insert_clean(subject_table, (gchar *) subject, node);
214 /* complete the unfinished threads */
215 for (node = root->children; node != NULL; ) {
216 prev = node->prev; /* CLAWS: need the last node */
219 msginfo = (MsgInfo *)node->data;
220 if (msginfo->inreplyto) {
221 parent = g_hash_table_lookup(msgid_table, msginfo->inreplyto);
222 /* node should not be the parent, and node should not
223 be an ancestor of parent (circular reference) */
224 if (parent && parent != node &&
225 !g_node_is_ancestor(node, parent)) {
228 (parent, parent->children, node);
229 /* CLAWS: ignore thread */
230 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags))
231 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
234 last = (next == NULL) ? prev : node;
238 if (prefs_common.thread_by_subject) {
239 for (node = last; node && node != NULL;) {
241 msginfo = (MsgInfo *) node->data;
242 subject = msginfo->subject + subject_get_prefix_length(msginfo->subject);
244 /* may not parentize if parent was delivered after childs */
245 if (subject != msginfo->subject)
246 parent = subject_table_lookup_clean(subject_table, (gchar *) subject);
250 /* the node may already be threaded by IN-REPLY-TO, so go up in the tree to
251 find the parent node */
252 if (parent != NULL) {
253 if (g_node_is_ancestor(node, parent))
257 /* make new thread parent if too old compared to previous one; probably
258 breaks ignoring threads for subject threading. not accurate because
259 the tree isn't sorted by date. */
260 if (parent && abs(difftime(msginfo->date_t, ((MsgInfo *)parent->data)->date_t)) >
261 prefs_common.thread_by_subject_max_age * 3600 * 24) {
262 subject_table_remove_clean(subject_table, (gchar *) subject);
263 subject_table_insert_clean(subject_table, (gchar *) subject, node);
270 g_node_append(parent, node);
271 /* CLAWS: ignore thread */
272 if (MSG_IS_IGNORE_THREAD(((MsgInfo *)parent->data)->flags) && !MSG_IS_IGNORE_THREAD(msginfo->flags)) {
273 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, procmsg_ignore_node, NULL);
281 g_hash_table_destroy(subject_table);
282 g_hash_table_destroy(msgid_table);
287 void procmsg_move_messages(GSList *mlist)
289 GSList *cur, *movelist = NULL;
291 FolderItem *dest = NULL;
295 folder_item_update_freeze();
297 for (cur = mlist; cur != NULL; cur = cur->next) {
298 msginfo = (MsgInfo *)cur->data;
300 dest = msginfo->to_folder;
301 movelist = g_slist_append(movelist, msginfo);
302 } else if (dest == msginfo->to_folder) {
303 movelist = g_slist_append(movelist, msginfo);
305 folder_item_move_msgs(dest, movelist);
306 g_slist_free(movelist);
308 dest = msginfo->to_folder;
309 movelist = g_slist_append(movelist, msginfo);
311 procmsg_msginfo_set_to_folder(msginfo, NULL);
315 folder_item_move_msgs(dest, movelist);
316 g_slist_free(movelist);
319 folder_item_update_thaw();
322 void procmsg_copy_messages(GSList *mlist)
324 GSList *cur, *copylist = NULL;
326 FolderItem *dest = NULL;
330 folder_item_update_freeze();
332 for (cur = mlist; cur != NULL; cur = cur->next) {
333 msginfo = (MsgInfo *)cur->data;
335 dest = msginfo->to_folder;
336 copylist = g_slist_append(copylist, msginfo);
337 } else if (dest == msginfo->to_folder) {
338 copylist = g_slist_append(copylist, msginfo);
340 folder_item_copy_msgs(dest, copylist);
341 g_slist_free(copylist);
343 dest = msginfo->to_folder;
344 copylist = g_slist_append(copylist, msginfo);
346 procmsg_msginfo_set_to_folder(msginfo, NULL);
350 folder_item_copy_msgs(dest, copylist);
351 g_slist_free(copylist);
354 folder_item_update_thaw();
357 gchar *procmsg_get_message_file_path(MsgInfo *msginfo)
361 g_return_val_if_fail(msginfo != NULL, NULL);
363 if (msginfo->plaintext_file)
364 file = g_strdup(msginfo->plaintext_file);
366 file = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
372 gchar *procmsg_get_message_file(MsgInfo *msginfo)
374 gchar *filename = NULL;
376 g_return_val_if_fail(msginfo != NULL, NULL);
378 filename = folder_item_fetch_msg(msginfo->folder, msginfo->msgnum);
380 g_warning("can't fetch message %d\n", msginfo->msgnum);
385 GSList *procmsg_get_message_file_list(GSList *mlist)
387 GSList *file_list = NULL;
389 MsgFileInfo *fileinfo;
392 while (mlist != NULL) {
393 msginfo = (MsgInfo *)mlist->data;
394 file = procmsg_get_message_file(msginfo);
396 procmsg_message_file_list_free(file_list);
399 fileinfo = g_new(MsgFileInfo, 1);
400 fileinfo->msginfo = procmsg_msginfo_new_ref(msginfo);
401 fileinfo->file = file;
402 fileinfo->flags = g_new(MsgFlags, 1);
403 *fileinfo->flags = msginfo->flags;
404 file_list = g_slist_prepend(file_list, fileinfo);
408 file_list = g_slist_reverse(file_list);
413 void procmsg_message_file_list_free(MsgInfoList *file_list)
416 MsgFileInfo *fileinfo;
418 for (cur = file_list; cur != NULL; cur = cur->next) {
419 fileinfo = (MsgFileInfo *)cur->data;
420 procmsg_msginfo_free(fileinfo->msginfo);
421 g_free(fileinfo->file);
422 g_free(fileinfo->flags);
426 g_slist_free(file_list);
429 FILE *procmsg_open_message(MsgInfo *msginfo)
434 g_return_val_if_fail(msginfo != NULL, NULL);
436 file = procmsg_get_message_file_path(msginfo);
437 g_return_val_if_fail(file != NULL, NULL);
439 if (!is_file_exist(file)) {
441 file = procmsg_get_message_file(msginfo);
442 g_return_val_if_fail(file != NULL, NULL);
445 if ((fp = fopen(file, "rb")) == NULL) {
446 FILE_OP_ERROR(file, "fopen");
453 if (MSG_IS_QUEUED(msginfo->flags) || MSG_IS_DRAFT(msginfo->flags)) {
456 while (fgets(buf, sizeof(buf), fp) != NULL)
457 if (buf[0] == '\r' || buf[0] == '\n') break;
464 FILE *procmsg_open_message_decrypted(MsgInfo *msginfo, MimeInfo **mimeinfo)
470 g_return_val_if_fail(msginfo != NULL, NULL);
472 if (mimeinfo) *mimeinfo = NULL;
474 if ((fp = procmsg_open_message(msginfo)) == NULL) return NULL;
476 mimeinfo_ = procmime_scan_mime_header(fp);
482 if (!MSG_IS_ENCRYPTED(msginfo->flags) &&
483 rfc2015_is_encrypted(mimeinfo_)) {
484 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_ENCRYPTED);
487 if (MSG_IS_ENCRYPTED(msginfo->flags) &&
488 !msginfo->plaintext_file &&
489 !msginfo->decryption_failed) {
491 rfc2015_decrypt_message(msginfo, mimeinfo_, fp);
492 if (msginfo->plaintext_file &&
493 !msginfo->decryption_failed) {
495 procmime_mimeinfo_free_all(mimeinfo_);
496 if ((fp = procmsg_open_message(msginfo)) == NULL)
498 mimeinfo_ = procmime_scan_mime_header(fp);
504 if (fseek(fp, fpos, SEEK_SET) < 0)
509 if (mimeinfo) *mimeinfo = mimeinfo_;
514 gboolean procmsg_msg_exist(MsgInfo *msginfo)
519 if (!msginfo) return FALSE;
521 path = folder_item_get_path(msginfo->folder);
523 ret = !folder_item_is_msg_changed(msginfo->folder, msginfo);
529 void procmsg_get_filter_keyword(MsgInfo *msginfo, gchar **header, gchar **key,
530 PrefsFilterType type)
532 static HeaderEntry hentry[] = {{"X-BeenThere:", NULL, TRUE},
533 {"X-ML-Name:", NULL, TRUE},
534 {"X-List:", NULL, TRUE},
535 {"X-Mailing-list:", NULL, TRUE},
536 {"List-Id:", NULL, TRUE},
537 {"X-Sequence:", NULL, TRUE},
538 {NULL, NULL, FALSE}};
544 H_X_MAILING_LIST = 3,
551 g_return_if_fail(msginfo != NULL);
552 g_return_if_fail(header != NULL);
553 g_return_if_fail(key != NULL);
562 if ((fp = procmsg_open_message(msginfo)) == NULL)
564 procheader_get_header_fields(fp, hentry);
567 #define SET_FILTER_KEY(hstr, idx) \
569 *header = g_strdup(hstr); \
570 *key = hentry[idx].body; \
571 hentry[idx].body = NULL; \
574 if (hentry[H_X_BEENTHERE].body != NULL) {
575 SET_FILTER_KEY("header \"X-BeenThere\"", H_X_BEENTHERE);
576 } else if (hentry[H_X_ML_NAME].body != NULL) {
577 SET_FILTER_KEY("header \"X-ML-Name\"", H_X_ML_NAME);
578 } else if (hentry[H_X_LIST].body != NULL) {
579 SET_FILTER_KEY("header \"X-List\"", H_X_LIST);
580 } else if (hentry[H_X_MAILING_LIST].body != NULL) {
581 SET_FILTER_KEY("header \"X-Mailing-List\"", H_X_MAILING_LIST);
582 } else if (hentry[H_LIST_ID].body != NULL) {
583 SET_FILTER_KEY("header \"List-Id\"", H_LIST_ID);
584 extract_list_id_str(*key);
585 } else if (hentry[H_X_SEQUENCE].body != NULL) {
588 SET_FILTER_KEY("X-Sequence", H_X_SEQUENCE);
591 while (*p != '\0' && !isspace(*p)) p++;
592 while (isspace(*p)) p++;
599 } else if (msginfo->subject) {
600 *header = g_strdup("subject");
601 *key = g_strdup(msginfo->subject);
604 #undef SET_FILTER_KEY
606 g_free(hentry[H_X_BEENTHERE].body);
607 hentry[H_X_BEENTHERE].body = NULL;
608 g_free(hentry[H_X_ML_NAME].body);
609 hentry[H_X_ML_NAME].body = NULL;
610 g_free(hentry[H_X_LIST].body);
611 hentry[H_X_LIST].body = NULL;
612 g_free(hentry[H_X_MAILING_LIST].body);
613 hentry[H_X_MAILING_LIST].body = NULL;
614 g_free(hentry[H_LIST_ID].body);
615 hentry[H_LIST_ID].body = NULL;
619 *header = g_strdup("from");
620 *key = g_strdup(msginfo->from);
623 *header = g_strdup("to");
624 *key = g_strdup(msginfo->to);
626 case FILTER_BY_SUBJECT:
627 *header = g_strdup("subject");
628 *key = g_strdup(msginfo->subject);
635 void procmsg_empty_trash(void)
640 for (cur = folder_get_list(); cur != NULL; cur = cur->next) {
641 trash = FOLDER(cur->data)->trash;
642 if (trash && trash->total_msgs > 0)
643 folder_item_remove_all_msg(trash);
648 *\brief Send messages in queue
650 *\param queue Queue folder to process
651 *\param save_msgs Unused
653 *\return Number of messages sent, negative if an error occurred
654 * positive if no error occurred
656 gint procmsg_send_queue(FolderItem *queue, gboolean save_msgs)
658 gint ret = 1, count = 0;
662 queue = folder_get_default_queue();
663 g_return_val_if_fail(queue != NULL, -1);
665 folder_item_scan(queue);
666 list = folder_item_get_msg_list(queue);
668 for (elem = list; elem != NULL; elem = elem->next) {
672 msginfo = (MsgInfo *)(elem->data);
673 if (!MSG_IS_LOCKED(msginfo->flags)) {
674 file = folder_item_fetch_msg(queue, msginfo->msgnum);
676 if (procmsg_send_message_queue(file) < 0) {
677 g_warning("Sending queued message %d failed.\n",
682 * We save in procmsg_send_message_queue because
683 * we need the destination folder from the queue
687 procmsg_save_to_outbox
688 (queue->folder->outbox,
692 folder_item_remove_msg(queue, msginfo->msgnum);
697 /* FIXME: supposedly if only one message is locked, and queue
698 * is being flushed, the following free says something like
699 * "freeing msg ## in folder (nil)". */
700 procmsg_msginfo_free(msginfo);
706 gint procmsg_remove_special_headers(const gchar *in, const gchar *out)
711 if ((fp = fopen(in, "rb")) == NULL) {
712 FILE_OP_ERROR(in, "fopen");
715 if ((outfp = fopen(out, "wb")) == NULL) {
716 FILE_OP_ERROR(out, "fopen");
720 while (fgets(buf, sizeof(buf), fp) != NULL)
721 if (buf[0] == '\r' || buf[0] == '\n') break;
722 while (fgets(buf, sizeof(buf), fp) != NULL)
729 gint procmsg_save_to_outbox(FolderItem *outbox, const gchar *file,
733 MsgInfo *msginfo, *tmp_msginfo;
734 MsgFlags flag = {0, 0};
736 debug_print("saving sent message...\n");
739 outbox = folder_get_default_outbox();
740 g_return_val_if_fail(outbox != NULL, -1);
742 /* remove queueing headers */
744 gchar tmp[MAXPATHLEN + 1];
746 g_snprintf(tmp, sizeof(tmp), "%s%ctmpmsg.out.%08x",
747 get_rc_dir(), G_DIR_SEPARATOR, (guint)random());
749 if (procmsg_remove_special_headers(file, tmp) !=0)
752 folder_item_scan(outbox);
753 if ((num = folder_item_add_msg(outbox, tmp, &flag, TRUE)) < 0) {
754 g_warning("can't save message\n");
759 folder_item_scan(outbox);
760 if ((num = folder_item_add_msg
761 (outbox, file, &flag, FALSE)) < 0) {
762 g_warning("can't save message\n");
767 msginfo = folder_item_get_msginfo(outbox, num); /* refcnt++ */
768 tmp_msginfo = procmsg_msginfo_get_full_info(msginfo); /* refcnt++ */
769 if (msginfo != NULL) {
770 procmsg_msginfo_unset_flags(msginfo, ~0, 0);
771 procmsg_msginfo_free(msginfo); /* refcnt-- */
772 /* tmp_msginfo == msginfo */
773 if (tmp_msginfo && (msginfo->dispositionnotificationto ||
774 msginfo->returnreceiptto)) {
775 procmsg_msginfo_set_flags(msginfo, MSG_RETRCPT_SENT, 0);
776 procmsg_msginfo_free(msginfo); /* refcnt-- */
779 folder_item_update(outbox, TRUE);
784 void procmsg_print_message(MsgInfo *msginfo, const gchar *cmdline)
786 static const gchar *def_cmd = "lpr %s";
793 g_return_if_fail(msginfo);
795 if ((tmpfp = procmime_get_first_text_content(msginfo)) == NULL) {
796 g_warning("Can't get text part\n");
800 prtmp = g_strdup_printf("%s%cprinttmp.%08x",
801 get_mime_tmp_dir(), G_DIR_SEPARATOR, id++);
803 if ((prfp = fopen(prtmp, "wb")) == NULL) {
804 FILE_OP_ERROR(prtmp, "fopen");
810 if (msginfo->date) fprintf(prfp, "Date: %s\n", msginfo->date);
811 if (msginfo->from) fprintf(prfp, "From: %s\n", msginfo->from);
812 if (msginfo->to) fprintf(prfp, "To: %s\n", msginfo->to);
813 if (msginfo->cc) fprintf(prfp, "Cc: %s\n", msginfo->cc);
814 if (msginfo->newsgroups)
815 fprintf(prfp, "Newsgroups: %s\n", msginfo->newsgroups);
816 if (msginfo->subject) fprintf(prfp, "Subject: %s\n", msginfo->subject);
819 while (fgets(buf, sizeof(buf), tmpfp) != NULL)
825 if (cmdline && (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
827 g_snprintf(buf, sizeof(buf) - 1, cmdline, prtmp);
830 g_warning("Print command line is invalid: `%s'\n",
832 g_snprintf(buf, sizeof(buf) - 1, def_cmd, prtmp);
838 if (buf[strlen(buf) - 1] != '&') strcat(buf, "&");
842 MsgInfo *procmsg_msginfo_new_ref(MsgInfo *msginfo)
849 MsgInfo *procmsg_msginfo_new(void)
853 newmsginfo = g_new0(MsgInfo, 1);
854 newmsginfo->refcnt = 1;
859 MsgInfo *procmsg_msginfo_copy(MsgInfo *msginfo)
863 if (msginfo == NULL) return NULL;
865 newmsginfo = g_new0(MsgInfo, 1);
867 newmsginfo->refcnt = 1;
869 #define MEMBCOPY(mmb) newmsginfo->mmb = msginfo->mmb
870 #define MEMBDUP(mmb) newmsginfo->mmb = msginfo->mmb ? \
871 g_strdup(msginfo->mmb) : NULL
895 MEMBDUP(dispositionnotificationto);
896 MEMBDUP(returnreceiptto);
900 MEMBCOPY(threadscore);
905 MsgInfo *procmsg_msginfo_get_full_info(MsgInfo *msginfo)
907 MsgInfo *full_msginfo;
910 if (msginfo == NULL) return NULL;
912 file = procmsg_get_message_file(msginfo);
914 g_warning("procmsg_msginfo_get_full_info(): can't get message file.\n");
918 full_msginfo = procheader_parse_file(file, msginfo->flags, TRUE, FALSE);
920 if (!full_msginfo) return NULL;
922 /* CLAWS: make sure we add the missing members; see:
923 * procheader.c::procheader_get_headernames() */
925 msginfo->xface = g_strdup(full_msginfo->xface);
926 if (!msginfo->dispositionnotificationto)
927 msginfo->dispositionnotificationto =
928 g_strdup(full_msginfo->dispositionnotificationto);
929 if (!msginfo->returnreceiptto)
930 msginfo->returnreceiptto = g_strdup
931 (full_msginfo->returnreceiptto);
932 procmsg_msginfo_free(full_msginfo);
934 return procmsg_msginfo_new_ref(msginfo);
936 full_msginfo->msgnum = msginfo->msgnum;
937 full_msginfo->size = msginfo->size;
938 full_msginfo->mtime = msginfo->mtime;
939 full_msginfo->folder = msginfo->folder;
941 full_msginfo->plaintext_file = g_strdup(msginfo->plaintext_file);
942 full_msginfo->decryption_failed = msginfo->decryption_failed;
944 procmsg_msginfo_set_to_folder(full_msginfo, msginfo->to_folder);
950 void procmsg_msginfo_free(MsgInfo *msginfo)
952 if (msginfo == NULL) return;
955 if (msginfo->refcnt > 0)
958 debug_print("freeing msginfo %d in %s\n", msginfo->msgnum, msginfo->folder ? msginfo->folder->path : "(nil)");
960 if (msginfo->to_folder) {
961 msginfo->to_folder->op_count--;
962 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
965 g_free(msginfo->fromspace);
966 g_free(msginfo->references);
967 g_free(msginfo->returnreceiptto);
968 g_free(msginfo->dispositionnotificationto);
969 g_free(msginfo->xface);
971 g_free(msginfo->fromname);
973 g_free(msginfo->date);
974 g_free(msginfo->from);
977 g_free(msginfo->newsgroups);
978 g_free(msginfo->subject);
979 g_free(msginfo->msgid);
980 g_free(msginfo->inreplyto);
981 g_free(msginfo->xref);
986 guint procmsg_msginfo_memusage(MsgInfo *msginfo)
990 memusage += sizeof(MsgInfo);
991 if (msginfo->fromname)
992 memusage += strlen(msginfo->fromname);
994 memusage += strlen(msginfo->date);
996 memusage += strlen(msginfo->from);
998 memusage += strlen(msginfo->to);
1000 memusage += strlen(msginfo->cc);
1001 if (msginfo->newsgroups)
1002 memusage += strlen(msginfo->newsgroups);
1003 if (msginfo->subject)
1004 memusage += strlen(msginfo->subject);
1006 memusage += strlen(msginfo->msgid);
1007 if (msginfo->inreplyto)
1008 memusage += strlen(msginfo->inreplyto);
1010 memusage += strlen(msginfo->xface);
1011 if (msginfo->dispositionnotificationto)
1012 memusage += strlen(msginfo->dispositionnotificationto);
1013 if (msginfo->returnreceiptto)
1014 memusage += strlen(msginfo->returnreceiptto);
1015 if (msginfo->references)
1016 memusage += strlen(msginfo->references);
1017 if (msginfo->fromspace)
1018 memusage += strlen(msginfo->fromspace);
1023 gint procmsg_cmp_msgnum_for_sort(gconstpointer a, gconstpointer b)
1025 const MsgInfo *msginfo1 = a;
1026 const MsgInfo *msginfo2 = b;
1033 return msginfo1->msgnum - msginfo2->msgnum;
1042 Q_MAIL_ACCOUNT_ID = 4,
1043 Q_NEWS_ACCOUNT_ID = 5,
1044 Q_SAVE_COPY_FOLDER = 6,
1045 Q_REPLY_MESSAGE_ID = 7,
1046 Q_FWD_MESSAGE_ID = 8
1049 gint procmsg_send_message_queue(const gchar *file)
1051 static HeaderEntry qentry[] = {{"S:", NULL, FALSE},
1052 {"SSV:", NULL, FALSE},
1053 {"R:", NULL, FALSE},
1054 {"NG:", NULL, FALSE},
1055 {"MAID:", NULL, FALSE},
1056 {"NAID:", NULL, FALSE},
1057 {"SCF:", NULL, FALSE},
1058 {"RMID:", NULL, FALSE},
1059 {"FMID:", NULL, FALSE},
1060 {NULL, NULL, FALSE}};
1063 gint mailval = 0, newsval = 0;
1065 gchar *smtpserver = NULL;
1066 GSList *to_list = NULL;
1067 GSList *newsgroup_list = NULL;
1068 gchar *savecopyfolder = NULL;
1069 gchar *replymessageid = NULL;
1070 gchar *fwdmessageid = NULL;
1071 gchar buf[BUFFSIZE];
1073 PrefsAccount *mailac = NULL, *newsac = NULL;
1076 g_return_val_if_fail(file != NULL, -1);
1078 if ((fp = fopen(file, "rb")) == NULL) {
1079 FILE_OP_ERROR(file, "fopen");
1083 while ((hnum = procheader_get_one_field(buf, sizeof(buf), fp, qentry))
1085 gchar *p = buf + strlen(qentry[hnum].name);
1089 if (!from) from = g_strdup(p);
1092 if (!smtpserver) smtpserver = g_strdup(p);
1095 to_list = address_list_append(to_list, p);
1098 newsgroup_list = newsgroup_list_append(newsgroup_list, p);
1100 case Q_MAIL_ACCOUNT_ID:
1101 mailac = account_find_from_id(atoi(p));
1103 case Q_NEWS_ACCOUNT_ID:
1104 newsac = account_find_from_id(atoi(p));
1106 case Q_SAVE_COPY_FOLDER:
1107 if (!savecopyfolder) savecopyfolder = g_strdup(p);
1109 case Q_REPLY_MESSAGE_ID:
1110 if (!replymessageid) replymessageid = g_strdup(p);
1112 case Q_FWD_MESSAGE_ID:
1113 if (!fwdmessageid) fwdmessageid = g_strdup(p);
1117 filepos = ftell(fp);
1120 debug_print("Sending message by mail\n");
1122 g_warning("Queued message header is broken.\n");
1124 } else if (mailac && mailac->use_mail_command &&
1125 mailac->mail_command && (* mailac->mail_command)) {
1126 mailval = send_message_local(mailac->mail_command, fp);
1128 } else if (prefs_common.use_extsend && prefs_common.extsend_cmd) {
1129 mailval = send_message_local(prefs_common.extsend_cmd, fp);
1133 mailac = account_find_from_smtp_server(from, smtpserver);
1135 g_warning("Account not found. "
1136 "Using current account...\n");
1137 mailac = cur_account;
1142 mailval = send_message_smtp(mailac, to_list, fp);
1144 PrefsAccount tmp_ac;
1146 g_warning("Account not found.\n");
1148 memset(&tmp_ac, 0, sizeof(PrefsAccount));
1149 tmp_ac.address = from;
1150 tmp_ac.smtp_server = smtpserver;
1151 tmp_ac.smtpport = SMTP_PORT;
1152 mailval = send_message_smtp(&tmp_ac, to_list, fp);
1157 fseek(fp, filepos, SEEK_SET);
1158 if (newsgroup_list && (newsval == 0)) {
1163 /* write to temporary file */
1164 tmp = g_strdup_printf("%s%ctmp%d", g_get_tmp_dir(),
1165 G_DIR_SEPARATOR, (gint)file);
1166 if ((tmpfp = fopen(tmp, "wb")) == NULL) {
1167 FILE_OP_ERROR(tmp, "fopen");
1169 alertpanel_error(_("Could not create temporary file for news sending."));
1171 if (change_file_mode_rw(tmpfp, tmp) < 0) {
1172 FILE_OP_ERROR(tmp, "chmod");
1173 g_warning("can't change file mode\n");
1176 while ((newsval == 0) && fgets(buf, sizeof(buf), fp) != NULL) {
1177 if (fputs(buf, tmpfp) == EOF) {
1178 FILE_OP_ERROR(tmp, "fputs");
1180 alertpanel_error(_("Error when writing temporary file for news sending."));
1186 debug_print("Sending message by news\n");
1188 folder = FOLDER(newsac->folder);
1190 newsval = news_post(folder, tmp);
1192 alertpanel_error(_("Error occurred while posting the message to %s ."),
1193 newsac->nntp_server);
1201 slist_free_strings(to_list);
1202 g_slist_free(to_list);
1203 slist_free_strings(newsgroup_list);
1204 g_slist_free(newsgroup_list);
1209 /* save message to outbox */
1210 if (mailval == 0 && newsval == 0 && savecopyfolder) {
1213 debug_print("saving sent message...\n");
1215 outbox = folder_find_item_from_identifier(savecopyfolder);
1217 outbox = folder_get_default_outbox();
1219 procmsg_save_to_outbox(outbox, file, TRUE);
1222 if (replymessageid != NULL || fwdmessageid != NULL) {
1226 if (replymessageid != NULL)
1227 tokens = g_strsplit(replymessageid, "\x7f", 0);
1229 tokens = g_strsplit(fwdmessageid, "\x7f", 0);
1230 item = folder_find_item_from_identifier(tokens[0]);
1232 /* check if queued message has valid folder and message id */
1233 if (item != NULL && tokens[2] != NULL) {
1236 msginfo = folder_item_get_msginfo(item, atoi(tokens[1]));
1238 /* check if referring message exists and has a message id */
1239 if ((msginfo != NULL) &&
1240 (msginfo->msgid != NULL) &&
1241 (strcmp(msginfo->msgid, tokens[2]) != 0)) {
1242 procmsg_msginfo_free(msginfo);
1246 if (msginfo == NULL) {
1247 msginfo = folder_item_get_msginfo_by_msgid(item, tokens[2]);
1250 if (msginfo != NULL) {
1251 if (replymessageid != NULL) {
1252 procmsg_msginfo_unset_flags(msginfo, MSG_FORWARDED, 0);
1253 procmsg_msginfo_set_flags(msginfo, MSG_REPLIED, 0);
1256 procmsg_msginfo_unset_flags(msginfo, MSG_REPLIED, 0);
1257 procmsg_msginfo_set_flags(msginfo, MSG_FORWARDED, 0);
1259 procmsg_msginfo_free(msginfo);
1265 g_free(savecopyfolder);
1266 g_free(replymessageid);
1267 g_free(fwdmessageid);
1269 return (newsval != 0 ? newsval : mailval);
1272 static void update_folder_msg_counts(FolderItem *item, MsgInfo *msginfo, MsgPermFlags old_flags)
1274 MsgPermFlags new_flags = msginfo->flags.perm_flags;
1277 if (!(old_flags & MSG_NEW) && (new_flags & MSG_NEW)) {
1281 if ((old_flags & MSG_NEW) && !(new_flags & MSG_NEW)) {
1286 if (!(old_flags & MSG_UNREAD) && (new_flags & MSG_UNREAD)) {
1287 item->unread_msgs++;
1288 if (procmsg_msg_has_marked_parent(msginfo))
1289 item->unreadmarked_msgs++;
1292 if ((old_flags & MSG_UNREAD) && !(new_flags & MSG_UNREAD)) {
1293 item->unread_msgs--;
1294 if (procmsg_msg_has_marked_parent(msginfo))
1295 item->unreadmarked_msgs--;
1299 if (!(old_flags & MSG_MARKED) && (new_flags & MSG_MARKED)) {
1300 procmsg_update_unread_children(msginfo, TRUE);
1303 if ((old_flags & MSG_MARKED) && !(new_flags & MSG_MARKED)) {
1304 procmsg_update_unread_children(msginfo, FALSE);
1308 void procmsg_msginfo_set_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1311 MsgInfoUpdate msginfo_update;
1312 MsgPermFlags perm_flags_new, perm_flags_old;
1314 g_return_if_fail(msginfo != NULL);
1315 item = msginfo->folder;
1316 g_return_if_fail(item != NULL);
1318 debug_print("Setting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1320 /* Perm Flags handling */
1321 perm_flags_old = msginfo->flags.perm_flags;
1322 perm_flags_new = msginfo->flags.perm_flags | perm_flags;
1323 if ((perm_flags & MSG_IGNORE_THREAD) || (perm_flags_old & MSG_IGNORE_THREAD)) {
1324 perm_flags_new &= ~(MSG_NEW | MSG_UNREAD);
1327 if (perm_flags_old != perm_flags_new) {
1328 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1330 update_folder_msg_counts(item, msginfo, perm_flags_old);
1332 msginfo_update.msginfo = msginfo;
1333 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1334 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1337 /* Tmp flags hanlding */
1338 msginfo->flags.tmp_flags |= tmp_flags;
1341 void procmsg_msginfo_unset_flags(MsgInfo *msginfo, MsgPermFlags perm_flags, MsgTmpFlags tmp_flags)
1344 MsgInfoUpdate msginfo_update;
1345 MsgPermFlags perm_flags_new, perm_flags_old;
1347 g_return_if_fail(msginfo != NULL);
1348 item = msginfo->folder;
1349 g_return_if_fail(item != NULL);
1351 debug_print("Unsetting flags for message %d in folder %s\n", msginfo->msgnum, item->path);
1353 /* Perm Flags handling */
1354 perm_flags_old = msginfo->flags.perm_flags;
1355 perm_flags_new = msginfo->flags.perm_flags & ~perm_flags;
1357 if (perm_flags_old != perm_flags_new) {
1358 folder_item_change_msg_flags(msginfo->folder, msginfo, perm_flags_new);
1360 update_folder_msg_counts(item, msginfo, perm_flags_old);
1362 msginfo_update.msginfo = msginfo;
1363 hooks_invoke(MSGINFO_UPDATE_HOOKLIST, &msginfo_update);
1364 folder_item_update(msginfo->folder, F_ITEM_UPDATE_MSGCNT);
1367 /* Tmp flags hanlding */
1368 msginfo->flags.tmp_flags &= ~tmp_flags;
1372 *\brief check for flags (e.g. mark) in prior msgs of current thread
1374 *\param info Current message
1375 *\param perm_flags Flags to be checked
1376 *\param parentmsgs Hash of prior msgs to avoid loops
1378 *\return gboolean TRUE if perm_flags are found
1380 gboolean procmsg_msg_has_flagged_parent_real(MsgInfo *info,
1381 MsgPermFlags perm_flags, GHashTable *parentmsgs)
1385 g_return_val_if_fail(info != NULL, FALSE);
1387 if (info != NULL && info->folder != NULL && info->inreplyto != NULL) {
1388 tmp = folder_item_get_msginfo_by_msgid(info->folder,
1390 if (tmp && (tmp->flags.perm_flags & perm_flags)) {
1391 procmsg_msginfo_free(tmp);
1393 } else if (tmp != NULL) {
1396 if (g_hash_table_lookup(parentmsgs, info)) {
1397 debug_print("loop detected: %s%c%d\n",
1398 folder_item_get_path(info->folder),
1399 G_DIR_SEPARATOR, info->msgnum);
1402 g_hash_table_insert(parentmsgs, info, "1");
1403 result = procmsg_msg_has_flagged_parent_real(
1404 tmp, perm_flags, parentmsgs);
1406 procmsg_msginfo_free(tmp);
1416 *\brief Callback for cleaning up hash of parentmsgs
1418 gboolean parentmsgs_hash_remove(gpointer key,
1426 *\brief Set up list of parentmsgs
1427 * See procmsg_msg_has_flagged_parent_real()
1429 gboolean procmsg_msg_has_flagged_parent(MsgInfo *info, MsgPermFlags perm_flags)
1432 GHashTable *parentmsgs = g_hash_table_new(NULL, NULL);
1434 result = procmsg_msg_has_flagged_parent_real(info, perm_flags, parentmsgs);
1435 g_hash_table_foreach_remove(parentmsgs, parentmsgs_hash_remove, NULL);
1436 g_hash_table_destroy(parentmsgs);
1441 *\brief Check if msgs prior in thread are marked
1442 * See procmsg_msg_has_flagged_parent_real()
1444 gboolean procmsg_msg_has_marked_parent(MsgInfo *info)
1446 return procmsg_msg_has_flagged_parent(info, MSG_MARKED);
1450 GSList *procmsg_find_children_func(MsgInfo *info,
1451 GSList *children, GSList *all)
1455 g_return_val_if_fail(info!=NULL, children);
1456 if (info->msgid == NULL)
1459 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1460 MsgInfo *tmp = (MsgInfo *)cur->data;
1461 if (tmp->inreplyto && !strcmp(tmp->inreplyto, info->msgid)) {
1462 /* Check if message is already in the list */
1463 if ((children == NULL) ||
1464 (g_slist_index(children, tmp) == -1)) {
1465 children = g_slist_prepend(children,
1466 procmsg_msginfo_new_ref(tmp));
1467 children = procmsg_find_children_func(tmp,
1476 GSList *procmsg_find_children (MsgInfo *info)
1481 g_return_val_if_fail(info!=NULL, NULL);
1482 all = folder_item_get_msg_list(info->folder);
1483 children = procmsg_find_children_func(info, NULL, all);
1484 if (children != NULL) {
1485 for (cur = all; cur != NULL; cur = g_slist_next(cur)) {
1486 /* this will not free the used pointers
1487 created with procmsg_msginfo_new_ref */
1488 procmsg_msginfo_free((MsgInfo *)cur->data);
1496 void procmsg_update_unread_children(MsgInfo *info, gboolean newly_marked)
1498 GSList *children = procmsg_find_children(info);
1500 for (cur = children; cur != NULL; cur = g_slist_next(cur)) {
1501 MsgInfo *tmp = (MsgInfo *)cur->data;
1502 if(MSG_IS_UNREAD(tmp->flags) && !MSG_IS_IGNORE_THREAD(tmp->flags)) {
1504 info->folder->unreadmarked_msgs++;
1506 info->folder->unreadmarked_msgs--;
1507 folder_item_update(info->folder, F_ITEM_UPDATE_MSGCNT);
1509 procmsg_msginfo_free(tmp);
1511 g_slist_free(children);
1515 * Set the destination folder for a copy or move operation
1517 * \param msginfo The message which's destination folder is changed
1518 * \param to_folder The destination folder for the operation
1520 void procmsg_msginfo_set_to_folder(MsgInfo *msginfo, FolderItem *to_folder)
1522 if(msginfo->to_folder != NULL) {
1523 msginfo->to_folder->op_count--;
1524 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1526 msginfo->to_folder = to_folder;
1527 if(to_folder != NULL) {
1528 to_folder->op_count++;
1529 folder_item_update(msginfo->to_folder, F_ITEM_UPDATE_MSGCNT);
1534 * Apply filtering actions to the msginfo
1536 * \param msginfo The MsgInfo describing the message that should be filtered
1537 * \return TRUE if the message was moved and MsgInfo is now invalid,
1540 gboolean procmsg_msginfo_filter(MsgInfo *msginfo)
1542 MailFilteringData mail_filtering_data;
1544 mail_filtering_data.msginfo = msginfo;
1545 if (hooks_invoke(MAIL_FILTERING_HOOKLIST, &mail_filtering_data))
1548 /* filter if enabled in prefs or move to inbox if not */
1549 if((global_processing != NULL) &&
1550 filter_message_by_msginfo(global_processing, msginfo))