summaryrefslogtreecommitdiff
path: root/pith/save.c
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/save.c
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'pith/save.c')
-rw-r--r--pith/save.c1777
1 files changed, 1777 insertions, 0 deletions
diff --git a/pith/save.c b/pith/save.c
new file mode 100644
index 00000000..957e163b
--- /dev/null
+++ b/pith/save.c
@@ -0,0 +1,1777 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: save.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2009 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/save.h"
+#include "../pith/state.h"
+#include "../pith/conf.h"
+#include "../pith/mimedesc.h"
+#include "../pith/filter.h"
+#include "../pith/context.h"
+#include "../pith/folder.h"
+#include "../pith/copyaddr.h"
+#include "../pith/mailview.h"
+#include "../pith/mailcmd.h"
+#include "../pith/bldaddr.h"
+#include "../pith/flag.h"
+#include "../pith/status.h"
+#include "../pith/ablookup.h"
+#include "../pith/news.h"
+#include "../pith/util.h"
+#include "../pith/reply.h"
+#include "../pith/sequence.h"
+#include "../pith/stream.h"
+#include "../pith/options.h"
+
+
+/*
+ * Internal prototypes
+ */
+int save_ex_replace_body(char *, unsigned long *,BODY *,gf_io_t);
+int save_ex_output_body(MAILSTREAM *, long, char *, BODY *, unsigned long *, gf_io_t);
+int save_ex_mask_types(char *, unsigned long *, gf_io_t);
+int save_ex_explain_body(BODY *, unsigned long *, gf_io_t);
+int save_ex_explain_parts(BODY *, int, unsigned long *, gf_io_t);
+int save_ex_output_line(char *, unsigned long *, gf_io_t);
+
+
+/*
+ * pith hook
+ */
+int (*pith_opt_save_create_prompt)(CONTEXT_S *, char *, int);
+int (*pith_opt_save_size_changed_prompt)(long, int);
+
+
+
+/*----------------------------------------------------------------------
+ save_get_default - return default folder name for saving
+ ----*/
+char *
+save_get_default(struct pine *state, ENVELOPE *e, long int rawno,
+ char *section, CONTEXT_S **cntxt)
+{
+ int context_was_set;
+
+ if(!cntxt)
+ return("");
+
+ context_was_set = ((*cntxt) != NULL);
+
+ /* start with the default save context */
+ if(!(*cntxt)
+ && ((*cntxt) = default_save_context(state->context_list)) == NULL)
+ (*cntxt) = state->context_list;
+
+ if(!e || ps_global->save_msg_rule == SAV_RULE_LAST
+ || ps_global->save_msg_rule == SAV_RULE_DEFLT){
+ if(ps_global->save_msg_rule == SAV_RULE_LAST && ps_global->last_save_context){
+ if(!context_was_set)
+ (*cntxt) = ps_global->last_save_context;
+ }
+ else{
+ strncpy(ps_global->last_save_folder,
+ ps_global->VAR_DEFAULT_SAVE_FOLDER,
+ sizeof(ps_global->last_save_folder)-1);
+ ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
+
+ /*
+ * If the user entered "inbox" as their default save folder it is very
+ * likely they meant the real inbox, not the inbox in the primary collection.
+ */
+ if(!context_was_set
+ && !strucmp(ps_global->inbox_name, ps_global->last_save_folder))
+ (*cntxt) = state->context_list;
+ }
+ }
+ else{
+ save_get_fldr_from_env(ps_global->last_save_folder,
+ sizeof(ps_global->last_save_folder),
+ e, state, rawno, section);
+ /* somebody expunged current message */
+ if(sp_expunge_count(ps_global->mail_stream))
+ return(NULL);
+ }
+
+ return(ps_global->last_save_folder);
+}
+
+
+
+/*----------------------------------------------------------------------
+ Grope through envelope to find default folder name to save to
+
+ Args: fbuf -- Buffer to return result in
+ nfbuf -- Size of fbuf
+ e -- The envelope to look in
+ state -- Usual pine state
+ rawmsgno -- Raw c-client sequence number of message
+ section -- Mime section of header data (for message/rfc822)
+
+ Result: The appropriate default folder name is copied into fbuf.
+ ----*/
+void
+save_get_fldr_from_env(char *fbuf, int nfbuf, ENVELOPE *e, struct pine *state,
+ long int rawmsgno, char *section)
+{
+ char fakedomain[2];
+ ADDRESS *tmp_adr = NULL;
+ char buf[MAX(MAXFOLDER,MAX_NICKNAME) + 1];
+ char *bufp;
+ char *folder_name = NULL;
+ static char botch[] = "programmer botch, unknown message save rule";
+ unsigned save_msg_rule;
+
+ if(!e)
+ return;
+
+ /* copy this because we might change it below */
+ save_msg_rule = state->save_msg_rule;
+
+ /* first get the relevant address to base the folder name on */
+ switch(save_msg_rule){
+ case SAV_RULE_FROM:
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_NICK_FROM_DEF:
+ case SAV_RULE_FCC_FROM:
+ case SAV_RULE_FCC_FROM_DEF:
+ case SAV_RULE_RN_FROM:
+ case SAV_RULE_RN_FROM_DEF:
+ tmp_adr = e->from ? copyaddr(e->from)
+ : e->sender ? copyaddr(e->sender) : NULL;
+ break;
+
+ case SAV_RULE_SENDER:
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_NICK_SENDER_DEF:
+ case SAV_RULE_FCC_SENDER:
+ case SAV_RULE_FCC_SENDER_DEF:
+ case SAV_RULE_RN_SENDER:
+ case SAV_RULE_RN_SENDER_DEF:
+ tmp_adr = e->sender ? copyaddr(e->sender)
+ : e->from ? copyaddr(e->from) : NULL;
+ break;
+
+ case SAV_RULE_REPLYTO:
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_NICK_REPLYTO_DEF:
+ case SAV_RULE_FCC_REPLYTO:
+ case SAV_RULE_FCC_REPLYTO_DEF:
+ case SAV_RULE_RN_REPLYTO:
+ case SAV_RULE_RN_REPLYTO_DEF:
+ tmp_adr = e->reply_to ? copyaddr(e->reply_to)
+ : e->from ? copyaddr(e->from)
+ : e->sender ? copyaddr(e->sender) : NULL;
+ break;
+
+ case SAV_RULE_RECIP:
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_NICK_RECIP_DEF:
+ case SAV_RULE_FCC_RECIP:
+ case SAV_RULE_FCC_RECIP_DEF:
+ case SAV_RULE_RN_RECIP:
+ case SAV_RULE_RN_RECIP_DEF:
+ /* news */
+ if(state->mail_stream && IS_NEWS(state->mail_stream)){
+ char *tmp_a_string, *ng_name;
+
+ fakedomain[0] = '@';
+ fakedomain[1] = '\0';
+
+ /* find the news group name */
+ if((ng_name = strstr(state->mail_stream->mailbox,"#news")) != NULL)
+ ng_name += 6;
+ else
+ ng_name = state->mail_stream->mailbox; /* shouldn't happen */
+
+ /* copy this string so rfc822_parse_adrlist can't blast it */
+ tmp_a_string = cpystr(ng_name);
+ /* make an adr */
+ rfc822_parse_adrlist(&tmp_adr, tmp_a_string, fakedomain);
+ fs_give((void **)&tmp_a_string);
+ if(tmp_adr && tmp_adr->host && tmp_adr->host[0] == '@')
+ tmp_adr->host[0] = '\0';
+ }
+ /* not news */
+ else{
+ static char *fields[] = {"Resent-To", NULL};
+ char *extras, *values[sizeof(fields)/sizeof(fields[0])];
+
+ extras = pine_fetchheader_lines(state->mail_stream, rawmsgno,
+ section, fields);
+ if(extras){
+ long i;
+
+ memset(values, 0, sizeof(fields));
+ simple_header_parse(extras, fields, values);
+ fs_give((void **)&extras);
+
+ for(i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
+ if(values[i]){
+ if(tmp_adr) /* take last matching value */
+ mail_free_address(&tmp_adr);
+
+ /* build a temporary address list */
+ fakedomain[0] = '@';
+ fakedomain[1] = '\0';
+ rfc822_parse_adrlist(&tmp_adr, values[i], fakedomain);
+ fs_give((void **)&values[i]);
+ }
+ }
+
+ if(!tmp_adr)
+ tmp_adr = e->to ? copyaddr(e->to) : NULL;
+ }
+
+ break;
+
+ default:
+ panic(botch);
+ break;
+ }
+
+ /* For that address, lookup the fcc or nickname from address book */
+ switch(save_msg_rule){
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_FCC_FROM:
+ case SAV_RULE_FCC_SENDER:
+ case SAV_RULE_FCC_REPLYTO:
+ case SAV_RULE_FCC_RECIP:
+ case SAV_RULE_NICK_FROM_DEF:
+ case SAV_RULE_NICK_SENDER_DEF:
+ case SAV_RULE_NICK_REPLYTO_DEF:
+ case SAV_RULE_NICK_RECIP_DEF:
+ case SAV_RULE_FCC_FROM_DEF:
+ case SAV_RULE_FCC_SENDER_DEF:
+ case SAV_RULE_FCC_REPLYTO_DEF:
+ case SAV_RULE_FCC_RECIP_DEF:
+ switch(save_msg_rule){
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_NICK_FROM_DEF:
+ case SAV_RULE_NICK_SENDER_DEF:
+ case SAV_RULE_NICK_REPLYTO_DEF:
+ case SAV_RULE_NICK_RECIP_DEF:
+ bufp = get_nickname_from_addr(tmp_adr, buf, sizeof(buf));
+ break;
+
+ case SAV_RULE_FCC_FROM:
+ case SAV_RULE_FCC_SENDER:
+ case SAV_RULE_FCC_REPLYTO:
+ case SAV_RULE_FCC_RECIP:
+ case SAV_RULE_FCC_FROM_DEF:
+ case SAV_RULE_FCC_SENDER_DEF:
+ case SAV_RULE_FCC_REPLYTO_DEF:
+ case SAV_RULE_FCC_RECIP_DEF:
+ bufp = get_fcc_from_addr(tmp_adr, buf, sizeof(buf));
+ break;
+ }
+
+ if(bufp && *bufp){
+ istrncpy(fbuf, bufp, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ }
+ else
+ /* fall back to non-nick/non-fcc version of rule */
+ switch(save_msg_rule){
+ case SAV_RULE_NICK_FROM:
+ case SAV_RULE_FCC_FROM:
+ save_msg_rule = SAV_RULE_FROM;
+ break;
+
+ case SAV_RULE_NICK_SENDER:
+ case SAV_RULE_FCC_SENDER:
+ save_msg_rule = SAV_RULE_SENDER;
+ break;
+
+ case SAV_RULE_NICK_REPLYTO:
+ case SAV_RULE_FCC_REPLYTO:
+ save_msg_rule = SAV_RULE_REPLYTO;
+ break;
+
+ case SAV_RULE_NICK_RECIP:
+ case SAV_RULE_FCC_RECIP:
+ save_msg_rule = SAV_RULE_RECIP;
+ break;
+
+ default:
+ istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ break;
+ }
+
+ break;
+ }
+
+ /* Realname */
+ switch(save_msg_rule){
+ case SAV_RULE_RN_FROM_DEF:
+ case SAV_RULE_RN_FROM:
+ case SAV_RULE_RN_SENDER_DEF:
+ case SAV_RULE_RN_SENDER:
+ case SAV_RULE_RN_RECIP_DEF:
+ case SAV_RULE_RN_RECIP:
+ case SAV_RULE_RN_REPLYTO_DEF:
+ case SAV_RULE_RN_REPLYTO:
+ /* Fish out the realname */
+ if(tmp_adr && tmp_adr->personal && tmp_adr->personal[0])
+ folder_name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, tmp_adr->personal);
+
+ if(folder_name && folder_name[0]){
+ istrncpy(fbuf, folder_name, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ }
+ else{ /* fall back to other behaviors */
+ switch(save_msg_rule){
+ case SAV_RULE_RN_FROM:
+ save_msg_rule = SAV_RULE_FROM;
+ break;
+
+ case SAV_RULE_RN_SENDER:
+ save_msg_rule = SAV_RULE_SENDER;
+ break;
+
+ case SAV_RULE_RN_RECIP:
+ save_msg_rule = SAV_RULE_RECIP;
+ break;
+
+ case SAV_RULE_RN_REPLYTO:
+ save_msg_rule = SAV_RULE_REPLYTO;
+ break;
+
+ default:
+ istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ break;
+ }
+ }
+
+ break;
+ }
+
+ /* get the username out of the mailbox for this address */
+ switch(save_msg_rule){
+ case SAV_RULE_FROM:
+ case SAV_RULE_SENDER:
+ case SAV_RULE_REPLYTO:
+ case SAV_RULE_RECIP:
+ /*
+ * Fish out the user's name from the mailbox portion of
+ * the address and put it in folder.
+ */
+ folder_name = (tmp_adr && tmp_adr->mailbox && tmp_adr->mailbox[0])
+ ? tmp_adr->mailbox : NULL;
+ if(!get_uname(folder_name, fbuf, nfbuf)){
+ istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
+ fbuf[nfbuf - 1] = '\0';
+ }
+
+ break;
+ }
+
+ if(tmp_adr)
+ mail_free_address(&tmp_adr);
+}
+
+
+/*----------------------------------------------------------------------
+ Do the work of actually saving messages to a folder
+
+ Args: state -- pine state struct (for stream pointers)
+ stream -- source stream, which msgmap refers to
+ context -- context to interpret name in if not fully qualified
+ folder -- The folder to save the message in
+ msgmap -- message map of currently selected messages
+ flgs -- Possible bits are
+ SV_DELETE - delete after saving
+ SV_FOR_FILT - called from filtering function, not save
+ SV_FIX_DELS - remove Del mark before saving
+ SV_INBOXWOCNTXT - "inbox" is interpreted without context making
+ it the one-true inbox instead
+
+ Result: Returns number of messages saved
+
+ Note: There's a bit going on here; temporary clearing of deleted flags
+ since they are *not* preserved, picking or creating the stream for
+ copy or append, and dealing with errors...
+ We try to preserve user keywords by setting them in the destination.
+ ----*/
+long
+save(struct pine *state, MAILSTREAM *stream, CONTEXT_S *context, char *folder,
+ MSGNO_S *msgmap, int flgs)
+{
+ int rv, rc, j, our_stream = 0, cancelled = 0;
+ int delete, filter, k, worry_about_keywords = 0;
+ char *save_folder, *seq, *flags = NULL, date[64], tmp[MAILTMPLEN];
+ long i, nmsgs, rawno;
+ size_t len;
+ STORE_S *so = NULL;
+ MAILSTREAM *save_stream = NULL;
+ MESSAGECACHE *mc;
+
+ delete = flgs & SV_DELETE;
+ filter = flgs & SV_FOR_FILT;
+
+ if(strucmp(folder, state->inbox_name) == 0 && flgs & SV_INBOXWOCNTXT){
+ save_folder = state->VAR_INBOX_PATH;
+ context = NULL;
+ }
+ else
+ save_folder = folder;
+
+ /*
+ * Because the COPY/APPEND command doesn't always create keywords when they
+ * aren't already defined in a mailbox, we need to ensure that the keywords
+ * exist in the destination (are defined and settable) before we do the copies.
+ * Here's what the code is doing
+ *
+ * If we have keywords set in the source messages
+ * Add a dummy message to destination mailbox
+ *
+ * for each keyword that is set in the set of messages we're saving
+ * set the keyword in that message (thus creating it)
+ *
+ * remember deleted messages
+ * undelete them
+ * delete dummy message
+ * expunge
+ * delete remembered messages
+ *
+ * After that the assumption is that the keywords will be saved by a
+ * COPY command. We need to set the flags string ourself for appends.
+ */
+
+ /* are any keywords set in the source messages? */
+ for(i = mn_first_cur(msgmap); !worry_about_keywords && i > 0L; i = mn_next_cur(msgmap)){
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && mc->user_flags)
+ worry_about_keywords++;
+ }
+
+ if(worry_about_keywords){
+ MAILSTREAM *dstn_stream = NULL;
+ int already_open = 0;
+ int we_blocked_reuse = 0;
+
+ /*
+ * Possible problem created by our stream re-use
+ * strategy. If we are going to open a new stream
+ * here, we want to be sure not to re-use the
+ * stream we are saving _from_, so take it out of the
+ * re-use pool before we call open.
+ */
+ if(sp_flagged(stream, SP_USEPOOL)){
+ we_blocked_reuse++;
+ sp_unflag(stream, SP_USEPOOL);
+ }
+
+ /* see if there is a stream open already */
+ if(!dstn_stream){
+ dstn_stream = context_already_open_stream(context,
+ save_folder,
+ AOS_RW_ONLY);
+ already_open = dstn_stream ? 1 : 0;
+ }
+
+ if(!dstn_stream)
+ dstn_stream = context_open(context, NULL,
+ save_folder,
+ SP_USEPOOL | SP_TEMPUSE,
+ NULL);
+
+ if(dstn_stream && dstn_stream->kwd_create){
+ imapuid_t dummy_uid = 0L;
+ long dummy_msgno, delete_count;
+ int expbits, set;
+ char *flags = NULL;
+ char *user_flag_name, *duser_flag_name;
+
+ /* find keywords that need to be defined */
+ for(k = 0; stream_to_user_flag_name(stream, k); k++){
+ user_flag_name = stream_to_user_flag_name(stream, k);
+ if(user_flag_name && user_flag_name[0]){
+ /* is this flag set in any of the save set? */
+ for(set = 0, i = mn_first_cur(msgmap);
+ !set && i > 0L;
+ i = mn_next_cur(msgmap)){
+ rawno = mn_m2raw(msgmap, i);
+ if(user_flag_is_set(stream, rawno, user_flag_name))
+ set++;
+ }
+
+ if(set){
+ /*
+ * The flag may already be defined in this
+ * mailbox. Check for that first.
+ */
+ for(j = 0; stream_to_user_flag_name(dstn_stream, j); j++){
+ duser_flag_name = stream_to_user_flag_name(dstn_stream, j);
+ if(duser_flag_name && duser_flag_name[0]
+ && !strucmp(duser_flag_name, user_flag_name)){
+ set = 0;
+ break;
+ }
+ }
+ }
+
+ if(set){
+ if(flags == NULL){
+ len = strlen(user_flag_name) + 1;
+ flags = (char *) fs_get((len+1) * sizeof(char));
+ snprintf(flags, len+1, "%s ", user_flag_name);
+ }
+ else{
+ char *p;
+ size_t newlen;
+
+ newlen = strlen(user_flag_name) + 1;
+ len += newlen;
+ fs_resize((void **) &flags, (len+1) * sizeof(char));
+ p = flags + strlen(flags);
+ snprintf(p, newlen+1, "%s ", user_flag_name);
+ }
+ }
+ }
+ }
+
+ if(flags){
+ char *p;
+ size_t newlen;
+ STRING msg;
+ char dummymsg[1000];
+ char *id = NULL;
+ char *idused;
+ appenduid_t *au;
+
+ newlen = strlen("\\DELETED");
+ len += newlen;
+ fs_resize((void **) &flags, (len+1) * sizeof(char));
+ p = flags + strlen(flags);
+ snprintf(p, newlen+1, "%s", "\\DELETED");
+
+ id = generate_message_id();
+ idused = id ? id : "<xyz>";
+ snprintf(dummymsg, sizeof(dummymsg), "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\nMessage-ID: %s\r\n\r\ndummy\r\n", idused);
+
+ /*
+ * We need to get the uid of the message we are about to
+ * append so that we can delete it when we're done and
+ * so we don't affect other messages.
+ */
+
+ if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream)){
+ au = mail_parameters(NIL, GET_APPENDUID, NIL);
+ mail_parameters(NIL, SET_APPENDUID, (void *) appenduid_cb);
+ }
+
+ INIT(&msg, mail_string, (void *) dummymsg, strlen(dummymsg));
+ if(pine_mail_append(dstn_stream, dstn_stream->mailbox, &msg)){
+
+ (void) pine_mail_ping(dstn_stream);
+
+ if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
+ dummy_uid = get_last_append_uid();
+
+ if(dummy_uid == 0L){
+ dummy_msgno = get_msgno_by_msg_id(dstn_stream, idused,
+ sp_msgmap(dstn_stream));
+ if(dummy_msgno <= 0L || dummy_msgno > dstn_stream->nmsgs)
+ dummy_msgno = dstn_stream->nmsgs;
+
+ rawno = mn_m2raw(sp_msgmap(dstn_stream), dummy_msgno);
+ if(rawno > 0L && rawno <= dstn_stream->nmsgs)
+ dummy_uid = mail_uid(dstn_stream, rawno);
+
+ if(dummy_uid == 0L)
+ dummy_msgno = dstn_stream->nmsgs;
+ }
+
+ /*
+ * We need to remember which messages are deleted,
+ * undelete them, do the expunge, then delete them again.
+ */
+ delete_count = count_flagged(dstn_stream, F_DEL);
+ if(delete_count){
+ for(i = 1L; i <= dstn_stream->nmsgs; i++)
+ if(((mc = mail_elt(dstn_stream, i)) && mc->valid && mc->deleted)
+ || (mc && !mc->valid && mc->searched)){
+ mc->sequence = 1;
+ expbits = MSG_EX_DELETE;
+ msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
+ }
+ else if((mc = mail_elt(dstn_stream, i)) != NULL)
+ mc->sequence = 0;
+
+ if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
+ mail_flag(dstn_stream, seq, "\\DELETED", ST_SILENT);
+ fs_give((void **) &seq);
+ }
+ }
+
+ if(dummy_uid > 0L)
+ mail_flag(dstn_stream, ulong2string(dummy_uid),
+ flags, ST_SET | ST_UID | ST_SILENT);
+ else
+ mail_flag(dstn_stream, ulong2string(dummy_msgno),
+ flags, ST_SET | ST_SILENT);
+
+ ps_global->mm_log_error = 0;
+ ps_global->expunge_in_progress = 1;
+ mail_expunge(dstn_stream);
+ ps_global->expunge_in_progress = 0;
+
+ if(delete_count){
+ for(i = 1L; i <= dstn_stream->nmsgs; i++)
+ if((mc = mail_elt(dstn_stream, i)) != NULL){
+ mc->sequence
+ = (msgno_exceptions(dstn_stream, i, "0", &expbits, FALSE)
+ && (expbits & MSG_EX_DELETE));
+
+ /*
+ * Remove the EX_DELETE bit in case we're still using
+ * this stream.
+ */
+ if(mc->sequence){
+ expbits &= ~MSG_EX_DELETE;
+ msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
+ }
+ }
+
+ if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
+ mail_flag(dstn_stream, seq, "\\DELETED", ST_SET | ST_SILENT);
+ fs_give((void **) &seq);
+ }
+ }
+ }
+
+ if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
+ mail_parameters(NIL, SET_APPENDUID, (void *) au);
+
+ if(id)
+ fs_give((void **) &id);
+
+ fs_give((void **) &flags);
+ }
+ }
+
+ if(dstn_stream && !already_open)
+ pine_mail_close(dstn_stream);
+
+ if(we_blocked_reuse)
+ sp_set_flags(stream, sp_flags(stream) | SP_USEPOOL);
+ }
+
+ /*
+ * If any of the messages have exceptional attachment handling
+ * we have to fall thru below to do the APPEND by hand...
+ */
+ if(!msgno_any_deletedparts(stream, msgmap)){
+ int loc_to_loc = 0;
+
+ /*
+ * Compare the current stream (the save's source) and the stream
+ * the destination folder will need...
+ */
+ context_apply(tmp, context, save_folder, sizeof(tmp));
+
+ /*
+ * If we're going to be doing a cross-format copy we're better off
+ * using the else code below that knows how to do multi-append.
+ * The part in the if is leaving save_stream set to NULL in the
+ * case that the stream is local and the folder is local and they
+ * are different formats (like unix and tenex). That will cause us
+ * to fall thru to the APPEND case which is faster than using
+ * copy which will use our imap_proxycopy which doesn't know
+ * about multiappend.
+ */
+ loc_to_loc = stream && stream->dtb
+ && stream->dtb->flags & DR_LOCAL && !IS_REMOTE(tmp);
+ if(!loc_to_loc || (stream->dtb->valid && (*stream->dtb->valid)(tmp)))
+ save_stream = loc_to_loc ? stream
+ : context_same_stream(context, save_folder, stream);
+ }
+
+ /* if needed, this'll get set in mm_notify */
+ ps_global->try_to_create = 0;
+ rv = rc = 0;
+ nmsgs = 0L;
+
+ /*
+ * At this point, if we have a save_stream, then none of the messages
+ * being saved involve special handling that would require our use
+ * of mail_append, so go with mail_copy since in the IMAP case it
+ * means no data on the wire...
+ */
+ if(save_stream){
+ char *dseq = NULL, *oseq;
+
+ if((flgs & SV_FIX_DELS) &&
+ (dseq = currentf_sequence(stream, msgmap, F_DEL, NULL,
+ 0, NULL, NULL)))
+ mail_flag(stream, dseq, "\\DELETED", 0L);
+
+ seq = currentf_sequence(stream, msgmap, 0L, &nmsgs, 0, NULL, NULL);
+ if(!(flgs & SV_PRESERVE)
+ && (F_ON(F_AGG_SEQ_COPY, ps_global)
+ || (mn_get_sort(msgmap) == SortArrival && !mn_get_revsort(msgmap)))){
+
+ /*
+ * currentf_sequence() above lit all the elt "sequence"
+ * bits of the interesting messages. Now, build a sequence
+ * that preserves sort order...
+ */
+ oseq = build_sequence(stream, msgmap, &nmsgs);
+ }
+ else{
+ oseq = NULL; /* no single sequence! */
+ nmsgs = 0L;
+ i = mn_first_cur(msgmap); /* set first to copy */
+ }
+
+ do{
+ while(!(rv = (int) context_copy(context, save_stream,
+ oseq ? oseq : long2string(mn_m2raw(msgmap, i)),
+ save_folder))){
+ if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
+ break; /* c-client returned error? */
+
+ if((context && context->use & CNTXT_INCMNG)
+ && context_isambig(save_folder)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can only save to existing folders in Incoming Collection"));
+ break;
+ }
+
+ ps_global->try_to_create = 0; /* reset for next time */
+ if((j = create_for_save(context, save_folder)) < 1){
+ if(j < 0)
+ cancelled = 1; /* user cancels */
+
+ break;
+ }
+ }
+
+ if(rv){ /* failure or finished? */
+ if(oseq) /* all done? */
+ break;
+ else
+ nmsgs++;
+ }
+ else{ /* failure! */
+ if(oseq)
+ nmsgs = 0L; /* nothing copy'd */
+
+ break;
+ }
+ }
+ while((i = mn_next_cur(msgmap)) > 0L);
+
+ if(rv && delete) /* delete those saved */
+ mail_flag(stream, seq, "\\DELETED", ST_SET);
+ else if(dseq) /* or restore previous state */
+ mail_flag(stream, dseq, "\\DELETED", ST_SET);
+
+ if(dseq) /* clean up */
+ fs_give((void **)&dseq);
+
+ if(oseq)
+ fs_give((void **)&oseq);
+
+ fs_give((void **)&seq);
+ }
+ else{
+ /*
+ * Special handling requires mail_append. See if we can
+ * re-use stream source messages are on...
+ */
+ save_stream = context_same_stream(context, save_folder, stream);
+
+ /*
+ * IF the destination's REMOTE, open a stream here so c-client
+ * doesn't have to open it for each aggregate save...
+ */
+ if(!save_stream){
+ if(context_apply(tmp, context, save_folder, sizeof(tmp))[0] == '{'
+ && (save_stream = context_open(context, NULL,
+ save_folder,
+ OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
+ NULL)))
+ our_stream = 1;
+ }
+
+ /*
+ * Allocate a storage object to temporarily store the message
+ * object in. Below it'll get mapped into a c-client STRING struct
+ * in preparation for handing off to context_append...
+ */
+ if(!(so = so_get(CharStar, NULL, WRITE_ACCESS))){
+ dprint((1, "Can't allocate store for save: %s\n",
+ error_description(errno)));
+ q_status_message(SM_ORDER | SM_DING, 3, 4,
+ "Problem creating space for message text.");
+ }
+
+ /*
+ * get a sequence of invalid elt's so we can get their flags...
+ */
+ if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
+ mail_fetch_fast(stream, seq, 0L);
+ fs_give((void **) &seq);
+ }
+
+ /*
+ * If we're supposed set the deleted flag, clear the elt bit
+ * we'll use to build the sequence later...
+ */
+ if(delete)
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->sequence = 0;
+
+ nmsgs = 0L;
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
+
+ /*
+ * if there is more than one message, do multiappend.
+ * otherwise, we can use our already open stream.
+ */
+ if(!save_stream || !is_imap_stream(save_stream) ||
+ (LEVELMULTIAPPEND(save_stream) && mn_total_cur(msgmap) > 1)){
+ APPENDPACKAGE pkg;
+ STRING msg;
+
+ pkg.stream = stream;
+ /* tell save_fetch_append_cb whether or not to leave deleted flag */
+ pkg.flags = flgs & SV_FIX_DELS ? NULL : cpystr("\\DELETED");
+ pkg.date = date;
+ pkg.msg = &msg;
+ pkg.msgmap = msgmap;
+
+ if ((pkg.so = so) && ((pkg.msgno = mn_first_cur(msgmap)) > 0L)) {
+ so_truncate(so, 0L);
+
+ /*
+ * we've gotta make sure this is a stream that we've
+ * opened ourselves.
+ */
+ rc = 0;
+ while(!(rv = context_append_multiple(context,
+ our_stream ? save_stream
+ : NULL, save_folder,
+ save_fetch_append_cb,
+ &pkg,
+ stream))) {
+
+ if(rc++ || !ps_global->try_to_create)
+ break;
+ if((context && context->use & CNTXT_INCMNG)
+ && context_isambig(save_folder)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can only save to existing folders in Incoming Collection"));
+ break;
+ }
+
+ ps_global->try_to_create = 0;
+ if((j = create_for_save(context, save_folder)) < 1){
+ if(j < 0)
+ cancelled = 1;
+ break;
+ }
+ }
+
+ if(pkg.flags)
+ fs_give((void **) &pkg.flags);
+
+ ps_global->noshow_error = 0;
+
+ if(rv){
+ /*
+ * Success! Count it, and if it's not already deleted and
+ * it's supposed to be, mark it to get deleted later...
+ */
+ for(i = mn_first_cur(msgmap); so && i > 0L;
+ i = mn_next_cur(msgmap)){
+ nmsgs++;
+ if(delete){
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && !mc->deleted)
+ mc->sequence = 1; /* mark for later deletion */
+ }
+ }
+ }
+ }
+ else
+ cancelled = 1; /* No messages to append! */
+
+ if(sp_expunge_count(stream))
+ cancelled = 1; /* All bets are off! */
+ }
+ else
+ for(i = mn_first_cur(msgmap); so && i > 0L; i = mn_next_cur(msgmap)){
+ int preserve_these_flags;
+
+ so_truncate(so, 0L);
+
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+
+ /* always preserve these flags */
+ preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
+ /* maybe preserve deleted flag */
+ preserve_these_flags |= flgs & SV_FIX_DELS ? 0 : F_DEL;
+ flags = flag_string(stream, rawno, preserve_these_flags);
+
+ if(mc && mc->day)
+ mail_date(date, mc);
+ else
+ *date = '\0';
+
+ rv = save_fetch_append(stream, mn_m2raw(msgmap, i),
+ NULL, save_stream, save_folder, context,
+ mc ? mc->rfc822_size : 0L, flags, date, so);
+
+ if(flags)
+ fs_give((void **) &flags);
+
+ if(sp_expunge_count(stream))
+ rv = -1; /* All bets are off! */
+
+ if(rv == 1){
+ /*
+ * Success! Count it, and if it's not already deleted and
+ * it's supposed to be, mark it to get deleted later...
+ */
+ nmsgs++;
+ if(delete){
+ rawno = mn_m2raw(msgmap, i);
+ mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
+ ? mail_elt(stream, rawno) : NULL;
+ if(mc && !mc->deleted)
+ mc->sequence = 1; /* mark for later deletion */
+ }
+ }
+ else{
+ if(rv == -1)
+ cancelled = 1; /* else horrendous failure */
+
+ break;
+ }
+ }
+
+ if(pith_opt_save_size_changed_prompt)
+ (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
+
+ if(our_stream)
+ pine_mail_close(save_stream);
+
+ if(so)
+ so_give(&so);
+
+ if(delete && (seq = build_sequence(stream, NULL, NULL))){
+ mail_flag(stream, seq, "\\DELETED", ST_SET);
+ fs_give((void **)&seq);
+ }
+ }
+
+ ps_global->try_to_create = 0; /* reset for next time */
+ if(!cancelled && nmsgs < mn_total_cur(msgmap)){
+ dprint((1, "FAILED save of msg %s (c-client sequence #)\n",
+ long2string(mn_m2raw(msgmap, mn_get_cur(msgmap)))));
+ if((mn_total_cur(msgmap) > 1L) && nmsgs != 0){
+ /* this shouldn't happen cause it should be all or nothing */
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%ld of %ld messages saved before error occurred",
+ nmsgs, mn_total_cur(msgmap));
+ dprint((1, "\t%s\n", tmp_20k_buf));
+ q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
+ }
+ else if(mn_total_cur(msgmap) == 1){
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%s to folder \"%s\" FAILED",
+ filter ? "Filter" : "Save",
+ strsquish(tmp_20k_buf+500, 500, folder, 35));
+ dprint((1, "\t%s\n", tmp_20k_buf));
+ q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ else{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF,
+ "%s of %s messages to folder \"%s\" FAILED",
+ filter ? "Filter" : "Save", comatose(mn_total_cur(msgmap)),
+ strsquish(tmp_20k_buf+500, 500, folder, 35));
+ dprint((1, "\t%s\n", tmp_20k_buf));
+ q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
+ }
+ }
+
+ return(nmsgs);
+}
+
+
+/* Append message callback
+ * Accepts: MAIL stream
+ * append data package
+ * pointer to return initial flags
+ * pointer to return message internal date
+ * pointer to return stringstruct of message or NIL to stop
+ * Returns: T if success (have message or stop), NIL if error
+ */
+
+long save_fetch_append_cb(MAILSTREAM *stream, void *data, char **flags,
+ char **date, STRING **message)
+{
+ unsigned long size = 0;
+ APPENDPACKAGE *pkg = (APPENDPACKAGE *) data;
+ MESSAGECACHE *mc;
+ char *fetch;
+ int rc;
+ unsigned long raw, hlen, tlen, mlen;
+
+ if(pkg->so && (pkg->msgno > 0L)) {
+ raw = mn_m2raw(pkg->msgmap, pkg->msgno);
+ mc = (raw > 0L && pkg->stream && raw <= pkg->stream->nmsgs)
+ ? mail_elt(pkg->stream, raw) : NULL;
+ if(mc){
+ int preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
+
+ size = mc->rfc822_size;
+
+ /*
+ * If the caller wants us to preserve the state of the
+ * \DELETED flag then pkg->flags will be \DELETED, otherwise
+ * let it be undeleted. Fix from Eduardo Chappa.
+ */
+ if(pkg->flags){
+ if(strstr(pkg->flags,"\\DELETED"))
+ preserve_these_flags |= F_DEL;
+
+ /* not used anymore */
+ fs_give((void **) &pkg->flags);
+ }
+
+ pkg->flags = flag_string(pkg->stream, raw, preserve_these_flags);
+ }
+
+ if(mc && mc->day)
+ mail_date(pkg->date, mc);
+ else
+ *pkg->date = '\0';
+ if((fetch = mail_fetch_header(pkg->stream, raw, NULL, NULL, &hlen,
+ FT_PEEK)) != NULL){
+ if(!*pkg->date)
+ saved_date(pkg->date, fetch);
+ }
+ else
+ return(0); /* fetchtext writes error */
+
+ rc = MSG_EX_DELETE; /* "rc" overloaded */
+ if(msgno_exceptions(pkg->stream, raw, NULL, &rc, 0)){
+ char section[64];
+ int failure = 0;
+ BODY *body;
+ gf_io_t pc;
+
+ size = 0; /* all bets off, abort sanity test */
+ gf_set_so_writec(&pc, pkg->so);
+
+ section[0] = '\0';
+ if(!pine_mail_fetch_structure(pkg->stream, raw, &body, 0))
+ return(0);
+
+ if(msgno_part_deleted(pkg->stream, raw, "")){
+ tlen = 0;
+ failure = !save_ex_replace_body(fetch, &hlen, body, pc);
+ }
+ else
+ failure = !(so_nputs(pkg->so, fetch, (long) hlen)
+ && save_ex_output_body(pkg->stream, raw, section,
+ body, &tlen, pc));
+
+ gf_clear_so_writec(pkg->so);
+
+ if(failure)
+ return(0);
+
+ q_status_message(SM_ORDER, 3, 3,
+ /* TRANSLATORS: A warning to user that the message parts
+ they deleted will not be included in the copy they
+ are now saving to. */
+ _("NOTE: Deleted message parts NOT included in saved copy!"));
+
+ }
+ else{
+ if(!so_nputs(pkg->so, fetch, (long) hlen))
+ return(0);
+
+ fetch = pine_mail_fetch_text(pkg->stream, raw, NULL, &tlen, FT_PEEK);
+
+ if(!(fetch && so_nputs(pkg->so, fetch, tlen)))
+ return(0);
+ }
+
+ so_seek(pkg->so, 0L, 0); /* rewind just in case */
+ mlen = hlen + tlen;
+
+ if(size && mlen < size){
+ char buf[128];
+
+ if(sp_dead_stream(pkg->stream)){
+ snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
+ q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
+ dprint((1, "save_fetch_append_cb: %s\n", buf));
+ return(0);
+ }
+ else{
+ if(pith_opt_save_size_changed_prompt
+ && (*pith_opt_save_size_changed_prompt)(mn_raw2m(pkg->msgmap, raw), 0) == 'y'){
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append_cb: %s", buf));
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: source msg # %ld may be saved incorrectly",
+ mn_raw2m(pkg->msgmap, raw));
+ q_status_message(SM_ORDER, 0, 3, buf);
+ }
+ else{
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: raw msg # %ld went from announced size %ld to actual size %ld",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append_cb: %s", buf));
+ return(0);
+ }
+ }
+ }
+
+ INIT(pkg->msg, mail_string, (void *)so_text(pkg->so), mlen);
+ *message = pkg->msg;
+ /* Next message */
+ pkg->msgno = mn_next_cur(pkg->msgmap);
+ }
+ else /* No more messages */
+ *message = NIL;
+
+ *flags = pkg->flags;
+ *date = (pkg->date && *pkg->date) ? pkg->date : NIL;
+ return LONGT; /* Return success */
+}
+
+
+/*----------------------------------------------------------------------
+ FETCH an rfc822 message header and body and APPEND to destination folder
+
+ Args:
+
+
+ Result:
+
+ ----*/
+int
+save_fetch_append(MAILSTREAM *stream, long int raw, char *sect,
+ MAILSTREAM *save_stream, char *save_folder, CONTEXT_S *context,
+ long unsigned int size, char *flags, char *date, STORE_S *so)
+{
+ int rc, rv, old_imap_server = 0;
+ long j;
+ char *fetch;
+ unsigned long hlen, tlen, mlen;
+ STRING msg;
+
+ if((fetch = mail_fetch_header(stream, raw, sect, NULL, &hlen, FT_PEEK)) != NULL){
+ /*
+ * If there's no date string, then caller found the
+ * MESSAGECACHE for this message element didn't already have it.
+ * So, parse the "internal date" by hand since fetchstructure
+ * hasn't been called yet for this particular message, and
+ * we don't want to call it now just to get the date since
+ * the full header has what we want. Likewise, don't even
+ * think about calling mail_fetchfast either since it also
+ * wants to load mc->rfc822_size (which we could actually
+ * use but...), which under some drivers is *very*
+ * expensive to acquire (can you say NNTP?)...
+ */
+ if(!*date)
+ saved_date(date, fetch);
+ }
+ else
+ return(0); /* fetchtext writes error */
+
+ rc = MSG_EX_DELETE; /* "rc" overloaded */
+ if(msgno_exceptions(stream, raw, NULL, &rc, 0)){
+ char section[64];
+ int failure = 0;
+ BODY *body;
+ gf_io_t pc;
+
+ size = 0; /* all bets off, abort sanity test */
+ gf_set_so_writec(&pc, so);
+
+ if(sect && *sect){
+ snprintf(section, sizeof(section), "%s.1", sect);
+ if(!(body = mail_body(stream, raw, (unsigned char *) section)))
+ return(0);
+ }
+ else{
+ section[0] = '\0';
+ if(!pine_mail_fetch_structure(stream, raw, &body, 0))
+ return(0);
+ }
+
+ /*
+ * Walk the MIME structure looking for exceptional segments,
+ * writing them in the requested fashion.
+ *
+ * First, though, check for the easy case...
+ */
+ if(msgno_part_deleted(stream, raw, sect ? sect : "")){
+ tlen = 0;
+ failure = !save_ex_replace_body(fetch, &hlen, body, pc);
+ }
+ else{
+ /*
+ * Otherwise, roll up your sleeves and get to work...
+ * start by writing msg header and then the processed body
+ */
+ failure = !(so_nputs(so, fetch, (long) hlen)
+ && save_ex_output_body(stream, raw, section,
+ body, &tlen, pc));
+ }
+
+ gf_clear_so_writec(so);
+
+ if(failure)
+ return(0);
+
+ q_status_message(SM_ORDER, 3, 3,
+ _("NOTE: Deleted message parts NOT included in saved copy!"));
+
+ }
+ else{
+ /* First, write the header we just fetched... */
+ if(!so_nputs(so, fetch, (long) hlen))
+ return(0);
+
+ old_imap_server = is_imap_stream(stream) && !modern_imap_stream(stream);
+
+ /* Second, go fetch the corresponding text... */
+ fetch = pine_mail_fetch_text(stream, raw, sect, &tlen,
+ !old_imap_server ? FT_PEEK : 0);
+
+ /*
+ * Special handling in case we're fetching a Message/rfc822
+ * segment and we're talking to an old server...
+ */
+ if(fetch && *fetch == '\0' && sect && (hlen + tlen) != size){
+ so_seek(so, 0L, 0);
+ fetch = pine_mail_fetch_body(stream, raw, sect, &tlen, 0L);
+ }
+
+ /*
+ * Pre IMAP4 servers can't do a non-peeking mail_fetch_text,
+ * so if the message we are saving from was originally unseen,
+ * we have to change it back to unseen. Flags contains the
+ * string "SEEN" if the original message was seen.
+ */
+ if(old_imap_server && (!flags || !srchstr(flags,"SEEN"))){
+ char seq[20];
+
+ strncpy(seq, long2string(raw), sizeof(seq));
+ seq[sizeof(seq)-1] = '\0';
+ mail_flag(stream, seq, "\\SEEN", 0);
+ }
+
+ /* If fetch succeeded, write the result */
+ if(!(fetch && so_nputs(so, fetch, tlen)))
+ return(0);
+ }
+
+ so_seek(so, 0L, 0); /* rewind just in case */
+
+ /*
+ * Set up a c-client string driver so we can hand the
+ * collected text down to mail_append.
+ *
+ * NOTE: We only test the size if and only if we already
+ * have it. See, in some drivers, especially under
+ * dos, its too expensive to get the size (full
+ * header and body text fetch plus MIME parse), so
+ * we only verify the size if we already know it.
+ */
+ mlen = hlen + tlen;
+
+ if(size && mlen < size){
+ char buf[128];
+
+ if(sp_dead_stream(stream)){
+ snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
+ q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
+ dprint((1, "save_fetch_append: %s", buf));
+ return(0);
+ }
+ else{
+ if(pith_opt_save_size_changed_prompt
+ && (*pith_opt_save_size_changed_prompt)(mn_raw2m(sp_msgmap(stream), raw), 0) == 'y'){
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append: %s", buf));
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: source msg # %ld may be saved incorrectly",
+ mn_raw2m(sp_msgmap(stream), raw));
+ q_status_message(SM_ORDER, 0, 3, buf);
+ }
+ else{
+ snprintf(buf, sizeof(buf),
+ "Message to save shrank: source raw msg # %ld went from announced size %ld to actual size %ld",
+ raw, size, mlen);
+ dprint((1, "save_fetch_append: %s", buf));
+ return(0);
+ }
+ }
+ }
+
+ INIT(&msg, mail_string, (void *)so_text(so), mlen);
+
+ rc = 0;
+ while(!(rv = (int) context_append_full(context, save_stream,
+ save_folder, flags,
+ *date ? date : NULL,
+ &msg))){
+ if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
+ break; /* c-client returned error? */
+
+ if(context && (context->use & CNTXT_INCMNG)
+ && context_isambig(save_folder)){
+ q_status_message(SM_ORDER, 3, 5,
+ _("Can only save to existing folders in Incoming Collection"));
+ break;
+ }
+
+ ps_global->try_to_create = 0; /* reset for next time */
+ if((j = create_for_save(context, save_folder)) < 1){
+ if(j < 0)
+ rv = -1; /* user cancelled */
+
+ break;
+ }
+
+ SETPOS((&msg), 0L); /* reset string driver */
+ }
+
+ return(rv);
+}
+
+
+/*
+ * save_ex_replace_body -
+ *
+ * NOTE : hlen points to a cell that has the byte count of "hdr" on entry
+ * *BUT* which is to contain the count of written bytes on exit
+ */
+int
+save_ex_replace_body(char *hdr, long unsigned int *hlen, struct mail_bodystruct *body, gf_io_t pc)
+{
+ unsigned long len;
+
+ /*
+ * "X-" out the given MIME headers unless they're
+ * the same as we're going to substitute...
+ */
+ if(body->type == TYPETEXT
+ && (!body->subtype || !strucmp(body->subtype, "plain"))
+ && body->encoding == ENC7BIT){
+ if(!gf_nputs(hdr, *hlen, pc)) /* write out header */
+ return(0);
+ }
+ else{
+ int bol = 1;
+
+ /*
+ * write header, "X-"ing out transport headers bothersome to
+ * software but potentially useful to the human recipient...
+ */
+ for(len = *hlen; len; len--){
+ if(bol){
+ unsigned long n;
+
+ bol = 0;
+ if(save_ex_mask_types(hdr, &n, pc))
+ *hlen += n; /* add what we inserted */
+ else
+ break;
+ }
+
+ if(*hdr == '\015' && *(hdr+1) == '\012'){
+ bol++; /* remember beginning of line */
+ len--; /* account for LF */
+ if(gf_nputs(hdr, 2, pc))
+ hdr += 2;
+ else
+ break;
+ }
+ else if(!(*pc)(*hdr++))
+ break;
+ }
+
+ if(len) /* bytes remain! */
+ return(0);
+ }
+
+ /* Now, blat out explanatory text as the body... */
+ if(save_ex_explain_body(body, &len, pc)){
+ *hlen += len;
+ return(1);
+ }
+ else
+ return(0);
+}
+
+
+int
+save_ex_output_body(MAILSTREAM *stream, long int raw, char *section,
+ struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
+{
+ char *txtp, newsect[128];
+ unsigned long ilen;
+
+ txtp = mail_fetch_mime(stream, raw, section, len, FT_PEEK);
+
+ if(msgno_part_deleted(stream, raw, section))
+ return(save_ex_replace_body(txtp, len, body, pc));
+
+ if(body->type == TYPEMULTIPART){
+ char *subsect, boundary[128];
+ int n, blen;
+ PART *part = body->nested.part;
+ PARAMETER *param;
+
+ /* Locate supplied multipart boundary */
+ for (param = body->parameter; param; param = param->next)
+ if (!strucmp(param->attribute, "boundary")){
+ snprintf(boundary, sizeof(boundary), "--%.*s\015\012", sizeof(boundary)-10,
+ param->value);
+ blen = strlen(boundary);
+ break;
+ }
+
+ if(!param){
+ q_status_message(SM_ORDER|SM_DING, 3, 3, "Missing MIME boundary");
+ return(0);
+ }
+
+ /*
+ * BUG: if multi/digest and a message deleted (which we'll
+ * change to text/plain), we need to coerce this composite
+ * type to multi/mixed !!
+ */
+ if(!gf_nputs(txtp, *len, pc)) /* write MIME header */
+ return(0);
+
+ /* Prepare to specify sub-sections */
+ strncpy(newsect, section, sizeof(newsect));
+ newsect[sizeof(newsect)-1] = '\0';
+ subsect = &newsect[n = strlen(newsect)];
+ if(n > 2 && !strcmp(&newsect[n-2], ".0"))
+ subsect--;
+ else if(n){
+ if((subsect-newsect) < sizeof(newsect))
+ *subsect++ = '.';
+ }
+
+ n = 1;
+ do { /* for each part */
+ strncpy(subsect, int2string(n++), sizeof(newsect)-(subsect-newsect));
+ newsect[sizeof(newsect)-1] = '\0';
+ if(gf_puts(boundary, pc)
+ && save_ex_output_body(stream, raw, newsect,
+ &part->body, &ilen, pc))
+ *len += blen + ilen;
+ else
+ return(0);
+ }
+ while ((part = part->next) != NULL); /* until done */
+
+ snprintf(boundary, sizeof(boundary), "--%.*s--\015\012", sizeof(boundary)-10,param->value);
+ *len += blen + 2;
+ return(gf_puts(boundary, pc));
+ }
+
+ /* Start by writing the part's MIME header */
+ if(!gf_nputs(txtp, *len, pc))
+ return(0);
+
+ if(body->type == TYPEMESSAGE
+ && (!body->subtype || !strucmp(body->subtype, "rfc822"))){
+ /* write RFC 822 message's header */
+ if((txtp = mail_fetch_header(stream,raw,section,NULL,&ilen,FT_PEEK))
+ && gf_nputs(txtp, ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+
+ /* then go deal with its body parts */
+ snprintf(newsect, sizeof(newsect), "%.10s%s%s", section, section ? "." : "",
+ (body->nested.msg->body->type == TYPEMULTIPART) ? "0" : "1");
+ if(save_ex_output_body(stream, raw, newsect,
+ body->nested.msg->body, &ilen, pc)){
+ *len += ilen;
+ return(1);
+ }
+
+ return(0);
+ }
+
+ /* Write corresponding body part */
+ if((txtp = pine_mail_fetch_body(stream, raw, section, &ilen, FT_PEEK))
+ && gf_nputs(txtp, (long) ilen, pc) && gf_puts("\015\012", pc)){
+ *len += ilen + 2;
+ return(1);
+ }
+
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Mask off any header entries we don't want to show up in exceptional saves
+
+Args: hdr -- pointer to start of a header line
+ pc -- function to write the prefix
+
+ ----*/
+int
+save_ex_mask_types(char *hdr, long unsigned int *len, gf_io_t pc)
+{
+ char *s = NULL;
+
+ if(!struncmp(hdr, "content-type:", 13))
+ s = "Content-Type: Text/Plain; charset=US-ASCII\015\012X-";
+ else if(!struncmp(hdr, "content-description:", 20))
+ s = "Content-Description: Deleted Attachment\015\012X-";
+ else if(!struncmp(hdr, "content-transfer-encoding:", 26)
+ || !struncmp(hdr, "content-disposition:", 20))
+ s = "X-";
+
+ return((*len = s ? strlen(s) : 0) ? gf_puts(s, pc) : 1);
+}
+
+
+int
+save_ex_explain_body(struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
+{
+ unsigned long ilen;
+ char **blurbp;
+ static char *blurb[] = {
+ N_("The following attachment was DELETED when this message was saved:"),
+ NULL
+ };
+
+ *len = 0;
+ for(blurbp = blurb; *blurbp; blurbp++)
+ if(save_ex_output_line(_(*blurbp), &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+
+ if(!save_ex_explain_parts(body, 0, &ilen, pc))
+ return(0);
+
+ *len += ilen;
+ return(1);
+}
+
+
+int
+save_ex_explain_parts(struct mail_bodystruct *body, int depth, long unsigned int *len, gf_io_t pc)
+{
+ char tmp[MAILTMPLEN], buftmp[MAILTMPLEN];
+ unsigned long ilen;
+ char *name = parameter_val(body->parameter, "name");
+
+ if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
+ PART *part = body->nested.part; /* first body part */
+
+ *len = 0;
+ if(body->description && *body->description){
+ snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment described",
+ depth, depth, " ", body_type_names(body->type),
+ sizeof(tmp)-300, body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "");
+ if(!save_ex_output_line(tmp, len, pc))
+ return(0);
+
+ snprintf(buftmp, sizeof(buftmp), "%.75s", body->description);
+ snprintf(tmp, sizeof(tmp), "%*.*s as \"%.50s\" containing:", depth, depth, " ",
+ (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp));
+ }
+ else{
+ snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment containing:",
+ depth, depth, " ",
+ body_type_names(body->type),
+ sizeof(tmp)-300, body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "");
+ }
+
+ if(save_ex_output_line(tmp, &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+
+ depth++;
+ do /* for each part */
+ if(save_ex_explain_parts(&part->body, depth, &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+ while ((part = part->next) != NULL); /* until done */
+ }
+ else{
+ snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment of about %s bytes%s",
+ depth, depth, " ",
+ body_type_names(body->type),
+ sizeof(tmp)-300, body->subtype ? body->subtype : "Unknown",
+ name ? " (Name=\"" : "",
+ name ? name : "",
+ name ? "\")" : "",
+ comatose((body->encoding == ENCBASE64)
+ ? ((body->size.bytes * 3)/4)
+ : body->size.bytes),
+ body->description ? "," : ".");
+ if(!save_ex_output_line(tmp, len, pc))
+ return(0);
+
+ if(body->description && *body->description){
+ snprintf(buftmp, sizeof(buftmp), "%.75s", body->description);
+ snprintf(tmp, sizeof(tmp), "%*.*s described as \"%.*s\"", depth, depth, " ",
+ sizeof(tmp)-100,
+ (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
+ SIZEOF_20KBUF, buftmp));
+ if(save_ex_output_line(tmp, &ilen, pc))
+ *len += ilen;
+ else
+ return(0);
+ }
+ }
+
+ return(1);
+}
+
+
+int
+save_ex_output_line(char *line, long unsigned int *len, gf_io_t pc)
+{
+ snprintf(tmp_20k_buf, SIZEOF_20KBUF, " [ %-*.*s ]\015\012", 68, 68, line);
+ *len = strlen(tmp_20k_buf);
+ return(gf_puts(tmp_20k_buf, pc));
+}
+
+
+/*----------------------------------------------------------------------
+ Save() helper function to create canonical date string from given header
+
+ Args: date -- buf to recieve canonical date string
+ header -- rfc822 header to fish date string from
+
+ Result: date filled with canonicalized date in header, or null string
+ ----*/
+void
+saved_date(char *date, char *header)
+{
+ char *d, *p, c;
+ MESSAGECACHE elt;
+
+ *date = '\0';
+ if((toupper((unsigned char)(*(d = header)))
+ == 'D' && !strncmp(d, "date:", 5))
+ || (d = srchstr(header, "\015\012date:"))){
+ for(d += 7; *d == ' '; d++)
+ ; /* skip white space */
+
+ if((p = strstr(d, "\015\012")) != NULL){
+ for(; p > d && *p == ' '; p--)
+ ; /* skip white space */
+
+ c = *p;
+ *p = '\0'; /* tie off internal date */
+ }
+
+ if(mail_parse_date(&elt, (unsigned char *) d)) /* normalize it */
+ mail_date(date, &elt);
+
+ if(p) /* restore header */
+ *p = c;
+ }
+}
+
+
+MAILSTREAM *
+save_msg_stream(CONTEXT_S *context, char *folder, int *we_opened)
+{
+ char tmp[MAILTMPLEN];
+ MAILSTREAM *save_stream = NULL;
+
+ if(IS_REMOTE(context_apply(tmp, context, folder, sizeof(tmp)))
+ && !(save_stream = sp_stream_get(tmp, SP_MATCH))
+ && !(save_stream = sp_stream_get(tmp, SP_SAME))){
+ if((save_stream = context_open(context, NULL, folder,
+ OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
+ NULL)) != NULL)
+ *we_opened = 1;
+ }
+
+ return(save_stream);
+}
+
+
+/*----------------------------------------------------------------------
+ Offer to create a non-existant folder to copy message[s] into
+
+ Args: context -- context to create folder in
+ folder -- name of folder to create
+
+ Result: 0 if create failed (c-client writes error)
+ 1 if create successful
+ -1 if user declines to create folder
+ ----*/
+int
+create_for_save(CONTEXT_S *context, char *folder)
+{
+ int ret;
+
+ if(pith_opt_save_create_prompt
+ && (ret = (*pith_opt_save_create_prompt)(context, folder, 1)) != 1)
+ return(ret);
+
+ return(context_create(context, NULL, folder));
+}