#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-2021 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); int save_ex_output_text(char *, int, 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: alpine_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 = oauth2_generate_state(); idused = id ? id : ""; snprintf(dummymsg, sizeof(dummymsg), "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\nMessage-ID: %s@example.com\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)); if(F_OFF(F_IGNORE_SIZE, ps_global)) 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){ #define BOUNDARYLEN 128 char *subsect, boundary[BOUNDARYLEN]; 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")){ /* BOUNDARYLEN == sizeof(boundary) */ snprintf(boundary, sizeof(boundary), "--%.*s\015\012", BOUNDARYLEN-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 */ /* BOUNDARYLEN = sizeof(boundary) */ snprintf(boundary, sizeof(boundary), "--%.*s--\015\012", BOUNDARYLEN-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=UTF-8\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); ilen = 0; /* maybe save_ex_explain_parts should set this to zero instead */ 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, namebuf[MAILTMPLEN], descbuf[MAILTMPLEN]; unsigned long ilen, tmplen; char *name = parameter_val(body->parameter, "name"); if(!name){ if(body->disposition.type && !strucmp(body->disposition.type, "ATTACHMENT")) name = parameter_val(body->disposition.parameter, "filename"); } if(name) rfc1522_decode_to_utf8((unsigned char *)namebuf, sizeof(namebuf), name); if(body->description && *body->description) rfc1522_decode_to_utf8((unsigned char *)descbuf, sizeof(descbuf), body->description); ilen = 0; if(body->type == TYPEMULTIPART) { /* multipart gets special handling */ PART *part = body->nested.part; /* first body part */ *len = 0; if(body->description && *body->description){ tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1 + strlen(body->subtype ? body->subtype : "Unknown") + strlen(name ? " (Name=\"" : "") + strlen(name ? namebuf : "") + strlen(name ? "\"" : "") + strlen(_(" segment described as ")) + strlen(descbuf) + strlen(_(" containing:")) + 1; tmp = fs_get((tmplen + 1)*sizeof(char)); sprintf(tmp, "%s%s/%s%s%s%s%s%s%s", _("A "), body_type_names(body->type), body->subtype ? body->subtype : "Unknown", name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "", _(" segment described as "), descbuf, _(" containing:")); if(!save_ex_output_text(tmp, depth, len, pc)) return(0); } else{ tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1 + strlen(body->subtype ? body->subtype : "Unknown") + strlen(name ? " (Name=\"" : "") + strlen(name ? namebuf : "") + strlen(name ? "\"" : "") + strlen(_(" segment containing:")) + 1; tmp = fs_get((tmplen + 1)*sizeof(char)); sprintf(tmp, "%s%s/%s%s%s%s%s", _("A "), body_type_names(body->type), body->subtype ? body->subtype : "Unknown", name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "", _(" segment containing:")); } if(save_ex_output_text(tmp, depth, &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{ char *comatosep = comatose((body->encoding == ENCBASE64) ? ((body->size.bytes * 3)/4) : body->size.bytes); tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1 + strlen(body->subtype && *body->subtype ? body->subtype : "Unknown") + strlen(name ? " (Name=\"" : "") + strlen(name ? namebuf : "") + strlen(name ? "\"" : "") + strlen(_(" segment of about ")) + strlen(comatosep) + strlen(_(" bytes")) + 1 + strlen(body->description && *body->description ? _(" described as \"") : "") + strlen(body->description && *body->description ? descbuf : "") + strlen(body->description && *body->description ? "\"": "") + 1; tmp = fs_get((tmplen + 1)*sizeof(char)); sprintf(tmp, "%s%s/%s%s%s%s%s%s%s%s%s%s%s", _("A "), body_type_names(body->type), body->subtype && *body->subtype ? body->subtype : "Unknown", name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "", _(" segment of about "), comatosep, _(" bytes"), body->description && *body->description ? "," : ".", body->description && *body->description ? " described as \"" : "", body->description && *body->description ? descbuf : "", body->description && *body->description ? "\"" : ""); if(save_ex_output_text(tmp, depth, &ilen, pc)) *len += ilen; else return(0); } return(1); } /* save output one line at the time. This uses save_ex_output_line, which * only allows 68 characters of text per line, so what we do is to get justify * the input text to 68 characters long. If it is not possible to do this, * we chop the line at 68 characters and move the remaining text to the next * line. */ int save_ex_output_text(char *text, int depth, unsigned long *len, gf_io_t pc) { int starti, i, startpos, pos, rv; char tmp[100]; /* a number bigger than 68, we justify text here. */ unsigned long ilen; rv = 0; pos = 0; do { ilen = 0; sprintf(tmp, "%*.*s", depth, depth, " "); startpos = pos; for(starti = i = depth; i < 68 && text[pos] != '\0'; i++, pos++){ tmp[i] = text[pos]; if(tmp[i] == ' '){ /* save when we reach a space */ starti = i; startpos = pos; } } tmp[i] = '\0'; if(i == 68){ if(text[pos] != '\0' && text[pos] != ' '){ /* if we are not at the end of a word */ if(startpos < pos && starti != depth){ /* rewind */ tmp[starti] = '\0'; pos = startpos; } } } rv += save_ex_output_line(tmp, &ilen, pc); *len += ilen; } while (text[pos] != '\0'); return rv; } 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 receive 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-existent 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)); }