* New version 2.21.999
[alpine.git] / pith / save.c
blob28dd3333ae7900d21e38a4438f663f01f666de73
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: save.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2009 University of Washington
8 * Copyright 2013-2018 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/save.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/mimedesc.h"
24 #include "../pith/filter.h"
25 #include "../pith/context.h"
26 #include "../pith/folder.h"
27 #include "../pith/copyaddr.h"
28 #include "../pith/mailview.h"
29 #include "../pith/mailcmd.h"
30 #include "../pith/bldaddr.h"
31 #include "../pith/flag.h"
32 #include "../pith/status.h"
33 #include "../pith/ablookup.h"
34 #include "../pith/news.h"
35 #include "../pith/util.h"
36 #include "../pith/reply.h"
37 #include "../pith/sequence.h"
38 #include "../pith/stream.h"
39 #include "../pith/options.h"
43 * Internal prototypes
45 int save_ex_replace_body(char *, unsigned long *,BODY *,gf_io_t);
46 int save_ex_output_body(MAILSTREAM *, long, char *, BODY *, unsigned long *, gf_io_t);
47 int save_ex_mask_types(char *, unsigned long *, gf_io_t);
48 int save_ex_explain_body(BODY *, unsigned long *, gf_io_t);
49 int save_ex_explain_parts(BODY *, int, unsigned long *, gf_io_t);
50 int save_ex_output_line(char *, unsigned long *, gf_io_t);
54 * pith hook
56 int (*pith_opt_save_create_prompt)(CONTEXT_S *, char *, int);
57 int (*pith_opt_save_size_changed_prompt)(long, int);
61 /*----------------------------------------------------------------------
62 save_get_default - return default folder name for saving
63 ----*/
64 char *
65 save_get_default(struct pine *state, ENVELOPE *e, long int rawno,
66 char *section, CONTEXT_S **cntxt)
68 int context_was_set;
70 if(!cntxt)
71 return("");
73 context_was_set = ((*cntxt) != NULL);
75 /* start with the default save context */
76 if(!(*cntxt)
77 && ((*cntxt) = default_save_context(state->context_list)) == NULL)
78 (*cntxt) = state->context_list;
80 if(!e || ps_global->save_msg_rule == SAV_RULE_LAST
81 || ps_global->save_msg_rule == SAV_RULE_DEFLT){
82 if(ps_global->save_msg_rule == SAV_RULE_LAST && ps_global->last_save_context){
83 if(!context_was_set)
84 (*cntxt) = ps_global->last_save_context;
86 else{
87 strncpy(ps_global->last_save_folder,
88 ps_global->VAR_DEFAULT_SAVE_FOLDER,
89 sizeof(ps_global->last_save_folder)-1);
90 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
93 * If the user entered "inbox" as their default save folder it is very
94 * likely they meant the real inbox, not the inbox in the primary collection.
96 if(!context_was_set
97 && !strucmp(ps_global->inbox_name, ps_global->last_save_folder))
98 (*cntxt) = state->context_list;
101 else{
102 save_get_fldr_from_env(ps_global->last_save_folder,
103 sizeof(ps_global->last_save_folder),
104 e, state, rawno, section);
105 /* somebody expunged current message */
106 if(sp_expunge_count(ps_global->mail_stream))
107 return(NULL);
110 return(ps_global->last_save_folder);
115 /*----------------------------------------------------------------------
116 Grope through envelope to find default folder name to save to
118 Args: fbuf -- Buffer to return result in
119 nfbuf -- Size of fbuf
120 e -- The envelope to look in
121 state -- Usual pine state
122 rawmsgno -- Raw c-client sequence number of message
123 section -- Mime section of header data (for message/rfc822)
125 Result: The appropriate default folder name is copied into fbuf.
126 ----*/
127 void
128 save_get_fldr_from_env(char *fbuf, int nfbuf, ENVELOPE *e, struct pine *state,
129 long int rawmsgno, char *section)
131 char fakedomain[2];
132 ADDRESS *tmp_adr = NULL;
133 char buf[MAX(MAXFOLDER,MAX_NICKNAME) + 1];
134 char *bufp;
135 char *folder_name = NULL;
136 static char botch[] = "programmer botch, unknown message save rule";
137 unsigned save_msg_rule;
139 if(!e)
140 return;
142 /* copy this because we might change it below */
143 save_msg_rule = state->save_msg_rule;
145 /* first get the relevant address to base the folder name on */
146 switch(save_msg_rule){
147 case SAV_RULE_FROM:
148 case SAV_RULE_NICK_FROM:
149 case SAV_RULE_NICK_FROM_DEF:
150 case SAV_RULE_FCC_FROM:
151 case SAV_RULE_FCC_FROM_DEF:
152 case SAV_RULE_RN_FROM:
153 case SAV_RULE_RN_FROM_DEF:
154 tmp_adr = e->from ? copyaddr(e->from)
155 : e->sender ? copyaddr(e->sender) : NULL;
156 break;
158 case SAV_RULE_SENDER:
159 case SAV_RULE_NICK_SENDER:
160 case SAV_RULE_NICK_SENDER_DEF:
161 case SAV_RULE_FCC_SENDER:
162 case SAV_RULE_FCC_SENDER_DEF:
163 case SAV_RULE_RN_SENDER:
164 case SAV_RULE_RN_SENDER_DEF:
165 tmp_adr = e->sender ? copyaddr(e->sender)
166 : e->from ? copyaddr(e->from) : NULL;
167 break;
169 case SAV_RULE_REPLYTO:
170 case SAV_RULE_NICK_REPLYTO:
171 case SAV_RULE_NICK_REPLYTO_DEF:
172 case SAV_RULE_FCC_REPLYTO:
173 case SAV_RULE_FCC_REPLYTO_DEF:
174 case SAV_RULE_RN_REPLYTO:
175 case SAV_RULE_RN_REPLYTO_DEF:
176 tmp_adr = e->reply_to ? copyaddr(e->reply_to)
177 : e->from ? copyaddr(e->from)
178 : e->sender ? copyaddr(e->sender) : NULL;
179 break;
181 case SAV_RULE_RECIP:
182 case SAV_RULE_NICK_RECIP:
183 case SAV_RULE_NICK_RECIP_DEF:
184 case SAV_RULE_FCC_RECIP:
185 case SAV_RULE_FCC_RECIP_DEF:
186 case SAV_RULE_RN_RECIP:
187 case SAV_RULE_RN_RECIP_DEF:
188 /* news */
189 if(state->mail_stream && IS_NEWS(state->mail_stream)){
190 char *tmp_a_string, *ng_name;
192 fakedomain[0] = '@';
193 fakedomain[1] = '\0';
195 /* find the news group name */
196 if((ng_name = strstr(state->mail_stream->mailbox,"#news")) != NULL)
197 ng_name += 6;
198 else
199 ng_name = state->mail_stream->mailbox; /* shouldn't happen */
201 /* copy this string so rfc822_parse_adrlist can't blast it */
202 tmp_a_string = cpystr(ng_name);
203 /* make an adr */
204 rfc822_parse_adrlist(&tmp_adr, tmp_a_string, fakedomain);
205 fs_give((void **)&tmp_a_string);
206 if(tmp_adr && tmp_adr->host && tmp_adr->host[0] == '@')
207 tmp_adr->host[0] = '\0';
209 /* not news */
210 else{
211 static char *fields[] = {"Resent-To", NULL};
212 char *extras, *values[sizeof(fields)/sizeof(fields[0])];
214 extras = pine_fetchheader_lines(state->mail_stream, rawmsgno,
215 section, fields);
216 if(extras){
217 long i;
219 memset(values, 0, sizeof(fields));
220 simple_header_parse(extras, fields, values);
221 fs_give((void **)&extras);
223 for(i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
224 if(values[i]){
225 if(tmp_adr) /* take last matching value */
226 mail_free_address(&tmp_adr);
228 /* build a temporary address list */
229 fakedomain[0] = '@';
230 fakedomain[1] = '\0';
231 rfc822_parse_adrlist(&tmp_adr, values[i], fakedomain);
232 fs_give((void **)&values[i]);
236 if(!tmp_adr)
237 tmp_adr = e->to ? copyaddr(e->to) : NULL;
240 break;
242 default:
243 alpine_panic(botch);
244 break;
247 /* For that address, lookup the fcc or nickname from address book */
248 switch(save_msg_rule){
249 case SAV_RULE_NICK_FROM:
250 case SAV_RULE_NICK_SENDER:
251 case SAV_RULE_NICK_REPLYTO:
252 case SAV_RULE_NICK_RECIP:
253 case SAV_RULE_FCC_FROM:
254 case SAV_RULE_FCC_SENDER:
255 case SAV_RULE_FCC_REPLYTO:
256 case SAV_RULE_FCC_RECIP:
257 case SAV_RULE_NICK_FROM_DEF:
258 case SAV_RULE_NICK_SENDER_DEF:
259 case SAV_RULE_NICK_REPLYTO_DEF:
260 case SAV_RULE_NICK_RECIP_DEF:
261 case SAV_RULE_FCC_FROM_DEF:
262 case SAV_RULE_FCC_SENDER_DEF:
263 case SAV_RULE_FCC_REPLYTO_DEF:
264 case SAV_RULE_FCC_RECIP_DEF:
265 switch(save_msg_rule){
266 case SAV_RULE_NICK_FROM:
267 case SAV_RULE_NICK_SENDER:
268 case SAV_RULE_NICK_REPLYTO:
269 case SAV_RULE_NICK_RECIP:
270 case SAV_RULE_NICK_FROM_DEF:
271 case SAV_RULE_NICK_SENDER_DEF:
272 case SAV_RULE_NICK_REPLYTO_DEF:
273 case SAV_RULE_NICK_RECIP_DEF:
274 bufp = get_nickname_from_addr(tmp_adr, buf, sizeof(buf));
275 break;
277 case SAV_RULE_FCC_FROM:
278 case SAV_RULE_FCC_SENDER:
279 case SAV_RULE_FCC_REPLYTO:
280 case SAV_RULE_FCC_RECIP:
281 case SAV_RULE_FCC_FROM_DEF:
282 case SAV_RULE_FCC_SENDER_DEF:
283 case SAV_RULE_FCC_REPLYTO_DEF:
284 case SAV_RULE_FCC_RECIP_DEF:
285 bufp = get_fcc_from_addr(tmp_adr, buf, sizeof(buf));
286 break;
289 if(bufp && *bufp){
290 istrncpy(fbuf, bufp, nfbuf - 1);
291 fbuf[nfbuf - 1] = '\0';
293 else
294 /* fall back to non-nick/non-fcc version of rule */
295 switch(save_msg_rule){
296 case SAV_RULE_NICK_FROM:
297 case SAV_RULE_FCC_FROM:
298 save_msg_rule = SAV_RULE_FROM;
299 break;
301 case SAV_RULE_NICK_SENDER:
302 case SAV_RULE_FCC_SENDER:
303 save_msg_rule = SAV_RULE_SENDER;
304 break;
306 case SAV_RULE_NICK_REPLYTO:
307 case SAV_RULE_FCC_REPLYTO:
308 save_msg_rule = SAV_RULE_REPLYTO;
309 break;
311 case SAV_RULE_NICK_RECIP:
312 case SAV_RULE_FCC_RECIP:
313 save_msg_rule = SAV_RULE_RECIP;
314 break;
316 default:
317 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
318 fbuf[nfbuf - 1] = '\0';
319 break;
322 break;
325 /* Realname */
326 switch(save_msg_rule){
327 case SAV_RULE_RN_FROM_DEF:
328 case SAV_RULE_RN_FROM:
329 case SAV_RULE_RN_SENDER_DEF:
330 case SAV_RULE_RN_SENDER:
331 case SAV_RULE_RN_RECIP_DEF:
332 case SAV_RULE_RN_RECIP:
333 case SAV_RULE_RN_REPLYTO_DEF:
334 case SAV_RULE_RN_REPLYTO:
335 /* Fish out the realname */
336 if(tmp_adr && tmp_adr->personal && tmp_adr->personal[0])
337 folder_name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
338 SIZEOF_20KBUF, tmp_adr->personal);
340 if(folder_name && folder_name[0]){
341 istrncpy(fbuf, folder_name, nfbuf - 1);
342 fbuf[nfbuf - 1] = '\0';
344 else{ /* fall back to other behaviors */
345 switch(save_msg_rule){
346 case SAV_RULE_RN_FROM:
347 save_msg_rule = SAV_RULE_FROM;
348 break;
350 case SAV_RULE_RN_SENDER:
351 save_msg_rule = SAV_RULE_SENDER;
352 break;
354 case SAV_RULE_RN_RECIP:
355 save_msg_rule = SAV_RULE_RECIP;
356 break;
358 case SAV_RULE_RN_REPLYTO:
359 save_msg_rule = SAV_RULE_REPLYTO;
360 break;
362 default:
363 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
364 fbuf[nfbuf - 1] = '\0';
365 break;
369 break;
372 /* get the username out of the mailbox for this address */
373 switch(save_msg_rule){
374 case SAV_RULE_FROM:
375 case SAV_RULE_SENDER:
376 case SAV_RULE_REPLYTO:
377 case SAV_RULE_RECIP:
379 * Fish out the user's name from the mailbox portion of
380 * the address and put it in folder.
382 folder_name = (tmp_adr && tmp_adr->mailbox && tmp_adr->mailbox[0])
383 ? tmp_adr->mailbox : NULL;
384 if(!get_uname(folder_name, fbuf, nfbuf)){
385 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
386 fbuf[nfbuf - 1] = '\0';
389 break;
392 if(tmp_adr)
393 mail_free_address(&tmp_adr);
397 /*----------------------------------------------------------------------
398 Do the work of actually saving messages to a folder
400 Args: state -- pine state struct (for stream pointers)
401 stream -- source stream, which msgmap refers to
402 context -- context to interpret name in if not fully qualified
403 folder -- The folder to save the message in
404 msgmap -- message map of currently selected messages
405 flgs -- Possible bits are
406 SV_DELETE - delete after saving
407 SV_FOR_FILT - called from filtering function, not save
408 SV_FIX_DELS - remove Del mark before saving
409 SV_INBOXWOCNTXT - "inbox" is interpreted without context making
410 it the one-true inbox instead
412 Result: Returns number of messages saved
414 Note: There's a bit going on here; temporary clearing of deleted flags
415 since they are *not* preserved, picking or creating the stream for
416 copy or append, and dealing with errors...
417 We try to preserve user keywords by setting them in the destination.
418 ----*/
419 long
420 save(struct pine *state, MAILSTREAM *stream, CONTEXT_S *context, char *folder,
421 MSGNO_S *msgmap, int flgs)
423 int rv, rc, j, our_stream = 0, cancelled = 0;
424 int delete, filter, k, worry_about_keywords = 0;
425 char *save_folder, *seq, *flags = NULL, date[64], tmp[MAILTMPLEN];
426 long i, nmsgs, rawno;
427 size_t len;
428 STORE_S *so = NULL;
429 MAILSTREAM *save_stream = NULL;
430 MESSAGECACHE *mc;
432 delete = flgs & SV_DELETE;
433 filter = flgs & SV_FOR_FILT;
435 if(strucmp(folder, state->inbox_name) == 0 && flgs & SV_INBOXWOCNTXT){
436 save_folder = state->VAR_INBOX_PATH;
437 context = NULL;
439 else
440 save_folder = folder;
443 * Because the COPY/APPEND command doesn't always create keywords when they
444 * aren't already defined in a mailbox, we need to ensure that the keywords
445 * exist in the destination (are defined and settable) before we do the copies.
446 * Here's what the code is doing
448 * If we have keywords set in the source messages
449 * Add a dummy message to destination mailbox
451 * for each keyword that is set in the set of messages we're saving
452 * set the keyword in that message (thus creating it)
454 * remember deleted messages
455 * undelete them
456 * delete dummy message
457 * expunge
458 * delete remembered messages
460 * After that the assumption is that the keywords will be saved by a
461 * COPY command. We need to set the flags string ourself for appends.
464 /* are any keywords set in the source messages? */
465 for(i = mn_first_cur(msgmap); !worry_about_keywords && i > 0L; i = mn_next_cur(msgmap)){
466 rawno = mn_m2raw(msgmap, i);
467 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
468 ? mail_elt(stream, rawno) : NULL;
469 if(mc && mc->user_flags)
470 worry_about_keywords++;
473 if(worry_about_keywords){
474 MAILSTREAM *dstn_stream = NULL;
475 int already_open = 0;
476 int we_blocked_reuse = 0;
479 * Possible problem created by our stream re-use
480 * strategy. If we are going to open a new stream
481 * here, we want to be sure not to re-use the
482 * stream we are saving _from_, so take it out of the
483 * re-use pool before we call open.
485 if(sp_flagged(stream, SP_USEPOOL)){
486 we_blocked_reuse++;
487 sp_unflag(stream, SP_USEPOOL);
490 /* see if there is a stream open already */
491 if(!dstn_stream){
492 dstn_stream = context_already_open_stream(context,
493 save_folder,
494 AOS_RW_ONLY);
495 already_open = dstn_stream ? 1 : 0;
498 if(!dstn_stream)
499 dstn_stream = context_open(context, NULL,
500 save_folder,
501 SP_USEPOOL | SP_TEMPUSE,
502 NULL);
504 if(dstn_stream && dstn_stream->kwd_create){
505 imapuid_t dummy_uid = 0L;
506 long dummy_msgno, delete_count;
507 int expbits, set;
508 char *flags = NULL;
509 char *user_flag_name, *duser_flag_name;
511 /* find keywords that need to be defined */
512 for(k = 0; stream_to_user_flag_name(stream, k); k++){
513 user_flag_name = stream_to_user_flag_name(stream, k);
514 if(user_flag_name && user_flag_name[0]){
515 /* is this flag set in any of the save set? */
516 for(set = 0, i = mn_first_cur(msgmap);
517 !set && i > 0L;
518 i = mn_next_cur(msgmap)){
519 rawno = mn_m2raw(msgmap, i);
520 if(user_flag_is_set(stream, rawno, user_flag_name))
521 set++;
524 if(set){
526 * The flag may already be defined in this
527 * mailbox. Check for that first.
529 for(j = 0; stream_to_user_flag_name(dstn_stream, j); j++){
530 duser_flag_name = stream_to_user_flag_name(dstn_stream, j);
531 if(duser_flag_name && duser_flag_name[0]
532 && !strucmp(duser_flag_name, user_flag_name)){
533 set = 0;
534 break;
539 if(set){
540 if(flags == NULL){
541 len = strlen(user_flag_name) + 1;
542 flags = (char *) fs_get((len+1) * sizeof(char));
543 snprintf(flags, len+1, "%s ", user_flag_name);
545 else{
546 char *p;
547 size_t newlen;
549 newlen = strlen(user_flag_name) + 1;
550 len += newlen;
551 fs_resize((void **) &flags, (len+1) * sizeof(char));
552 p = flags + strlen(flags);
553 snprintf(p, newlen+1, "%s ", user_flag_name);
559 if(flags){
560 char *p;
561 size_t newlen;
562 STRING msg;
563 char dummymsg[1000];
564 char *id = NULL;
565 char *idused;
566 appenduid_t *au;
568 newlen = strlen("\\DELETED");
569 len += newlen;
570 fs_resize((void **) &flags, (len+1) * sizeof(char));
571 p = flags + strlen(flags);
572 snprintf(p, newlen+1, "%s", "\\DELETED");
574 id = generate_message_id();
575 idused = id ? id : "<xyz>";
576 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);
579 * We need to get the uid of the message we are about to
580 * append so that we can delete it when we're done and
581 * so we don't affect other messages.
584 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream)){
585 au = mail_parameters(NIL, GET_APPENDUID, NIL);
586 mail_parameters(NIL, SET_APPENDUID, (void *) appenduid_cb);
589 INIT(&msg, mail_string, (void *) dummymsg, strlen(dummymsg));
590 if(pine_mail_append(dstn_stream, dstn_stream->mailbox, &msg)){
592 (void) pine_mail_ping(dstn_stream);
594 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
595 dummy_uid = get_last_append_uid();
597 if(dummy_uid == 0L){
598 dummy_msgno = get_msgno_by_msg_id(dstn_stream, idused,
599 sp_msgmap(dstn_stream));
600 if(dummy_msgno <= 0L || dummy_msgno > dstn_stream->nmsgs)
601 dummy_msgno = dstn_stream->nmsgs;
603 rawno = mn_m2raw(sp_msgmap(dstn_stream), dummy_msgno);
604 if(rawno > 0L && rawno <= dstn_stream->nmsgs)
605 dummy_uid = mail_uid(dstn_stream, rawno);
607 if(dummy_uid == 0L)
608 dummy_msgno = dstn_stream->nmsgs;
612 * We need to remember which messages are deleted,
613 * undelete them, do the expunge, then delete them again.
615 delete_count = count_flagged(dstn_stream, F_DEL);
616 if(delete_count){
617 for(i = 1L; i <= dstn_stream->nmsgs; i++)
618 if(((mc = mail_elt(dstn_stream, i)) && mc->valid && mc->deleted)
619 || (mc && !mc->valid && mc->searched)){
620 mc->sequence = 1;
621 expbits = MSG_EX_DELETE;
622 msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
624 else if((mc = mail_elt(dstn_stream, i)) != NULL)
625 mc->sequence = 0;
627 if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
628 mail_flag(dstn_stream, seq, "\\DELETED", ST_SILENT);
629 fs_give((void **) &seq);
633 if(dummy_uid > 0L)
634 mail_flag(dstn_stream, ulong2string(dummy_uid),
635 flags, ST_SET | ST_UID | ST_SILENT);
636 else
637 mail_flag(dstn_stream, ulong2string(dummy_msgno),
638 flags, ST_SET | ST_SILENT);
640 ps_global->mm_log_error = 0;
641 ps_global->expunge_in_progress = 1;
642 mail_expunge(dstn_stream);
643 ps_global->expunge_in_progress = 0;
645 if(delete_count){
646 for(i = 1L; i <= dstn_stream->nmsgs; i++)
647 if((mc = mail_elt(dstn_stream, i)) != NULL){
648 mc->sequence
649 = (msgno_exceptions(dstn_stream, i, "0", &expbits, FALSE)
650 && (expbits & MSG_EX_DELETE));
653 * Remove the EX_DELETE bit in case we're still using
654 * this stream.
656 if(mc->sequence){
657 expbits &= ~MSG_EX_DELETE;
658 msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
662 if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
663 mail_flag(dstn_stream, seq, "\\DELETED", ST_SET | ST_SILENT);
664 fs_give((void **) &seq);
669 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
670 mail_parameters(NIL, SET_APPENDUID, (void *) au);
672 if(id)
673 fs_give((void **) &id);
675 fs_give((void **) &flags);
679 if(dstn_stream && !already_open)
680 pine_mail_close(dstn_stream);
682 if(we_blocked_reuse)
683 sp_set_flags(stream, sp_flags(stream) | SP_USEPOOL);
687 * If any of the messages have exceptional attachment handling
688 * we have to fall thru below to do the APPEND by hand...
690 if(!msgno_any_deletedparts(stream, msgmap)){
691 int loc_to_loc = 0;
694 * Compare the current stream (the save's source) and the stream
695 * the destination folder will need...
697 context_apply(tmp, context, save_folder, sizeof(tmp));
700 * If we're going to be doing a cross-format copy we're better off
701 * using the else code below that knows how to do multi-append.
702 * The part in the if is leaving save_stream set to NULL in the
703 * case that the stream is local and the folder is local and they
704 * are different formats (like unix and tenex). That will cause us
705 * to fall thru to the APPEND case which is faster than using
706 * copy which will use our imap_proxycopy which doesn't know
707 * about multiappend.
709 loc_to_loc = stream && stream->dtb
710 && stream->dtb->flags & DR_LOCAL && !IS_REMOTE(tmp);
711 if(!loc_to_loc || (stream->dtb->valid && (*stream->dtb->valid)(tmp)))
712 save_stream = loc_to_loc ? stream
713 : context_same_stream(context, save_folder, stream);
716 /* if needed, this'll get set in mm_notify */
717 ps_global->try_to_create = 0;
718 rv = rc = 0;
719 nmsgs = 0L;
722 * At this point, if we have a save_stream, then none of the messages
723 * being saved involve special handling that would require our use
724 * of mail_append, so go with mail_copy since in the IMAP case it
725 * means no data on the wire...
727 if(save_stream){
728 char *dseq = NULL, *oseq;
730 if((flgs & SV_FIX_DELS) &&
731 (dseq = currentf_sequence(stream, msgmap, F_DEL, NULL,
732 0, NULL, NULL)))
733 mail_flag(stream, dseq, "\\DELETED", 0L);
735 seq = currentf_sequence(stream, msgmap, 0L, &nmsgs, 0, NULL, NULL);
736 if(!(flgs & SV_PRESERVE)
737 && (F_ON(F_AGG_SEQ_COPY, ps_global)
738 || (mn_get_sort(msgmap) == SortArrival && !mn_get_revsort(msgmap)))){
741 * currentf_sequence() above lit all the elt "sequence"
742 * bits of the interesting messages. Now, build a sequence
743 * that preserves sort order...
745 oseq = build_sequence(stream, msgmap, &nmsgs);
747 else{
748 oseq = NULL; /* no single sequence! */
749 nmsgs = 0L;
750 i = mn_first_cur(msgmap); /* set first to copy */
754 while(!(rv = (int) context_copy(context, save_stream,
755 oseq ? oseq : long2string(mn_m2raw(msgmap, i)),
756 save_folder))){
757 if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
758 break; /* c-client returned error? */
760 if((context && context->use & CNTXT_INCMNG)
761 && context_isambig(save_folder)){
762 q_status_message(SM_ORDER, 3, 5,
763 _("Can only save to existing folders in Incoming Collection"));
764 break;
767 ps_global->try_to_create = 0; /* reset for next time */
768 if((j = create_for_save(context, save_folder)) < 1){
769 if(j < 0)
770 cancelled = 1; /* user cancels */
772 break;
776 if(rv){ /* failure or finished? */
777 if(oseq) /* all done? */
778 break;
779 else
780 nmsgs++;
782 else{ /* failure! */
783 if(oseq)
784 nmsgs = 0L; /* nothing copy'd */
786 break;
789 while((i = mn_next_cur(msgmap)) > 0L);
791 if(rv && delete) /* delete those saved */
792 mail_flag(stream, seq, "\\DELETED", ST_SET);
793 else if(dseq) /* or restore previous state */
794 mail_flag(stream, dseq, "\\DELETED", ST_SET);
796 if(dseq) /* clean up */
797 fs_give((void **)&dseq);
799 if(oseq)
800 fs_give((void **)&oseq);
802 fs_give((void **)&seq);
804 else{
806 * Special handling requires mail_append. See if we can
807 * re-use stream source messages are on...
809 save_stream = context_same_stream(context, save_folder, stream);
812 * IF the destination's REMOTE, open a stream here so c-client
813 * doesn't have to open it for each aggregate save...
815 if(!save_stream){
816 if(context_apply(tmp, context, save_folder, sizeof(tmp))[0] == '{'
817 && (save_stream = context_open(context, NULL,
818 save_folder,
819 OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
820 NULL)))
821 our_stream = 1;
825 * Allocate a storage object to temporarily store the message
826 * object in. Below it'll get mapped into a c-client STRING struct
827 * in preparation for handing off to context_append...
829 if(!(so = so_get(CharStar, NULL, WRITE_ACCESS))){
830 dprint((1, "Can't allocate store for save: %s\n",
831 error_description(errno)));
832 q_status_message(SM_ORDER | SM_DING, 3, 4,
833 "Problem creating space for message text.");
837 * get a sequence of invalid elt's so we can get their flags...
839 if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
840 mail_fetch_fast(stream, seq, 0L);
841 fs_give((void **) &seq);
845 * If we're supposed set the deleted flag, clear the elt bit
846 * we'll use to build the sequence later...
848 if(delete)
849 for(i = 1L; i <= stream->nmsgs; i++)
850 if((mc = mail_elt(stream, i)) != NULL)
851 mc->sequence = 0;
853 nmsgs = 0L;
855 if(pith_opt_save_size_changed_prompt)
856 (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
859 * if there is more than one message, do multiappend.
860 * otherwise, we can use our already open stream.
862 if(!save_stream || !is_imap_stream(save_stream) ||
863 (LEVELMULTIAPPEND(save_stream) && mn_total_cur(msgmap) > 1)){
864 APPENDPACKAGE pkg;
865 STRING msg;
867 pkg.stream = stream;
868 /* tell save_fetch_append_cb whether or not to leave deleted flag */
869 pkg.flags = flgs & SV_FIX_DELS ? NULL : cpystr("\\DELETED");
870 pkg.date = date;
871 pkg.msg = &msg;
872 pkg.msgmap = msgmap;
874 if ((pkg.so = so) && ((pkg.msgno = mn_first_cur(msgmap)) > 0L)) {
875 so_truncate(so, 0L);
878 * we've gotta make sure this is a stream that we've
879 * opened ourselves.
881 rc = 0;
882 while(!(rv = context_append_multiple(context,
883 our_stream ? save_stream
884 : NULL, save_folder,
885 save_fetch_append_cb,
886 &pkg,
887 stream))) {
889 if(rc++ || !ps_global->try_to_create)
890 break;
891 if((context && context->use & CNTXT_INCMNG)
892 && context_isambig(save_folder)){
893 q_status_message(SM_ORDER, 3, 5,
894 _("Can only save to existing folders in Incoming Collection"));
895 break;
898 ps_global->try_to_create = 0;
899 if((j = create_for_save(context, save_folder)) < 1){
900 if(j < 0)
901 cancelled = 1;
902 break;
906 if(pkg.flags)
907 fs_give((void **) &pkg.flags);
909 ps_global->noshow_error = 0;
911 if(rv){
913 * Success! Count it, and if it's not already deleted and
914 * it's supposed to be, mark it to get deleted later...
916 for(i = mn_first_cur(msgmap); so && i > 0L;
917 i = mn_next_cur(msgmap)){
918 nmsgs++;
919 if(delete){
920 rawno = mn_m2raw(msgmap, i);
921 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
922 ? mail_elt(stream, rawno) : NULL;
923 if(mc && !mc->deleted)
924 mc->sequence = 1; /* mark for later deletion */
929 else
930 cancelled = 1; /* No messages to append! */
932 if(sp_expunge_count(stream))
933 cancelled = 1; /* All bets are off! */
935 else
936 for(i = mn_first_cur(msgmap); so && i > 0L; i = mn_next_cur(msgmap)){
937 int preserve_these_flags;
939 so_truncate(so, 0L);
941 rawno = mn_m2raw(msgmap, i);
942 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
943 ? mail_elt(stream, rawno) : NULL;
945 /* always preserve these flags */
946 preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
947 /* maybe preserve deleted flag */
948 preserve_these_flags |= flgs & SV_FIX_DELS ? 0 : F_DEL;
949 flags = flag_string(stream, rawno, preserve_these_flags);
951 if(mc && mc->day)
952 mail_date(date, mc);
953 else
954 *date = '\0';
956 rv = save_fetch_append(stream, mn_m2raw(msgmap, i),
957 NULL, save_stream, save_folder, context,
958 mc ? mc->rfc822_size : 0L, flags, date, so);
960 if(flags)
961 fs_give((void **) &flags);
963 if(sp_expunge_count(stream))
964 rv = -1; /* All bets are off! */
966 if(rv == 1){
968 * Success! Count it, and if it's not already deleted and
969 * it's supposed to be, mark it to get deleted later...
971 nmsgs++;
972 if(delete){
973 rawno = mn_m2raw(msgmap, i);
974 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
975 ? mail_elt(stream, rawno) : NULL;
976 if(mc && !mc->deleted)
977 mc->sequence = 1; /* mark for later deletion */
980 else{
981 if(rv == -1)
982 cancelled = 1; /* else horrendous failure */
984 break;
988 if(pith_opt_save_size_changed_prompt)
989 (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
991 if(our_stream)
992 pine_mail_close(save_stream);
994 if(so)
995 so_give(&so);
997 if(delete && (seq = build_sequence(stream, NULL, NULL))){
998 mail_flag(stream, seq, "\\DELETED", ST_SET);
999 fs_give((void **)&seq);
1003 ps_global->try_to_create = 0; /* reset for next time */
1004 if(!cancelled && nmsgs < mn_total_cur(msgmap)){
1005 dprint((1, "FAILED save of msg %s (c-client sequence #)\n",
1006 long2string(mn_m2raw(msgmap, mn_get_cur(msgmap)))));
1007 if((mn_total_cur(msgmap) > 1L) && nmsgs != 0){
1008 /* this shouldn't happen cause it should be all or nothing */
1009 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1010 "%ld of %ld messages saved before error occurred",
1011 nmsgs, mn_total_cur(msgmap));
1012 dprint((1, "\t%s\n", tmp_20k_buf));
1013 q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
1015 else if(mn_total_cur(msgmap) == 1){
1016 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1017 "%s to folder \"%s\" FAILED",
1018 filter ? "Filter" : "Save",
1019 strsquish(tmp_20k_buf+500, 500, folder, 35));
1020 dprint((1, "\t%s\n", tmp_20k_buf));
1021 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
1023 else{
1024 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1025 "%s of %s messages to folder \"%s\" FAILED",
1026 filter ? "Filter" : "Save", comatose(mn_total_cur(msgmap)),
1027 strsquish(tmp_20k_buf+500, 500, folder, 35));
1028 dprint((1, "\t%s\n", tmp_20k_buf));
1029 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
1033 return(nmsgs);
1037 /* Append message callback
1038 * Accepts: MAIL stream
1039 * append data package
1040 * pointer to return initial flags
1041 * pointer to return message internal date
1042 * pointer to return stringstruct of message or NIL to stop
1043 * Returns: T if success (have message or stop), NIL if error
1046 long save_fetch_append_cb(MAILSTREAM *stream, void *data, char **flags,
1047 char **date, STRING **message)
1049 unsigned long size = 0;
1050 APPENDPACKAGE *pkg = (APPENDPACKAGE *) data;
1051 MESSAGECACHE *mc;
1052 char *fetch;
1053 int rc;
1054 unsigned long raw, hlen, tlen, mlen;
1056 if(pkg->so && (pkg->msgno > 0L)) {
1057 raw = mn_m2raw(pkg->msgmap, pkg->msgno);
1058 mc = (raw > 0L && pkg->stream && raw <= pkg->stream->nmsgs)
1059 ? mail_elt(pkg->stream, raw) : NULL;
1060 if(mc){
1061 int preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
1063 size = mc->rfc822_size;
1066 * If the caller wants us to preserve the state of the
1067 * \DELETED flag then pkg->flags will be \DELETED, otherwise
1068 * let it be undeleted. Fix from Eduardo Chappa.
1070 if(pkg->flags){
1071 if(strstr(pkg->flags,"\\DELETED"))
1072 preserve_these_flags |= F_DEL;
1074 /* not used anymore */
1075 fs_give((void **) &pkg->flags);
1078 pkg->flags = flag_string(pkg->stream, raw, preserve_these_flags);
1081 if(mc && mc->day)
1082 mail_date(pkg->date, mc);
1083 else
1084 *pkg->date = '\0';
1085 if((fetch = mail_fetch_header(pkg->stream, raw, NULL, NULL, &hlen,
1086 FT_PEEK)) != NULL){
1087 if(!*pkg->date)
1088 saved_date(pkg->date, fetch);
1090 else
1091 return(0); /* fetchtext writes error */
1093 rc = MSG_EX_DELETE; /* "rc" overloaded */
1094 if(msgno_exceptions(pkg->stream, raw, NULL, &rc, 0)){
1095 char section[64];
1096 int failure = 0;
1097 BODY *body;
1098 gf_io_t pc;
1100 size = 0; /* all bets off, abort sanity test */
1101 gf_set_so_writec(&pc, pkg->so);
1103 section[0] = '\0';
1104 if(!pine_mail_fetch_structure(pkg->stream, raw, &body, 0))
1105 return(0);
1107 if(msgno_part_deleted(pkg->stream, raw, "")){
1108 tlen = 0;
1109 failure = !save_ex_replace_body(fetch, &hlen, body, pc);
1111 else
1112 failure = !(so_nputs(pkg->so, fetch, (long) hlen)
1113 && save_ex_output_body(pkg->stream, raw, section,
1114 body, &tlen, pc));
1116 gf_clear_so_writec(pkg->so);
1118 if(failure)
1119 return(0);
1121 q_status_message(SM_ORDER, 3, 3,
1122 /* TRANSLATORS: A warning to user that the message parts
1123 they deleted will not be included in the copy they
1124 are now saving to. */
1125 _("NOTE: Deleted message parts NOT included in saved copy!"));
1128 else{
1129 if(!so_nputs(pkg->so, fetch, (long) hlen))
1130 return(0);
1132 fetch = pine_mail_fetch_text(pkg->stream, raw, NULL, &tlen, FT_PEEK);
1134 if(!(fetch && so_nputs(pkg->so, fetch, tlen)))
1135 return(0);
1138 so_seek(pkg->so, 0L, 0); /* rewind just in case */
1139 mlen = hlen + tlen;
1141 if(size && mlen < size){
1142 char buf[128];
1144 if(sp_dead_stream(pkg->stream)){
1145 snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
1146 q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
1147 dprint((1, "save_fetch_append_cb: %s\n", buf));
1148 return(0);
1150 else{
1151 if(pith_opt_save_size_changed_prompt
1152 && (*pith_opt_save_size_changed_prompt)(mn_raw2m(pkg->msgmap, raw), 0) == 'y'){
1153 snprintf(buf, sizeof(buf),
1154 "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
1155 raw, size, mlen);
1156 dprint((1, "save_fetch_append_cb: %s", buf));
1157 snprintf(buf, sizeof(buf),
1158 "Message to save shrank: source msg # %ld may be saved incorrectly",
1159 mn_raw2m(pkg->msgmap, raw));
1160 if(F_OFF(F_IGNORE_SIZE, ps_global))
1161 q_status_message(SM_ORDER, 0, 3, buf);
1163 else{
1164 snprintf(buf, sizeof(buf),
1165 "Message to save shrank: raw msg # %ld went from announced size %ld to actual size %ld",
1166 raw, size, mlen);
1167 dprint((1, "save_fetch_append_cb: %s", buf));
1168 return(0);
1173 INIT(pkg->msg, mail_string, (void *)so_text(pkg->so), mlen);
1174 *message = pkg->msg;
1175 /* Next message */
1176 pkg->msgno = mn_next_cur(pkg->msgmap);
1178 else /* No more messages */
1179 *message = NIL;
1181 *flags = pkg->flags;
1182 *date = (pkg->date && *pkg->date) ? pkg->date : NIL;
1183 return LONGT; /* Return success */
1187 /*----------------------------------------------------------------------
1188 FETCH an rfc822 message header and body and APPEND to destination folder
1190 Args:
1193 Result:
1195 ----*/
1197 save_fetch_append(MAILSTREAM *stream, long int raw, char *sect,
1198 MAILSTREAM *save_stream, char *save_folder, CONTEXT_S *context,
1199 long unsigned int size, char *flags, char *date, STORE_S *so)
1201 int rc, rv, old_imap_server = 0;
1202 long j;
1203 char *fetch;
1204 unsigned long hlen, tlen, mlen;
1205 STRING msg;
1207 if((fetch = mail_fetch_header(stream, raw, sect, NULL, &hlen, FT_PEEK)) != NULL){
1209 * If there's no date string, then caller found the
1210 * MESSAGECACHE for this message element didn't already have it.
1211 * So, parse the "internal date" by hand since fetchstructure
1212 * hasn't been called yet for this particular message, and
1213 * we don't want to call it now just to get the date since
1214 * the full header has what we want. Likewise, don't even
1215 * think about calling mail_fetchfast either since it also
1216 * wants to load mc->rfc822_size (which we could actually
1217 * use but...), which under some drivers is *very*
1218 * expensive to acquire (can you say NNTP?)...
1220 if(!*date)
1221 saved_date(date, fetch);
1223 else
1224 return(0); /* fetchtext writes error */
1226 rc = MSG_EX_DELETE; /* "rc" overloaded */
1227 if(msgno_exceptions(stream, raw, NULL, &rc, 0)){
1228 char section[64];
1229 int failure = 0;
1230 BODY *body;
1231 gf_io_t pc;
1233 size = 0; /* all bets off, abort sanity test */
1234 gf_set_so_writec(&pc, so);
1236 if(sect && *sect){
1237 snprintf(section, sizeof(section), "%s.1", sect);
1238 if(!(body = mail_body(stream, raw, (unsigned char *) section)))
1239 return(0);
1241 else{
1242 section[0] = '\0';
1243 if(!pine_mail_fetch_structure(stream, raw, &body, 0))
1244 return(0);
1248 * Walk the MIME structure looking for exceptional segments,
1249 * writing them in the requested fashion.
1251 * First, though, check for the easy case...
1253 if(msgno_part_deleted(stream, raw, sect ? sect : "")){
1254 tlen = 0;
1255 failure = !save_ex_replace_body(fetch, &hlen, body, pc);
1257 else{
1259 * Otherwise, roll up your sleeves and get to work...
1260 * start by writing msg header and then the processed body
1262 failure = !(so_nputs(so, fetch, (long) hlen)
1263 && save_ex_output_body(stream, raw, section,
1264 body, &tlen, pc));
1267 gf_clear_so_writec(so);
1269 if(failure)
1270 return(0);
1272 q_status_message(SM_ORDER, 3, 3,
1273 _("NOTE: Deleted message parts NOT included in saved copy!"));
1276 else{
1277 /* First, write the header we just fetched... */
1278 if(!so_nputs(so, fetch, (long) hlen))
1279 return(0);
1281 old_imap_server = is_imap_stream(stream) && !modern_imap_stream(stream);
1283 /* Second, go fetch the corresponding text... */
1284 fetch = pine_mail_fetch_text(stream, raw, sect, &tlen,
1285 !old_imap_server ? FT_PEEK : 0);
1288 * Special handling in case we're fetching a Message/rfc822
1289 * segment and we're talking to an old server...
1291 if(fetch && *fetch == '\0' && sect && (hlen + tlen) != size){
1292 so_seek(so, 0L, 0);
1293 fetch = pine_mail_fetch_body(stream, raw, sect, &tlen, 0L);
1297 * Pre IMAP4 servers can't do a non-peeking mail_fetch_text,
1298 * so if the message we are saving from was originally unseen,
1299 * we have to change it back to unseen. Flags contains the
1300 * string "SEEN" if the original message was seen.
1302 if(old_imap_server && (!flags || !srchstr(flags,"SEEN"))){
1303 char seq[20];
1305 strncpy(seq, long2string(raw), sizeof(seq));
1306 seq[sizeof(seq)-1] = '\0';
1307 mail_flag(stream, seq, "\\SEEN", 0);
1310 /* If fetch succeeded, write the result */
1311 if(!(fetch && so_nputs(so, fetch, tlen)))
1312 return(0);
1315 so_seek(so, 0L, 0); /* rewind just in case */
1318 * Set up a c-client string driver so we can hand the
1319 * collected text down to mail_append.
1321 * NOTE: We only test the size if and only if we already
1322 * have it. See, in some drivers, especially under
1323 * dos, its too expensive to get the size (full
1324 * header and body text fetch plus MIME parse), so
1325 * we only verify the size if we already know it.
1327 mlen = hlen + tlen;
1329 if(size && mlen < size){
1330 char buf[128];
1332 if(sp_dead_stream(stream)){
1333 snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
1334 q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
1335 dprint((1, "save_fetch_append: %s", buf));
1336 return(0);
1338 else{
1339 if(pith_opt_save_size_changed_prompt
1340 && (*pith_opt_save_size_changed_prompt)(mn_raw2m(sp_msgmap(stream), raw), 0) == 'y'){
1341 snprintf(buf, sizeof(buf),
1342 "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
1343 raw, size, mlen);
1344 dprint((1, "save_fetch_append: %s", buf));
1345 snprintf(buf, sizeof(buf),
1346 "Message to save shrank: source msg # %ld may be saved incorrectly",
1347 mn_raw2m(sp_msgmap(stream), raw));
1348 q_status_message(SM_ORDER, 0, 3, buf);
1350 else{
1351 snprintf(buf, sizeof(buf),
1352 "Message to save shrank: source raw msg # %ld went from announced size %ld to actual size %ld",
1353 raw, size, mlen);
1354 dprint((1, "save_fetch_append: %s", buf));
1355 return(0);
1360 INIT(&msg, mail_string, (void *)so_text(so), mlen);
1362 rc = 0;
1363 while(!(rv = (int) context_append_full(context, save_stream,
1364 save_folder, flags,
1365 *date ? date : NULL,
1366 &msg))){
1367 if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
1368 break; /* c-client returned error? */
1370 if(context && (context->use & CNTXT_INCMNG)
1371 && context_isambig(save_folder)){
1372 q_status_message(SM_ORDER, 3, 5,
1373 _("Can only save to existing folders in Incoming Collection"));
1374 break;
1377 ps_global->try_to_create = 0; /* reset for next time */
1378 if((j = create_for_save(context, save_folder)) < 1){
1379 if(j < 0)
1380 rv = -1; /* user cancelled */
1382 break;
1385 SETPOS((&msg), 0L); /* reset string driver */
1388 return(rv);
1393 * save_ex_replace_body -
1395 * NOTE : hlen points to a cell that has the byte count of "hdr" on entry
1396 * *BUT* which is to contain the count of written bytes on exit
1399 save_ex_replace_body(char *hdr, long unsigned int *hlen, struct mail_bodystruct *body, gf_io_t pc)
1401 unsigned long len;
1404 * "X-" out the given MIME headers unless they're
1405 * the same as we're going to substitute...
1407 if(body->type == TYPETEXT
1408 && (!body->subtype || !strucmp(body->subtype, "plain"))
1409 && body->encoding == ENC7BIT){
1410 if(!gf_nputs(hdr, *hlen, pc)) /* write out header */
1411 return(0);
1413 else{
1414 int bol = 1;
1417 * write header, "X-"ing out transport headers bothersome to
1418 * software but potentially useful to the human recipient...
1420 for(len = *hlen; len; len--){
1421 if(bol){
1422 unsigned long n;
1424 bol = 0;
1425 if(save_ex_mask_types(hdr, &n, pc))
1426 *hlen += n; /* add what we inserted */
1427 else
1428 break;
1431 if(*hdr == '\015' && *(hdr+1) == '\012'){
1432 bol++; /* remember beginning of line */
1433 len--; /* account for LF */
1434 if(gf_nputs(hdr, 2, pc))
1435 hdr += 2;
1436 else
1437 break;
1439 else if(!(*pc)(*hdr++))
1440 break;
1443 if(len) /* bytes remain! */
1444 return(0);
1447 /* Now, blat out explanatory text as the body... */
1448 if(save_ex_explain_body(body, &len, pc)){
1449 *hlen += len;
1450 return(1);
1452 else
1453 return(0);
1458 save_ex_output_body(MAILSTREAM *stream, long int raw, char *section,
1459 struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
1461 char *txtp, newsect[128];
1462 unsigned long ilen;
1464 txtp = mail_fetch_mime(stream, raw, section, len, FT_PEEK);
1466 if(msgno_part_deleted(stream, raw, section))
1467 return(save_ex_replace_body(txtp, len, body, pc));
1469 if(body->type == TYPEMULTIPART){
1470 #define BOUNDARYLEN 128
1471 char *subsect, boundary[BOUNDARYLEN];
1472 int n, blen;
1473 PART *part = body->nested.part;
1474 PARAMETER *param;
1476 /* Locate supplied multipart boundary */
1477 for (param = body->parameter; param; param = param->next)
1478 if (!strucmp(param->attribute, "boundary")){ /* BOUNDARYLEN == sizeof(boundary) */
1479 snprintf(boundary, sizeof(boundary), "--%.*s\015\012", BOUNDARYLEN-10,
1480 param->value);
1481 blen = strlen(boundary);
1482 break;
1485 if(!param){
1486 q_status_message(SM_ORDER|SM_DING, 3, 3, "Missing MIME boundary");
1487 return(0);
1491 * BUG: if multi/digest and a message deleted (which we'll
1492 * change to text/plain), we need to coerce this composite
1493 * type to multi/mixed !!
1495 if(!gf_nputs(txtp, *len, pc)) /* write MIME header */
1496 return(0);
1498 /* Prepare to specify sub-sections */
1499 strncpy(newsect, section, sizeof(newsect));
1500 newsect[sizeof(newsect)-1] = '\0';
1501 subsect = &newsect[n = strlen(newsect)];
1502 if(n > 2 && !strcmp(&newsect[n-2], ".0"))
1503 subsect--;
1504 else if(n){
1505 if((subsect-newsect) < sizeof(newsect))
1506 *subsect++ = '.';
1509 n = 1;
1510 do { /* for each part */
1511 strncpy(subsect, int2string(n++), sizeof(newsect)-(subsect-newsect));
1512 newsect[sizeof(newsect)-1] = '\0';
1513 if(gf_puts(boundary, pc)
1514 && save_ex_output_body(stream, raw, newsect,
1515 &part->body, &ilen, pc))
1516 *len += blen + ilen;
1517 else
1518 return(0);
1520 while ((part = part->next) != NULL); /* until done */
1521 /* BOUNDARYLEN = sizeof(boundary) */
1522 snprintf(boundary, sizeof(boundary), "--%.*s--\015\012", BOUNDARYLEN-10,param->value);
1523 *len += blen + 2;
1524 return(gf_puts(boundary, pc));
1527 /* Start by writing the part's MIME header */
1528 if(!gf_nputs(txtp, *len, pc))
1529 return(0);
1531 if(body->type == TYPEMESSAGE
1532 && (!body->subtype || !strucmp(body->subtype, "rfc822"))){
1533 /* write RFC 822 message's header */
1534 if((txtp = mail_fetch_header(stream,raw,section,NULL,&ilen,FT_PEEK))
1535 && gf_nputs(txtp, ilen, pc))
1536 *len += ilen;
1537 else
1538 return(0);
1540 /* then go deal with its body parts */
1541 snprintf(newsect, sizeof(newsect), "%.10s%s%s", section, section ? "." : "",
1542 (body->nested.msg->body->type == TYPEMULTIPART) ? "0" : "1");
1543 if(save_ex_output_body(stream, raw, newsect,
1544 body->nested.msg->body, &ilen, pc)){
1545 *len += ilen;
1546 return(1);
1549 return(0);
1552 /* Write corresponding body part */
1553 if((txtp = pine_mail_fetch_body(stream, raw, section, &ilen, FT_PEEK))
1554 && gf_nputs(txtp, (long) ilen, pc) && gf_puts("\015\012", pc)){
1555 *len += ilen + 2;
1556 return(1);
1559 return(0);
1563 /*----------------------------------------------------------------------
1564 Mask off any header entries we don't want to show up in exceptional saves
1566 Args: hdr -- pointer to start of a header line
1567 pc -- function to write the prefix
1569 ----*/
1571 save_ex_mask_types(char *hdr, long unsigned int *len, gf_io_t pc)
1573 char *s = NULL;
1575 if(!struncmp(hdr, "content-type:", 13))
1576 s = "Content-Type: Text/Plain; charset=US-ASCII\015\012X-";
1577 else if(!struncmp(hdr, "content-description:", 20))
1578 s = "Content-Description: Deleted Attachment\015\012X-";
1579 else if(!struncmp(hdr, "content-transfer-encoding:", 26)
1580 || !struncmp(hdr, "content-disposition:", 20))
1581 s = "X-";
1583 return((*len = s ? strlen(s) : 0) ? gf_puts(s, pc) : 1);
1588 save_ex_explain_body(struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
1590 unsigned long ilen;
1591 char **blurbp;
1592 static char *blurb[] = {
1593 N_("The following attachment was DELETED when this message was saved:"),
1594 NULL
1597 *len = 0;
1598 for(blurbp = blurb; *blurbp; blurbp++)
1599 if(save_ex_output_line(_(*blurbp), &ilen, pc))
1600 *len += ilen;
1601 else
1602 return(0);
1604 if(!save_ex_explain_parts(body, 0, &ilen, pc))
1605 return(0);
1607 *len += ilen;
1608 return(1);
1613 save_ex_explain_parts(struct mail_bodystruct *body, int depth, long unsigned int *len, gf_io_t pc)
1615 char tmp[MAILTMPLEN], buftmp[MAILTMPLEN];
1616 unsigned long ilen;
1617 char *name = parameter_val(body->parameter, "name");
1619 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
1620 PART *part = body->nested.part; /* first body part */
1622 *len = 0;
1623 if(body->description && *body->description){ /* MAILTMPLEN = sizeof(tmp) */
1624 snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment described",
1625 depth, depth, " ", body_type_names(body->type),
1626 MAILTMPLEN-300, body->subtype ? body->subtype : "Unknown",
1627 name ? " (Name=\"" : "",
1628 name ? name : "",
1629 name ? "\")" : "");
1630 if(!save_ex_output_line(tmp, len, pc))
1631 return(0);
1633 snprintf(buftmp, sizeof(buftmp), "%.75s", body->description);
1634 snprintf(tmp, sizeof(tmp), "%*.*s as \"%.50s\" containing:", depth, depth, " ",
1635 (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1636 SIZEOF_20KBUF, buftmp));
1638 else{ /* MAILTMPLEN = sizeof(tmp) */
1639 snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment containing:",
1640 depth, depth, " ",
1641 body_type_names(body->type),
1642 MAILTMPLEN-300, body->subtype ? body->subtype : "Unknown",
1643 name ? " (Name=\"" : "",
1644 name ? name : "",
1645 name ? "\")" : "");
1648 if(save_ex_output_line(tmp, &ilen, pc))
1649 *len += ilen;
1650 else
1651 return(0);
1653 depth++;
1654 do /* for each part */
1655 if(save_ex_explain_parts(&part->body, depth, &ilen, pc))
1656 *len += ilen;
1657 else
1658 return(0);
1659 while ((part = part->next) != NULL); /* until done */
1661 else{ /* MAILTMPLEN = sizeof(tmp) */
1662 snprintf(tmp, sizeof(tmp), "%*.*sA %s/%.*s%.10s%.100s%.10s segment of about %s bytes%s",
1663 depth, depth, " ",
1664 body_type_names(body->type),
1665 MAILTMPLEN-300, body->subtype ? body->subtype : "Unknown",
1666 name ? " (Name=\"" : "",
1667 name ? name : "",
1668 name ? "\")" : "",
1669 comatose((body->encoding == ENCBASE64)
1670 ? ((body->size.bytes * 3)/4)
1671 : body->size.bytes),
1672 body->description ? "," : ".");
1673 if(!save_ex_output_line(tmp, len, pc))
1674 return(0);
1676 if(body->description && *body->description){ /* MAILTMPLEN = sizeof(tmp) */
1677 snprintf(buftmp, sizeof(buftmp), "%.75s", body->description);
1678 snprintf(tmp, sizeof(tmp), "%*.*s described as \"%.*s\"", depth, depth, " ",
1679 MAILTMPLEN-100,
1680 (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1681 SIZEOF_20KBUF, buftmp));
1682 if(save_ex_output_line(tmp, &ilen, pc))
1683 *len += ilen;
1684 else
1685 return(0);
1689 return(1);
1694 save_ex_output_line(char *line, long unsigned int *len, gf_io_t pc)
1696 snprintf(tmp_20k_buf, SIZEOF_20KBUF, " [ %-*.*s ]\015\012", 68, 68, line);
1697 *len = strlen(tmp_20k_buf);
1698 return(gf_puts(tmp_20k_buf, pc));
1702 /*----------------------------------------------------------------------
1703 Save() helper function to create canonical date string from given header
1705 Args: date -- buf to recieve canonical date string
1706 header -- rfc822 header to fish date string from
1708 Result: date filled with canonicalized date in header, or null string
1709 ----*/
1710 void
1711 saved_date(char *date, char *header)
1713 char *d, *p, c;
1714 MESSAGECACHE elt;
1716 *date = '\0';
1717 if((toupper((unsigned char)(*(d = header)))
1718 == 'D' && !strncmp(d, "date:", 5))
1719 || (d = srchstr(header, "\015\012date:"))){
1720 for(d += 7; *d == ' '; d++)
1721 ; /* skip white space */
1723 if((p = strstr(d, "\015\012")) != NULL){
1724 for(; p > d && *p == ' '; p--)
1725 ; /* skip white space */
1727 c = *p;
1728 *p = '\0'; /* tie off internal date */
1731 if(mail_parse_date(&elt, (unsigned char *) d)) /* normalize it */
1732 mail_date(date, &elt);
1734 if(p) /* restore header */
1735 *p = c;
1740 MAILSTREAM *
1741 save_msg_stream(CONTEXT_S *context, char *folder, int *we_opened)
1743 char tmp[MAILTMPLEN];
1744 MAILSTREAM *save_stream = NULL;
1746 if(IS_REMOTE(context_apply(tmp, context, folder, sizeof(tmp)))
1747 && !(save_stream = sp_stream_get(tmp, SP_MATCH))
1748 && !(save_stream = sp_stream_get(tmp, SP_SAME))){
1749 if((save_stream = context_open(context, NULL, folder,
1750 OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
1751 NULL)) != NULL)
1752 *we_opened = 1;
1755 return(save_stream);
1759 /*----------------------------------------------------------------------
1760 Offer to create a non-existant folder to copy message[s] into
1762 Args: context -- context to create folder in
1763 folder -- name of folder to create
1765 Result: 0 if create failed (c-client writes error)
1766 1 if create successful
1767 -1 if user declines to create folder
1768 ----*/
1770 create_for_save(CONTEXT_S *context, char *folder)
1772 int ret;
1774 if(pith_opt_save_create_prompt
1775 && (ret = (*pith_opt_save_create_prompt)(context, folder, 1)) != 1)
1776 return(ret);
1778 return(context_create(context, NULL, folder));