* clear out some warnings by gcc 9.3.1.
[alpine.git] / pith / save.c
blob2d74d544abec7d0606cd45980f1d9283d89c0734
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-2020 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);
51 int save_ex_output_text(char *, int, unsigned long *, gf_io_t);
55 * pith hook
57 int (*pith_opt_save_create_prompt)(CONTEXT_S *, char *, int);
58 int (*pith_opt_save_size_changed_prompt)(long, int);
62 /*----------------------------------------------------------------------
63 save_get_default - return default folder name for saving
64 ----*/
65 char *
66 save_get_default(struct pine *state, ENVELOPE *e, long int rawno,
67 char *section, CONTEXT_S **cntxt)
69 int context_was_set;
71 if(!cntxt)
72 return("");
74 context_was_set = ((*cntxt) != NULL);
76 /* start with the default save context */
77 if(!(*cntxt)
78 && ((*cntxt) = default_save_context(state->context_list)) == NULL)
79 (*cntxt) = state->context_list;
81 if(!e || ps_global->save_msg_rule == SAV_RULE_LAST
82 || ps_global->save_msg_rule == SAV_RULE_DEFLT){
83 if(ps_global->save_msg_rule == SAV_RULE_LAST && ps_global->last_save_context){
84 if(!context_was_set)
85 (*cntxt) = ps_global->last_save_context;
87 else{
88 strncpy(ps_global->last_save_folder,
89 ps_global->VAR_DEFAULT_SAVE_FOLDER,
90 sizeof(ps_global->last_save_folder)-1);
91 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
94 * If the user entered "inbox" as their default save folder it is very
95 * likely they meant the real inbox, not the inbox in the primary collection.
97 if(!context_was_set
98 && !strucmp(ps_global->inbox_name, ps_global->last_save_folder))
99 (*cntxt) = state->context_list;
102 else{
103 save_get_fldr_from_env(ps_global->last_save_folder,
104 sizeof(ps_global->last_save_folder),
105 e, state, rawno, section);
106 /* somebody expunged current message */
107 if(sp_expunge_count(ps_global->mail_stream))
108 return(NULL);
111 return(ps_global->last_save_folder);
116 /*----------------------------------------------------------------------
117 Grope through envelope to find default folder name to save to
119 Args: fbuf -- Buffer to return result in
120 nfbuf -- Size of fbuf
121 e -- The envelope to look in
122 state -- Usual pine state
123 rawmsgno -- Raw c-client sequence number of message
124 section -- Mime section of header data (for message/rfc822)
126 Result: The appropriate default folder name is copied into fbuf.
127 ----*/
128 void
129 save_get_fldr_from_env(char *fbuf, int nfbuf, ENVELOPE *e, struct pine *state,
130 long int rawmsgno, char *section)
132 char fakedomain[2];
133 ADDRESS *tmp_adr = NULL;
134 char buf[MAX(MAXFOLDER,MAX_NICKNAME) + 1];
135 char *bufp;
136 char *folder_name = NULL;
137 static char botch[] = "programmer botch, unknown message save rule";
138 unsigned save_msg_rule;
140 if(!e)
141 return;
143 /* copy this because we might change it below */
144 save_msg_rule = state->save_msg_rule;
146 /* first get the relevant address to base the folder name on */
147 switch(save_msg_rule){
148 case SAV_RULE_FROM:
149 case SAV_RULE_NICK_FROM:
150 case SAV_RULE_NICK_FROM_DEF:
151 case SAV_RULE_FCC_FROM:
152 case SAV_RULE_FCC_FROM_DEF:
153 case SAV_RULE_RN_FROM:
154 case SAV_RULE_RN_FROM_DEF:
155 tmp_adr = e->from ? copyaddr(e->from)
156 : e->sender ? copyaddr(e->sender) : NULL;
157 break;
159 case SAV_RULE_SENDER:
160 case SAV_RULE_NICK_SENDER:
161 case SAV_RULE_NICK_SENDER_DEF:
162 case SAV_RULE_FCC_SENDER:
163 case SAV_RULE_FCC_SENDER_DEF:
164 case SAV_RULE_RN_SENDER:
165 case SAV_RULE_RN_SENDER_DEF:
166 tmp_adr = e->sender ? copyaddr(e->sender)
167 : e->from ? copyaddr(e->from) : NULL;
168 break;
170 case SAV_RULE_REPLYTO:
171 case SAV_RULE_NICK_REPLYTO:
172 case SAV_RULE_NICK_REPLYTO_DEF:
173 case SAV_RULE_FCC_REPLYTO:
174 case SAV_RULE_FCC_REPLYTO_DEF:
175 case SAV_RULE_RN_REPLYTO:
176 case SAV_RULE_RN_REPLYTO_DEF:
177 tmp_adr = e->reply_to ? copyaddr(e->reply_to)
178 : e->from ? copyaddr(e->from)
179 : e->sender ? copyaddr(e->sender) : NULL;
180 break;
182 case SAV_RULE_RECIP:
183 case SAV_RULE_NICK_RECIP:
184 case SAV_RULE_NICK_RECIP_DEF:
185 case SAV_RULE_FCC_RECIP:
186 case SAV_RULE_FCC_RECIP_DEF:
187 case SAV_RULE_RN_RECIP:
188 case SAV_RULE_RN_RECIP_DEF:
189 /* news */
190 if(state->mail_stream && IS_NEWS(state->mail_stream)){
191 char *tmp_a_string, *ng_name;
193 fakedomain[0] = '@';
194 fakedomain[1] = '\0';
196 /* find the news group name */
197 if((ng_name = strstr(state->mail_stream->mailbox,"#news")) != NULL)
198 ng_name += 6;
199 else
200 ng_name = state->mail_stream->mailbox; /* shouldn't happen */
202 /* copy this string so rfc822_parse_adrlist can't blast it */
203 tmp_a_string = cpystr(ng_name);
204 /* make an adr */
205 rfc822_parse_adrlist(&tmp_adr, tmp_a_string, fakedomain);
206 fs_give((void **)&tmp_a_string);
207 if(tmp_adr && tmp_adr->host && tmp_adr->host[0] == '@')
208 tmp_adr->host[0] = '\0';
210 /* not news */
211 else{
212 static char *fields[] = {"Resent-To", NULL};
213 char *extras, *values[sizeof(fields)/sizeof(fields[0])];
215 extras = pine_fetchheader_lines(state->mail_stream, rawmsgno,
216 section, fields);
217 if(extras){
218 long i;
220 memset(values, 0, sizeof(fields));
221 simple_header_parse(extras, fields, values);
222 fs_give((void **)&extras);
224 for(i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
225 if(values[i]){
226 if(tmp_adr) /* take last matching value */
227 mail_free_address(&tmp_adr);
229 /* build a temporary address list */
230 fakedomain[0] = '@';
231 fakedomain[1] = '\0';
232 rfc822_parse_adrlist(&tmp_adr, values[i], fakedomain);
233 fs_give((void **)&values[i]);
237 if(!tmp_adr)
238 tmp_adr = e->to ? copyaddr(e->to) : NULL;
241 break;
243 default:
244 alpine_panic(botch);
245 break;
248 /* For that address, lookup the fcc or nickname from address book */
249 switch(save_msg_rule){
250 case SAV_RULE_NICK_FROM:
251 case SAV_RULE_NICK_SENDER:
252 case SAV_RULE_NICK_REPLYTO:
253 case SAV_RULE_NICK_RECIP:
254 case SAV_RULE_FCC_FROM:
255 case SAV_RULE_FCC_SENDER:
256 case SAV_RULE_FCC_REPLYTO:
257 case SAV_RULE_FCC_RECIP:
258 case SAV_RULE_NICK_FROM_DEF:
259 case SAV_RULE_NICK_SENDER_DEF:
260 case SAV_RULE_NICK_REPLYTO_DEF:
261 case SAV_RULE_NICK_RECIP_DEF:
262 case SAV_RULE_FCC_FROM_DEF:
263 case SAV_RULE_FCC_SENDER_DEF:
264 case SAV_RULE_FCC_REPLYTO_DEF:
265 case SAV_RULE_FCC_RECIP_DEF:
266 switch(save_msg_rule){
267 case SAV_RULE_NICK_FROM:
268 case SAV_RULE_NICK_SENDER:
269 case SAV_RULE_NICK_REPLYTO:
270 case SAV_RULE_NICK_RECIP:
271 case SAV_RULE_NICK_FROM_DEF:
272 case SAV_RULE_NICK_SENDER_DEF:
273 case SAV_RULE_NICK_REPLYTO_DEF:
274 case SAV_RULE_NICK_RECIP_DEF:
275 bufp = get_nickname_from_addr(tmp_adr, buf, sizeof(buf));
276 break;
278 case SAV_RULE_FCC_FROM:
279 case SAV_RULE_FCC_SENDER:
280 case SAV_RULE_FCC_REPLYTO:
281 case SAV_RULE_FCC_RECIP:
282 case SAV_RULE_FCC_FROM_DEF:
283 case SAV_RULE_FCC_SENDER_DEF:
284 case SAV_RULE_FCC_REPLYTO_DEF:
285 case SAV_RULE_FCC_RECIP_DEF:
286 bufp = get_fcc_from_addr(tmp_adr, buf, sizeof(buf));
287 break;
290 if(bufp && *bufp){
291 istrncpy(fbuf, bufp, nfbuf - 1);
292 fbuf[nfbuf - 1] = '\0';
294 else
295 /* fall back to non-nick/non-fcc version of rule */
296 switch(save_msg_rule){
297 case SAV_RULE_NICK_FROM:
298 case SAV_RULE_FCC_FROM:
299 save_msg_rule = SAV_RULE_FROM;
300 break;
302 case SAV_RULE_NICK_SENDER:
303 case SAV_RULE_FCC_SENDER:
304 save_msg_rule = SAV_RULE_SENDER;
305 break;
307 case SAV_RULE_NICK_REPLYTO:
308 case SAV_RULE_FCC_REPLYTO:
309 save_msg_rule = SAV_RULE_REPLYTO;
310 break;
312 case SAV_RULE_NICK_RECIP:
313 case SAV_RULE_FCC_RECIP:
314 save_msg_rule = SAV_RULE_RECIP;
315 break;
317 default:
318 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
319 fbuf[nfbuf - 1] = '\0';
320 break;
323 break;
326 /* Realname */
327 switch(save_msg_rule){
328 case SAV_RULE_RN_FROM_DEF:
329 case SAV_RULE_RN_FROM:
330 case SAV_RULE_RN_SENDER_DEF:
331 case SAV_RULE_RN_SENDER:
332 case SAV_RULE_RN_RECIP_DEF:
333 case SAV_RULE_RN_RECIP:
334 case SAV_RULE_RN_REPLYTO_DEF:
335 case SAV_RULE_RN_REPLYTO:
336 /* Fish out the realname */
337 if(tmp_adr && tmp_adr->personal && tmp_adr->personal[0])
338 folder_name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
339 SIZEOF_20KBUF, tmp_adr->personal);
341 if(folder_name && folder_name[0]){
342 istrncpy(fbuf, folder_name, nfbuf - 1);
343 fbuf[nfbuf - 1] = '\0';
345 else{ /* fall back to other behaviors */
346 switch(save_msg_rule){
347 case SAV_RULE_RN_FROM:
348 save_msg_rule = SAV_RULE_FROM;
349 break;
351 case SAV_RULE_RN_SENDER:
352 save_msg_rule = SAV_RULE_SENDER;
353 break;
355 case SAV_RULE_RN_RECIP:
356 save_msg_rule = SAV_RULE_RECIP;
357 break;
359 case SAV_RULE_RN_REPLYTO:
360 save_msg_rule = SAV_RULE_REPLYTO;
361 break;
363 default:
364 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
365 fbuf[nfbuf - 1] = '\0';
366 break;
370 break;
373 /* get the username out of the mailbox for this address */
374 switch(save_msg_rule){
375 case SAV_RULE_FROM:
376 case SAV_RULE_SENDER:
377 case SAV_RULE_REPLYTO:
378 case SAV_RULE_RECIP:
380 * Fish out the user's name from the mailbox portion of
381 * the address and put it in folder.
383 folder_name = (tmp_adr && tmp_adr->mailbox && tmp_adr->mailbox[0])
384 ? tmp_adr->mailbox : NULL;
385 if(!get_uname(folder_name, fbuf, nfbuf)){
386 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
387 fbuf[nfbuf - 1] = '\0';
390 break;
393 if(tmp_adr)
394 mail_free_address(&tmp_adr);
398 /*----------------------------------------------------------------------
399 Do the work of actually saving messages to a folder
401 Args: state -- pine state struct (for stream pointers)
402 stream -- source stream, which msgmap refers to
403 context -- context to interpret name in if not fully qualified
404 folder -- The folder to save the message in
405 msgmap -- message map of currently selected messages
406 flgs -- Possible bits are
407 SV_DELETE - delete after saving
408 SV_FOR_FILT - called from filtering function, not save
409 SV_FIX_DELS - remove Del mark before saving
410 SV_INBOXWOCNTXT - "inbox" is interpreted without context making
411 it the one-true inbox instead
413 Result: Returns number of messages saved
415 Note: There's a bit going on here; temporary clearing of deleted flags
416 since they are *not* preserved, picking or creating the stream for
417 copy or append, and dealing with errors...
418 We try to preserve user keywords by setting them in the destination.
419 ----*/
420 long
421 save(struct pine *state, MAILSTREAM *stream, CONTEXT_S *context, char *folder,
422 MSGNO_S *msgmap, int flgs)
424 int rv, rc, j, our_stream = 0, cancelled = 0;
425 int delete, filter, k, worry_about_keywords = 0;
426 char *save_folder, *seq, *flags = NULL, date[64], tmp[MAILTMPLEN];
427 long i, nmsgs, rawno;
428 size_t len;
429 STORE_S *so = NULL;
430 MAILSTREAM *save_stream = NULL;
431 MESSAGECACHE *mc;
433 delete = flgs & SV_DELETE;
434 filter = flgs & SV_FOR_FILT;
436 if(strucmp(folder, state->inbox_name) == 0 && flgs & SV_INBOXWOCNTXT){
437 save_folder = state->VAR_INBOX_PATH;
438 context = NULL;
440 else
441 save_folder = folder;
444 * Because the COPY/APPEND command doesn't always create keywords when they
445 * aren't already defined in a mailbox, we need to ensure that the keywords
446 * exist in the destination (are defined and settable) before we do the copies.
447 * Here's what the code is doing
449 * If we have keywords set in the source messages
450 * Add a dummy message to destination mailbox
452 * for each keyword that is set in the set of messages we're saving
453 * set the keyword in that message (thus creating it)
455 * remember deleted messages
456 * undelete them
457 * delete dummy message
458 * expunge
459 * delete remembered messages
461 * After that the assumption is that the keywords will be saved by a
462 * COPY command. We need to set the flags string ourself for appends.
465 /* are any keywords set in the source messages? */
466 for(i = mn_first_cur(msgmap); !worry_about_keywords && i > 0L; i = mn_next_cur(msgmap)){
467 rawno = mn_m2raw(msgmap, i);
468 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
469 ? mail_elt(stream, rawno) : NULL;
470 if(mc && mc->user_flags)
471 worry_about_keywords++;
474 if(worry_about_keywords){
475 MAILSTREAM *dstn_stream = NULL;
476 int already_open = 0;
477 int we_blocked_reuse = 0;
480 * Possible problem created by our stream re-use
481 * strategy. If we are going to open a new stream
482 * here, we want to be sure not to re-use the
483 * stream we are saving _from_, so take it out of the
484 * re-use pool before we call open.
486 if(sp_flagged(stream, SP_USEPOOL)){
487 we_blocked_reuse++;
488 sp_unflag(stream, SP_USEPOOL);
491 /* see if there is a stream open already */
492 if(!dstn_stream){
493 dstn_stream = context_already_open_stream(context,
494 save_folder,
495 AOS_RW_ONLY);
496 already_open = dstn_stream ? 1 : 0;
499 if(!dstn_stream)
500 dstn_stream = context_open(context, NULL,
501 save_folder,
502 SP_USEPOOL | SP_TEMPUSE,
503 NULL);
505 if(dstn_stream && dstn_stream->kwd_create){
506 imapuid_t dummy_uid = 0L;
507 long dummy_msgno, delete_count;
508 int expbits, set;
509 char *flags = NULL;
510 char *user_flag_name, *duser_flag_name;
512 /* find keywords that need to be defined */
513 for(k = 0; stream_to_user_flag_name(stream, k); k++){
514 user_flag_name = stream_to_user_flag_name(stream, k);
515 if(user_flag_name && user_flag_name[0]){
516 /* is this flag set in any of the save set? */
517 for(set = 0, i = mn_first_cur(msgmap);
518 !set && i > 0L;
519 i = mn_next_cur(msgmap)){
520 rawno = mn_m2raw(msgmap, i);
521 if(user_flag_is_set(stream, rawno, user_flag_name))
522 set++;
525 if(set){
527 * The flag may already be defined in this
528 * mailbox. Check for that first.
530 for(j = 0; stream_to_user_flag_name(dstn_stream, j); j++){
531 duser_flag_name = stream_to_user_flag_name(dstn_stream, j);
532 if(duser_flag_name && duser_flag_name[0]
533 && !strucmp(duser_flag_name, user_flag_name)){
534 set = 0;
535 break;
540 if(set){
541 if(flags == NULL){
542 len = strlen(user_flag_name) + 1;
543 flags = (char *) fs_get((len+1) * sizeof(char));
544 snprintf(flags, len+1, "%s ", user_flag_name);
546 else{
547 char *p;
548 size_t newlen;
550 newlen = strlen(user_flag_name) + 1;
551 len += newlen;
552 fs_resize((void **) &flags, (len+1) * sizeof(char));
553 p = flags + strlen(flags);
554 snprintf(p, newlen+1, "%s ", user_flag_name);
560 if(flags){
561 char *p;
562 size_t newlen;
563 STRING msg;
564 char dummymsg[1000];
565 char *id = NULL;
566 char *idused;
567 appenduid_t *au;
569 newlen = strlen("\\DELETED");
570 len += newlen;
571 fs_resize((void **) &flags, (len+1) * sizeof(char));
572 p = flags + strlen(flags);
573 snprintf(p, newlen+1, "%s", "\\DELETED");
575 id = oauth2_generate_state();
576 idused = id ? id : "<xyz>";
577 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);
580 * We need to get the uid of the message we are about to
581 * append so that we can delete it when we're done and
582 * so we don't affect other messages.
585 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream)){
586 au = mail_parameters(NIL, GET_APPENDUID, NIL);
587 mail_parameters(NIL, SET_APPENDUID, (void *) appenduid_cb);
590 INIT(&msg, mail_string, (void *) dummymsg, strlen(dummymsg));
591 if(pine_mail_append(dstn_stream, dstn_stream->mailbox, &msg)){
593 (void) pine_mail_ping(dstn_stream);
595 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
596 dummy_uid = get_last_append_uid();
598 if(dummy_uid == 0L){
599 dummy_msgno = get_msgno_by_msg_id(dstn_stream, idused,
600 sp_msgmap(dstn_stream));
601 if(dummy_msgno <= 0L || dummy_msgno > dstn_stream->nmsgs)
602 dummy_msgno = dstn_stream->nmsgs;
604 rawno = mn_m2raw(sp_msgmap(dstn_stream), dummy_msgno);
605 if(rawno > 0L && rawno <= dstn_stream->nmsgs)
606 dummy_uid = mail_uid(dstn_stream, rawno);
608 if(dummy_uid == 0L)
609 dummy_msgno = dstn_stream->nmsgs;
613 * We need to remember which messages are deleted,
614 * undelete them, do the expunge, then delete them again.
616 delete_count = count_flagged(dstn_stream, F_DEL);
617 if(delete_count){
618 for(i = 1L; i <= dstn_stream->nmsgs; i++)
619 if(((mc = mail_elt(dstn_stream, i)) && mc->valid && mc->deleted)
620 || (mc && !mc->valid && mc->searched)){
621 mc->sequence = 1;
622 expbits = MSG_EX_DELETE;
623 msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
625 else if((mc = mail_elt(dstn_stream, i)) != NULL)
626 mc->sequence = 0;
628 if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
629 mail_flag(dstn_stream, seq, "\\DELETED", ST_SILENT);
630 fs_give((void **) &seq);
634 if(dummy_uid > 0L)
635 mail_flag(dstn_stream, ulong2string(dummy_uid),
636 flags, ST_SET | ST_UID | ST_SILENT);
637 else
638 mail_flag(dstn_stream, ulong2string(dummy_msgno),
639 flags, ST_SET | ST_SILENT);
641 ps_global->mm_log_error = 0;
642 ps_global->expunge_in_progress = 1;
643 mail_expunge(dstn_stream);
644 ps_global->expunge_in_progress = 0;
646 if(delete_count){
647 for(i = 1L; i <= dstn_stream->nmsgs; i++)
648 if((mc = mail_elt(dstn_stream, i)) != NULL){
649 mc->sequence
650 = (msgno_exceptions(dstn_stream, i, "0", &expbits, FALSE)
651 && (expbits & MSG_EX_DELETE));
654 * Remove the EX_DELETE bit in case we're still using
655 * this stream.
657 if(mc->sequence){
658 expbits &= ~MSG_EX_DELETE;
659 msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
663 if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
664 mail_flag(dstn_stream, seq, "\\DELETED", ST_SET | ST_SILENT);
665 fs_give((void **) &seq);
670 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
671 mail_parameters(NIL, SET_APPENDUID, (void *) au);
673 if(id)
674 fs_give((void **) &id);
676 fs_give((void **) &flags);
680 if(dstn_stream && !already_open)
681 pine_mail_close(dstn_stream);
683 if(we_blocked_reuse)
684 sp_set_flags(stream, sp_flags(stream) | SP_USEPOOL);
688 * If any of the messages have exceptional attachment handling
689 * we have to fall thru below to do the APPEND by hand...
691 if(!msgno_any_deletedparts(stream, msgmap)){
692 int loc_to_loc = 0;
695 * Compare the current stream (the save's source) and the stream
696 * the destination folder will need...
698 context_apply(tmp, context, save_folder, sizeof(tmp));
701 * If we're going to be doing a cross-format copy we're better off
702 * using the else code below that knows how to do multi-append.
703 * The part in the if is leaving save_stream set to NULL in the
704 * case that the stream is local and the folder is local and they
705 * are different formats (like unix and tenex). That will cause us
706 * to fall thru to the APPEND case which is faster than using
707 * copy which will use our imap_proxycopy which doesn't know
708 * about multiappend.
710 loc_to_loc = stream && stream->dtb
711 && stream->dtb->flags & DR_LOCAL && !IS_REMOTE(tmp);
712 if(!loc_to_loc || (stream->dtb->valid && (*stream->dtb->valid)(tmp)))
713 save_stream = loc_to_loc ? stream
714 : context_same_stream(context, save_folder, stream);
717 /* if needed, this'll get set in mm_notify */
718 ps_global->try_to_create = 0;
719 rv = rc = 0;
720 nmsgs = 0L;
723 * At this point, if we have a save_stream, then none of the messages
724 * being saved involve special handling that would require our use
725 * of mail_append, so go with mail_copy since in the IMAP case it
726 * means no data on the wire...
728 if(save_stream){
729 char *dseq = NULL, *oseq;
731 if((flgs & SV_FIX_DELS) &&
732 (dseq = currentf_sequence(stream, msgmap, F_DEL, NULL,
733 0, NULL, NULL)))
734 mail_flag(stream, dseq, "\\DELETED", 0L);
736 seq = currentf_sequence(stream, msgmap, 0L, &nmsgs, 0, NULL, NULL);
737 if(!(flgs & SV_PRESERVE)
738 && (F_ON(F_AGG_SEQ_COPY, ps_global)
739 || (mn_get_sort(msgmap) == SortArrival && !mn_get_revsort(msgmap)))){
742 * currentf_sequence() above lit all the elt "sequence"
743 * bits of the interesting messages. Now, build a sequence
744 * that preserves sort order...
746 oseq = build_sequence(stream, msgmap, &nmsgs);
748 else{
749 oseq = NULL; /* no single sequence! */
750 nmsgs = 0L;
751 i = mn_first_cur(msgmap); /* set first to copy */
755 while(!(rv = (int) context_copy(context, save_stream,
756 oseq ? oseq : long2string(mn_m2raw(msgmap, i)),
757 save_folder))){
758 if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
759 break; /* c-client returned error? */
761 if((context && context->use & CNTXT_INCMNG)
762 && context_isambig(save_folder)){
763 q_status_message(SM_ORDER, 3, 5,
764 _("Can only save to existing folders in Incoming Collection"));
765 break;
768 ps_global->try_to_create = 0; /* reset for next time */
769 if((j = create_for_save(context, save_folder)) < 1){
770 if(j < 0)
771 cancelled = 1; /* user cancels */
773 break;
777 if(rv){ /* failure or finished? */
778 if(oseq) /* all done? */
779 break;
780 else
781 nmsgs++;
783 else{ /* failure! */
784 if(oseq)
785 nmsgs = 0L; /* nothing copy'd */
787 break;
790 while((i = mn_next_cur(msgmap)) > 0L);
792 if(rv && delete) /* delete those saved */
793 mail_flag(stream, seq, "\\DELETED", ST_SET);
794 else if(dseq) /* or restore previous state */
795 mail_flag(stream, dseq, "\\DELETED", ST_SET);
797 if(dseq) /* clean up */
798 fs_give((void **)&dseq);
800 if(oseq)
801 fs_give((void **)&oseq);
803 fs_give((void **)&seq);
805 else{
807 * Special handling requires mail_append. See if we can
808 * re-use stream source messages are on...
810 save_stream = context_same_stream(context, save_folder, stream);
813 * IF the destination's REMOTE, open a stream here so c-client
814 * doesn't have to open it for each aggregate save...
816 if(!save_stream){
817 if(context_apply(tmp, context, save_folder, sizeof(tmp))[0] == '{'
818 && (save_stream = context_open(context, NULL,
819 save_folder,
820 OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
821 NULL)))
822 our_stream = 1;
826 * Allocate a storage object to temporarily store the message
827 * object in. Below it'll get mapped into a c-client STRING struct
828 * in preparation for handing off to context_append...
830 if(!(so = so_get(CharStar, NULL, WRITE_ACCESS))){
831 dprint((1, "Can't allocate store for save: %s\n",
832 error_description(errno)));
833 q_status_message(SM_ORDER | SM_DING, 3, 4,
834 "Problem creating space for message text.");
838 * get a sequence of invalid elt's so we can get their flags...
840 if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
841 mail_fetch_fast(stream, seq, 0L);
842 fs_give((void **) &seq);
846 * If we're supposed set the deleted flag, clear the elt bit
847 * we'll use to build the sequence later...
849 if(delete)
850 for(i = 1L; i <= stream->nmsgs; i++)
851 if((mc = mail_elt(stream, i)) != NULL)
852 mc->sequence = 0;
854 nmsgs = 0L;
856 if(pith_opt_save_size_changed_prompt)
857 (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
860 * if there is more than one message, do multiappend.
861 * otherwise, we can use our already open stream.
863 if(!save_stream || !is_imap_stream(save_stream) ||
864 (LEVELMULTIAPPEND(save_stream) && mn_total_cur(msgmap) > 1)){
865 APPENDPACKAGE pkg;
866 STRING msg;
868 pkg.stream = stream;
869 /* tell save_fetch_append_cb whether or not to leave deleted flag */
870 pkg.flags = flgs & SV_FIX_DELS ? NULL : cpystr("\\DELETED");
871 pkg.date = date;
872 pkg.msg = &msg;
873 pkg.msgmap = msgmap;
875 if ((pkg.so = so) && ((pkg.msgno = mn_first_cur(msgmap)) > 0L)) {
876 so_truncate(so, 0L);
879 * we've gotta make sure this is a stream that we've
880 * opened ourselves.
882 rc = 0;
883 while(!(rv = context_append_multiple(context,
884 our_stream ? save_stream
885 : NULL, save_folder,
886 save_fetch_append_cb,
887 &pkg,
888 stream))) {
890 if(rc++ || !ps_global->try_to_create)
891 break;
892 if((context && context->use & CNTXT_INCMNG)
893 && context_isambig(save_folder)){
894 q_status_message(SM_ORDER, 3, 5,
895 _("Can only save to existing folders in Incoming Collection"));
896 break;
899 ps_global->try_to_create = 0;
900 if((j = create_for_save(context, save_folder)) < 1){
901 if(j < 0)
902 cancelled = 1;
903 break;
907 if(pkg.flags)
908 fs_give((void **) &pkg.flags);
910 ps_global->noshow_error = 0;
912 if(rv){
914 * Success! Count it, and if it's not already deleted and
915 * it's supposed to be, mark it to get deleted later...
917 for(i = mn_first_cur(msgmap); so && i > 0L;
918 i = mn_next_cur(msgmap)){
919 nmsgs++;
920 if(delete){
921 rawno = mn_m2raw(msgmap, i);
922 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
923 ? mail_elt(stream, rawno) : NULL;
924 if(mc && !mc->deleted)
925 mc->sequence = 1; /* mark for later deletion */
930 else
931 cancelled = 1; /* No messages to append! */
933 if(sp_expunge_count(stream))
934 cancelled = 1; /* All bets are off! */
936 else
937 for(i = mn_first_cur(msgmap); so && i > 0L; i = mn_next_cur(msgmap)){
938 int preserve_these_flags;
940 so_truncate(so, 0L);
942 rawno = mn_m2raw(msgmap, i);
943 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
944 ? mail_elt(stream, rawno) : NULL;
946 /* always preserve these flags */
947 preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
948 /* maybe preserve deleted flag */
949 preserve_these_flags |= flgs & SV_FIX_DELS ? 0 : F_DEL;
950 flags = flag_string(stream, rawno, preserve_these_flags);
952 if(mc && mc->day)
953 mail_date(date, mc);
954 else
955 *date = '\0';
957 rv = save_fetch_append(stream, mn_m2raw(msgmap, i),
958 NULL, save_stream, save_folder, context,
959 mc ? mc->rfc822_size : 0L, flags, date, so);
961 if(flags)
962 fs_give((void **) &flags);
964 if(sp_expunge_count(stream))
965 rv = -1; /* All bets are off! */
967 if(rv == 1){
969 * Success! Count it, and if it's not already deleted and
970 * it's supposed to be, mark it to get deleted later...
972 nmsgs++;
973 if(delete){
974 rawno = mn_m2raw(msgmap, i);
975 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
976 ? mail_elt(stream, rawno) : NULL;
977 if(mc && !mc->deleted)
978 mc->sequence = 1; /* mark for later deletion */
981 else{
982 if(rv == -1)
983 cancelled = 1; /* else horrendous failure */
985 break;
989 if(pith_opt_save_size_changed_prompt)
990 (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
992 if(our_stream)
993 pine_mail_close(save_stream);
995 if(so)
996 so_give(&so);
998 if(delete && (seq = build_sequence(stream, NULL, NULL))){
999 mail_flag(stream, seq, "\\DELETED", ST_SET);
1000 fs_give((void **)&seq);
1004 ps_global->try_to_create = 0; /* reset for next time */
1005 if(!cancelled && nmsgs < mn_total_cur(msgmap)){
1006 dprint((1, "FAILED save of msg %s (c-client sequence #)\n",
1007 long2string(mn_m2raw(msgmap, mn_get_cur(msgmap)))));
1008 if((mn_total_cur(msgmap) > 1L) && nmsgs != 0){
1009 /* this shouldn't happen cause it should be all or nothing */
1010 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1011 "%ld of %ld messages saved before error occurred",
1012 nmsgs, mn_total_cur(msgmap));
1013 dprint((1, "\t%s\n", tmp_20k_buf));
1014 q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
1016 else if(mn_total_cur(msgmap) == 1){
1017 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1018 "%s to folder \"%s\" FAILED",
1019 filter ? "Filter" : "Save",
1020 strsquish(tmp_20k_buf+500, 500, folder, 35));
1021 dprint((1, "\t%s\n", tmp_20k_buf));
1022 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
1024 else{
1025 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1026 "%s of %s messages to folder \"%s\" FAILED",
1027 filter ? "Filter" : "Save", comatose(mn_total_cur(msgmap)),
1028 strsquish(tmp_20k_buf+500, 500, folder, 35));
1029 dprint((1, "\t%s\n", tmp_20k_buf));
1030 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
1034 return(nmsgs);
1038 /* Append message callback
1039 * Accepts: MAIL stream
1040 * append data package
1041 * pointer to return initial flags
1042 * pointer to return message internal date
1043 * pointer to return stringstruct of message or NIL to stop
1044 * Returns: T if success (have message or stop), NIL if error
1047 long save_fetch_append_cb(MAILSTREAM *stream, void *data, char **flags,
1048 char **date, STRING **message)
1050 unsigned long size = 0;
1051 APPENDPACKAGE *pkg = (APPENDPACKAGE *) data;
1052 MESSAGECACHE *mc;
1053 char *fetch;
1054 int rc;
1055 unsigned long raw, hlen, tlen, mlen;
1057 if(pkg->so && (pkg->msgno > 0L)) {
1058 raw = mn_m2raw(pkg->msgmap, pkg->msgno);
1059 mc = (raw > 0L && pkg->stream && raw <= pkg->stream->nmsgs)
1060 ? mail_elt(pkg->stream, raw) : NULL;
1061 if(mc){
1062 int preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
1064 size = mc->rfc822_size;
1067 * If the caller wants us to preserve the state of the
1068 * \DELETED flag then pkg->flags will be \DELETED, otherwise
1069 * let it be undeleted. Fix from Eduardo Chappa.
1071 if(pkg->flags){
1072 if(strstr(pkg->flags,"\\DELETED"))
1073 preserve_these_flags |= F_DEL;
1075 /* not used anymore */
1076 fs_give((void **) &pkg->flags);
1079 pkg->flags = flag_string(pkg->stream, raw, preserve_these_flags);
1082 if(mc && mc->day)
1083 mail_date(pkg->date, mc);
1084 else
1085 *pkg->date = '\0';
1086 if((fetch = mail_fetch_header(pkg->stream, raw, NULL, NULL, &hlen,
1087 FT_PEEK)) != NULL){
1088 if(!*pkg->date)
1089 saved_date(pkg->date, fetch);
1091 else
1092 return(0); /* fetchtext writes error */
1094 rc = MSG_EX_DELETE; /* "rc" overloaded */
1095 if(msgno_exceptions(pkg->stream, raw, NULL, &rc, 0)){
1096 char section[64];
1097 int failure = 0;
1098 BODY *body;
1099 gf_io_t pc;
1101 size = 0; /* all bets off, abort sanity test */
1102 gf_set_so_writec(&pc, pkg->so);
1104 section[0] = '\0';
1105 if(!pine_mail_fetch_structure(pkg->stream, raw, &body, 0))
1106 return(0);
1108 if(msgno_part_deleted(pkg->stream, raw, "")){
1109 tlen = 0;
1110 failure = !save_ex_replace_body(fetch, &hlen, body, pc);
1112 else
1113 failure = !(so_nputs(pkg->so, fetch, (long) hlen)
1114 && save_ex_output_body(pkg->stream, raw, section,
1115 body, &tlen, pc));
1117 gf_clear_so_writec(pkg->so);
1119 if(failure)
1120 return(0);
1122 q_status_message(SM_ORDER, 3, 3,
1123 /* TRANSLATORS: A warning to user that the message parts
1124 they deleted will not be included in the copy they
1125 are now saving to. */
1126 _("NOTE: Deleted message parts NOT included in saved copy!"));
1129 else{
1130 if(!so_nputs(pkg->so, fetch, (long) hlen))
1131 return(0);
1133 fetch = pine_mail_fetch_text(pkg->stream, raw, NULL, &tlen, FT_PEEK);
1135 if(!(fetch && so_nputs(pkg->so, fetch, tlen)))
1136 return(0);
1139 so_seek(pkg->so, 0L, 0); /* rewind just in case */
1140 mlen = hlen + tlen;
1142 if(size && mlen < size){
1143 char buf[128];
1145 if(sp_dead_stream(pkg->stream)){
1146 snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
1147 q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
1148 dprint((1, "save_fetch_append_cb: %s\n", buf));
1149 return(0);
1151 else{
1152 if(pith_opt_save_size_changed_prompt
1153 && (*pith_opt_save_size_changed_prompt)(mn_raw2m(pkg->msgmap, raw), 0) == 'y'){
1154 snprintf(buf, sizeof(buf),
1155 "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
1156 raw, size, mlen);
1157 dprint((1, "save_fetch_append_cb: %s", buf));
1158 snprintf(buf, sizeof(buf),
1159 "Message to save shrank: source msg # %ld may be saved incorrectly",
1160 mn_raw2m(pkg->msgmap, raw));
1161 if(F_OFF(F_IGNORE_SIZE, ps_global))
1162 q_status_message(SM_ORDER, 0, 3, buf);
1164 else{
1165 snprintf(buf, sizeof(buf),
1166 "Message to save shrank: raw msg # %ld went from announced size %ld to actual size %ld",
1167 raw, size, mlen);
1168 dprint((1, "save_fetch_append_cb: %s", buf));
1169 return(0);
1174 INIT(pkg->msg, mail_string, (void *)so_text(pkg->so), mlen);
1175 *message = pkg->msg;
1176 /* Next message */
1177 pkg->msgno = mn_next_cur(pkg->msgmap);
1179 else /* No more messages */
1180 *message = NIL;
1182 *flags = pkg->flags;
1183 *date = (pkg->date && *pkg->date) ? pkg->date : NIL;
1184 return LONGT; /* Return success */
1188 /*----------------------------------------------------------------------
1189 FETCH an rfc822 message header and body and APPEND to destination folder
1191 Args:
1194 Result:
1196 ----*/
1198 save_fetch_append(MAILSTREAM *stream, long int raw, char *sect,
1199 MAILSTREAM *save_stream, char *save_folder, CONTEXT_S *context,
1200 long unsigned int size, char *flags, char *date, STORE_S *so)
1202 int rc, rv, old_imap_server = 0;
1203 long j;
1204 char *fetch;
1205 unsigned long hlen, tlen, mlen;
1206 STRING msg;
1208 if((fetch = mail_fetch_header(stream, raw, sect, NULL, &hlen, FT_PEEK)) != NULL){
1210 * If there's no date string, then caller found the
1211 * MESSAGECACHE for this message element didn't already have it.
1212 * So, parse the "internal date" by hand since fetchstructure
1213 * hasn't been called yet for this particular message, and
1214 * we don't want to call it now just to get the date since
1215 * the full header has what we want. Likewise, don't even
1216 * think about calling mail_fetchfast either since it also
1217 * wants to load mc->rfc822_size (which we could actually
1218 * use but...), which under some drivers is *very*
1219 * expensive to acquire (can you say NNTP?)...
1221 if(!*date)
1222 saved_date(date, fetch);
1224 else
1225 return(0); /* fetchtext writes error */
1227 rc = MSG_EX_DELETE; /* "rc" overloaded */
1228 if(msgno_exceptions(stream, raw, NULL, &rc, 0)){
1229 char section[64];
1230 int failure = 0;
1231 BODY *body;
1232 gf_io_t pc;
1234 size = 0; /* all bets off, abort sanity test */
1235 gf_set_so_writec(&pc, so);
1237 if(sect && *sect){
1238 snprintf(section, sizeof(section), "%s.1", sect);
1239 if(!(body = mail_body(stream, raw, (unsigned char *) section)))
1240 return(0);
1242 else{
1243 section[0] = '\0';
1244 if(!pine_mail_fetch_structure(stream, raw, &body, 0))
1245 return(0);
1249 * Walk the MIME structure looking for exceptional segments,
1250 * writing them in the requested fashion.
1252 * First, though, check for the easy case...
1254 if(msgno_part_deleted(stream, raw, sect ? sect : "")){
1255 tlen = 0;
1256 failure = !save_ex_replace_body(fetch, &hlen, body, pc);
1258 else{
1260 * Otherwise, roll up your sleeves and get to work...
1261 * start by writing msg header and then the processed body
1263 failure = !(so_nputs(so, fetch, (long) hlen)
1264 && save_ex_output_body(stream, raw, section,
1265 body, &tlen, pc));
1268 gf_clear_so_writec(so);
1270 if(failure)
1271 return(0);
1273 q_status_message(SM_ORDER, 3, 3,
1274 _("NOTE: Deleted message parts NOT included in saved copy!"));
1277 else{
1278 /* First, write the header we just fetched... */
1279 if(!so_nputs(so, fetch, (long) hlen))
1280 return(0);
1282 old_imap_server = is_imap_stream(stream) && !modern_imap_stream(stream);
1284 /* Second, go fetch the corresponding text... */
1285 fetch = pine_mail_fetch_text(stream, raw, sect, &tlen,
1286 !old_imap_server ? FT_PEEK : 0);
1289 * Special handling in case we're fetching a Message/rfc822
1290 * segment and we're talking to an old server...
1292 if(fetch && *fetch == '\0' && sect && (hlen + tlen) != size){
1293 so_seek(so, 0L, 0);
1294 fetch = pine_mail_fetch_body(stream, raw, sect, &tlen, 0L);
1298 * Pre IMAP4 servers can't do a non-peeking mail_fetch_text,
1299 * so if the message we are saving from was originally unseen,
1300 * we have to change it back to unseen. Flags contains the
1301 * string "SEEN" if the original message was seen.
1303 if(old_imap_server && (!flags || !srchstr(flags,"SEEN"))){
1304 char seq[20];
1306 strncpy(seq, long2string(raw), sizeof(seq));
1307 seq[sizeof(seq)-1] = '\0';
1308 mail_flag(stream, seq, "\\SEEN", 0);
1311 /* If fetch succeeded, write the result */
1312 if(!(fetch && so_nputs(so, fetch, tlen)))
1313 return(0);
1316 so_seek(so, 0L, 0); /* rewind just in case */
1319 * Set up a c-client string driver so we can hand the
1320 * collected text down to mail_append.
1322 * NOTE: We only test the size if and only if we already
1323 * have it. See, in some drivers, especially under
1324 * dos, its too expensive to get the size (full
1325 * header and body text fetch plus MIME parse), so
1326 * we only verify the size if we already know it.
1328 mlen = hlen + tlen;
1330 if(size && mlen < size){
1331 char buf[128];
1333 if(sp_dead_stream(stream)){
1334 snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
1335 q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
1336 dprint((1, "save_fetch_append: %s", buf));
1337 return(0);
1339 else{
1340 if(pith_opt_save_size_changed_prompt
1341 && (*pith_opt_save_size_changed_prompt)(mn_raw2m(sp_msgmap(stream), raw), 0) == 'y'){
1342 snprintf(buf, sizeof(buf),
1343 "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
1344 raw, size, mlen);
1345 dprint((1, "save_fetch_append: %s", buf));
1346 snprintf(buf, sizeof(buf),
1347 "Message to save shrank: source msg # %ld may be saved incorrectly",
1348 mn_raw2m(sp_msgmap(stream), raw));
1349 q_status_message(SM_ORDER, 0, 3, buf);
1351 else{
1352 snprintf(buf, sizeof(buf),
1353 "Message to save shrank: source raw msg # %ld went from announced size %ld to actual size %ld",
1354 raw, size, mlen);
1355 dprint((1, "save_fetch_append: %s", buf));
1356 return(0);
1361 INIT(&msg, mail_string, (void *)so_text(so), mlen);
1363 rc = 0;
1364 while(!(rv = (int) context_append_full(context, save_stream,
1365 save_folder, flags,
1366 *date ? date : NULL,
1367 &msg))){
1368 if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
1369 break; /* c-client returned error? */
1371 if(context && (context->use & CNTXT_INCMNG)
1372 && context_isambig(save_folder)){
1373 q_status_message(SM_ORDER, 3, 5,
1374 _("Can only save to existing folders in Incoming Collection"));
1375 break;
1378 ps_global->try_to_create = 0; /* reset for next time */
1379 if((j = create_for_save(context, save_folder)) < 1){
1380 if(j < 0)
1381 rv = -1; /* user cancelled */
1383 break;
1386 SETPOS((&msg), 0L); /* reset string driver */
1389 return(rv);
1394 * save_ex_replace_body -
1396 * NOTE : hlen points to a cell that has the byte count of "hdr" on entry
1397 * *BUT* which is to contain the count of written bytes on exit
1400 save_ex_replace_body(char *hdr, long unsigned int *hlen, struct mail_bodystruct *body, gf_io_t pc)
1402 unsigned long len;
1405 * "X-" out the given MIME headers unless they're
1406 * the same as we're going to substitute...
1408 if(body->type == TYPETEXT
1409 && (!body->subtype || !strucmp(body->subtype, "plain"))
1410 && body->encoding == ENC7BIT){
1411 if(!gf_nputs(hdr, *hlen, pc)) /* write out header */
1412 return(0);
1414 else{
1415 int bol = 1;
1418 * write header, "X-"ing out transport headers bothersome to
1419 * software but potentially useful to the human recipient...
1421 for(len = *hlen; len; len--){
1422 if(bol){
1423 unsigned long n;
1425 bol = 0;
1426 if(save_ex_mask_types(hdr, &n, pc))
1427 *hlen += n; /* add what we inserted */
1428 else
1429 break;
1432 if(*hdr == '\015' && *(hdr+1) == '\012'){
1433 bol++; /* remember beginning of line */
1434 len--; /* account for LF */
1435 if(gf_nputs(hdr, 2, pc))
1436 hdr += 2;
1437 else
1438 break;
1440 else if(!(*pc)(*hdr++))
1441 break;
1444 if(len) /* bytes remain! */
1445 return(0);
1448 /* Now, blat out explanatory text as the body... */
1449 if(save_ex_explain_body(body, &len, pc)){
1450 *hlen += len;
1451 return(1);
1453 else
1454 return(0);
1459 save_ex_output_body(MAILSTREAM *stream, long int raw, char *section,
1460 struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
1462 char *txtp, newsect[128];
1463 unsigned long ilen;
1465 txtp = mail_fetch_mime(stream, raw, section, len, FT_PEEK);
1467 if(msgno_part_deleted(stream, raw, section))
1468 return(save_ex_replace_body(txtp, len, body, pc));
1470 if(body->type == TYPEMULTIPART){
1471 #define BOUNDARYLEN 128
1472 char *subsect, boundary[BOUNDARYLEN];
1473 int n, blen;
1474 PART *part = body->nested.part;
1475 PARAMETER *param;
1477 /* Locate supplied multipart boundary */
1478 for (param = body->parameter; param; param = param->next)
1479 if (!strucmp(param->attribute, "boundary")){ /* BOUNDARYLEN == sizeof(boundary) */
1480 snprintf(boundary, sizeof(boundary), "--%.*s\015\012", BOUNDARYLEN-10,
1481 param->value);
1482 blen = strlen(boundary);
1483 break;
1486 if(!param){
1487 q_status_message(SM_ORDER|SM_DING, 3, 3, "Missing MIME boundary");
1488 return(0);
1492 * BUG: if multi/digest and a message deleted (which we'll
1493 * change to text/plain), we need to coerce this composite
1494 * type to multi/mixed !!
1496 if(!gf_nputs(txtp, *len, pc)) /* write MIME header */
1497 return(0);
1499 /* Prepare to specify sub-sections */
1500 strncpy(newsect, section, sizeof(newsect));
1501 newsect[sizeof(newsect)-1] = '\0';
1502 subsect = &newsect[n = strlen(newsect)];
1503 if(n > 2 && !strcmp(&newsect[n-2], ".0"))
1504 subsect--;
1505 else if(n){
1506 if((subsect-newsect) < sizeof(newsect))
1507 *subsect++ = '.';
1510 n = 1;
1511 do { /* for each part */
1512 strncpy(subsect, int2string(n++), sizeof(newsect)-(subsect-newsect));
1513 newsect[sizeof(newsect)-1] = '\0';
1514 if(gf_puts(boundary, pc)
1515 && save_ex_output_body(stream, raw, newsect,
1516 &part->body, &ilen, pc))
1517 *len += blen + ilen;
1518 else
1519 return(0);
1521 while ((part = part->next) != NULL); /* until done */
1522 /* BOUNDARYLEN = sizeof(boundary) */
1523 snprintf(boundary, sizeof(boundary), "--%.*s--\015\012", BOUNDARYLEN-10,param->value);
1524 *len += blen + 2;
1525 return(gf_puts(boundary, pc));
1528 /* Start by writing the part's MIME header */
1529 if(!gf_nputs(txtp, *len, pc))
1530 return(0);
1532 if(body->type == TYPEMESSAGE
1533 && (!body->subtype || !strucmp(body->subtype, "rfc822"))){
1534 /* write RFC 822 message's header */
1535 if((txtp = mail_fetch_header(stream,raw,section,NULL,&ilen,FT_PEEK))
1536 && gf_nputs(txtp, ilen, pc))
1537 *len += ilen;
1538 else
1539 return(0);
1541 /* then go deal with its body parts */
1542 snprintf(newsect, sizeof(newsect), "%.10s%s%s", section, section ? "." : "",
1543 (body->nested.msg->body->type == TYPEMULTIPART) ? "0" : "1");
1544 if(save_ex_output_body(stream, raw, newsect,
1545 body->nested.msg->body, &ilen, pc)){
1546 *len += ilen;
1547 return(1);
1550 return(0);
1553 /* Write corresponding body part */
1554 if((txtp = pine_mail_fetch_body(stream, raw, section, &ilen, FT_PEEK))
1555 && gf_nputs(txtp, (long) ilen, pc) && gf_puts("\015\012", pc)){
1556 *len += ilen + 2;
1557 return(1);
1560 return(0);
1564 /*----------------------------------------------------------------------
1565 Mask off any header entries we don't want to show up in exceptional saves
1567 Args: hdr -- pointer to start of a header line
1568 pc -- function to write the prefix
1570 ----*/
1572 save_ex_mask_types(char *hdr, long unsigned int *len, gf_io_t pc)
1574 char *s = NULL;
1576 if(!struncmp(hdr, "content-type:", 13))
1577 s = "Content-Type: Text/Plain; charset=UTF-8\015\012X-";
1578 else if(!struncmp(hdr, "content-description:", 20))
1579 s = "Content-Description: Deleted Attachment\015\012X-";
1580 else if(!struncmp(hdr, "content-transfer-encoding:", 26)
1581 || !struncmp(hdr, "content-disposition:", 20))
1582 s = "X-";
1584 return((*len = s ? strlen(s) : 0) ? gf_puts(s, pc) : 1);
1589 save_ex_explain_body(struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
1591 unsigned long ilen;
1592 char **blurbp;
1593 static char *blurb[] = {
1594 N_("The following attachment was DELETED when this message was saved:"),
1595 NULL
1598 *len = 0;
1599 for(blurbp = blurb; *blurbp; blurbp++)
1600 if(save_ex_output_line(_(*blurbp), &ilen, pc))
1601 *len += ilen;
1602 else
1603 return(0);
1605 if(!save_ex_explain_parts(body, 0, &ilen, pc))
1606 return(0);
1608 *len += ilen;
1609 return(1);
1614 save_ex_explain_parts(struct mail_bodystruct *body, int depth, long unsigned int *len, gf_io_t pc)
1616 char *tmp, namebuf[MAILTMPLEN], descbuf[MAILTMPLEN];
1617 unsigned long ilen, tmplen;
1618 char *name = parameter_val(body->parameter, "name");
1620 if(!name){
1621 if(body->disposition.type &&
1622 !strucmp(body->disposition.type, "ATTACHMENT"))
1623 name = parameter_val(body->disposition.parameter, "filename");
1625 if(name)
1626 rfc1522_decode_to_utf8((unsigned char *)namebuf, sizeof(namebuf), name);
1628 if(body->description && *body->description)
1629 rfc1522_decode_to_utf8((unsigned char *)descbuf, sizeof(descbuf), body->description);
1631 ilen = 0;
1632 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
1633 PART *part = body->nested.part; /* first body part */
1635 *len = 0;
1636 if(body->description && *body->description){
1637 tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1
1638 + strlen(body->subtype ? body->subtype : "Unknown")
1639 + strlen(name ? " (Name=\"" : "")
1640 + strlen(name ? namebuf : "")
1641 + strlen(name ? "\"" : "") + strlen(_(" segment described as "))
1642 + strlen(descbuf) + strlen(_(" containing:")) + 1;
1643 tmp = fs_get((tmplen + 1)*sizeof(char));
1644 sprintf(tmp, "%s%s/%s%s%s%s%s%s%s", _("A "),
1645 body_type_names(body->type), body->subtype ? body->subtype : "Unknown",
1646 name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "",
1647 _(" segment described as "), descbuf, _(" containing:"));
1648 if(!save_ex_output_text(tmp, depth, len, pc))
1649 return(0);
1651 else{
1652 tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1
1653 + strlen(body->subtype ? body->subtype : "Unknown")
1654 + strlen(name ? " (Name=\"" : "")
1655 + strlen(name ? namebuf : "")
1656 + strlen(name ? "\"" : "") + strlen(_(" segment containing:")) + 1;
1657 tmp = fs_get((tmplen + 1)*sizeof(char));
1658 sprintf(tmp, "%s%s/%s%s%s%s%s", _("A "),
1659 body_type_names(body->type), body->subtype ? body->subtype : "Unknown",
1660 name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "",
1661 _(" segment containing:"));
1664 if(save_ex_output_text(tmp, depth, &ilen, pc))
1665 *len += ilen;
1666 else
1667 return(0);
1669 depth++;
1670 do /* for each part */
1671 if(save_ex_explain_parts(&part->body, depth, &ilen, pc))
1672 *len += ilen;
1673 else
1674 return(0);
1675 while ((part = part->next) != NULL); /* until done */
1677 else{
1678 char *comatosep = comatose((body->encoding == ENCBASE64)
1679 ? ((body->size.bytes * 3)/4)
1680 : body->size.bytes);
1681 tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1
1682 + strlen(body->subtype && *body->subtype ? body->subtype : "Unknown")
1683 + strlen(name ? " (Name=\"" : "")
1684 + strlen(name ? namebuf : "")
1685 + strlen(name ? "\"" : "") + strlen(_(" segment of about "))
1686 + strlen(comatosep) + strlen(_(" bytes")) + 1
1687 + strlen(body->description && *body->description ? _(" described as \"") : "")
1688 + strlen(body->description && *body->description ? descbuf : "")
1689 + strlen(body->description && *body->description ? "\"": "")
1690 + 1;
1691 tmp = fs_get((tmplen + 1)*sizeof(char));
1692 sprintf(tmp, "%s%s/%s%s%s%s%s%s%s%s%s%s%s",
1693 _("A "),
1694 body_type_names(body->type),
1695 body->subtype && *body->subtype ? body->subtype : "Unknown",
1696 name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "",
1697 _(" segment of about "),
1698 comatosep,
1699 _(" bytes"),
1700 body->description && *body->description ? "," : ".",
1701 body->description && *body->description ? " described as \"" : "",
1702 body->description && *body->description ? descbuf : "",
1703 body->description && *body->description ? "\"" : "");
1705 if(save_ex_output_text(tmp, depth, &ilen, pc))
1706 *len += ilen;
1707 else
1708 return(0);
1711 return(1);
1714 /* save output one line at the time. This uses save_ex_output_line, which
1715 * only allows 68 characters of text per line, so what we do is to get justify
1716 * the input text to 68 characters long. If it is not possible to do this,
1717 * we chop the line at 68 characters and move the remaining text to the next
1718 * line.
1721 save_ex_output_text(char *text, int depth, unsigned long *len, gf_io_t pc)
1723 int starti, i, j, startpos, pos, rv;
1724 int line_len = 68 - depth;
1725 char tmp[100]; /* a number bigger than 68, we justify text here. */
1726 char *s;
1727 unsigned long ilen;
1729 rv = 0;
1730 pos = 0;
1731 do {
1732 ilen = 0;
1733 sprintf(tmp, "%*.*s", depth, depth, " ");
1734 startpos = pos;
1735 for(starti = i = depth; i < 68 && text[pos] != '\0'; i++, pos++){
1736 tmp[i] = text[pos];
1737 if(tmp[i] == ' '){ /* save when we reach a space */
1738 starti = i;
1739 startpos = pos;
1742 tmp[i] = '\0';
1743 if(i == 68){
1744 if(text[pos] != '\0' && text[pos] != ' '){ /* if we are not at the end of a word */
1745 if(startpos < pos && starti != depth){ /* rewind */
1746 tmp[starti] = '\0';
1747 pos = startpos;
1751 rv += save_ex_output_line(tmp, &ilen, pc);
1752 *len += ilen;
1754 while (text[pos] != '\0');
1755 return rv;
1759 save_ex_output_line(char *line, long unsigned int *len, gf_io_t pc)
1761 snprintf(tmp_20k_buf, SIZEOF_20KBUF, " [ %-*.*s ]\015\012", 68, 68, line);
1762 *len = strlen(tmp_20k_buf);
1763 return(gf_puts(tmp_20k_buf, pc));
1767 /*----------------------------------------------------------------------
1768 Save() helper function to create canonical date string from given header
1770 Args: date -- buf to receive canonical date string
1771 header -- rfc822 header to fish date string from
1773 Result: date filled with canonicalized date in header, or null string
1774 ----*/
1775 void
1776 saved_date(char *date, char *header)
1778 char *d, *p, c;
1779 MESSAGECACHE elt;
1781 *date = '\0';
1782 if((toupper((unsigned char)(*(d = header)))
1783 == 'D' && !strncmp(d, "date:", 5))
1784 || (d = srchstr(header, "\015\012date:"))){
1785 for(d += 7; *d == ' '; d++)
1786 ; /* skip white space */
1788 if((p = strstr(d, "\015\012")) != NULL){
1789 for(; p > d && *p == ' '; p--)
1790 ; /* skip white space */
1792 c = *p;
1793 *p = '\0'; /* tie off internal date */
1796 if(mail_parse_date(&elt, (unsigned char *) d)) /* normalize it */
1797 mail_date(date, &elt);
1799 if(p) /* restore header */
1800 *p = c;
1805 MAILSTREAM *
1806 save_msg_stream(CONTEXT_S *context, char *folder, int *we_opened)
1808 char tmp[MAILTMPLEN];
1809 MAILSTREAM *save_stream = NULL;
1811 if(IS_REMOTE(context_apply(tmp, context, folder, sizeof(tmp)))
1812 && !(save_stream = sp_stream_get(tmp, SP_MATCH))
1813 && !(save_stream = sp_stream_get(tmp, SP_SAME))){
1814 if((save_stream = context_open(context, NULL, folder,
1815 OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
1816 NULL)) != NULL)
1817 *we_opened = 1;
1820 return(save_stream);
1824 /*----------------------------------------------------------------------
1825 Offer to create a non-existent folder to copy message[s] into
1827 Args: context -- context to create folder in
1828 folder -- name of folder to create
1830 Result: 0 if create failed (c-client writes error)
1831 1 if create successful
1832 -1 if user declines to create folder
1833 ----*/
1835 create_for_save(CONTEXT_S *context, char *folder)
1837 int ret;
1839 if(pith_opt_save_create_prompt
1840 && (ret = (*pith_opt_save_create_prompt)(context, folder, 1)) != 1)
1841 return(ret);
1843 return(context_create(context, NULL, folder));