* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / pith / save.c
blobe1c13f7f62e1e15fe894436a6e532229fd04c8d6
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/save.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/mimedesc.h"
20 #include "../pith/filter.h"
21 #include "../pith/context.h"
22 #include "../pith/folder.h"
23 #include "../pith/copyaddr.h"
24 #include "../pith/mailview.h"
25 #include "../pith/mailcmd.h"
26 #include "../pith/bldaddr.h"
27 #include "../pith/flag.h"
28 #include "../pith/status.h"
29 #include "../pith/ablookup.h"
30 #include "../pith/news.h"
31 #include "../pith/util.h"
32 #include "../pith/reply.h"
33 #include "../pith/sequence.h"
34 #include "../pith/stream.h"
35 #include "../pith/options.h"
39 * Internal prototypes
41 int save_ex_replace_body(char *, unsigned long *,BODY *,gf_io_t);
42 int save_ex_output_body(MAILSTREAM *, long, char *, BODY *, unsigned long *, gf_io_t);
43 int save_ex_mask_types(char *, unsigned long *, gf_io_t);
44 int save_ex_explain_body(BODY *, unsigned long *, gf_io_t);
45 int save_ex_explain_parts(BODY *, int, unsigned long *, gf_io_t);
46 int save_ex_output_line(char *, unsigned long *, gf_io_t);
47 int save_ex_output_text(char *, int, unsigned long *, gf_io_t);
51 * pith hook
53 int (*pith_opt_save_create_prompt)(CONTEXT_S *, char *, int);
54 int (*pith_opt_save_size_changed_prompt)(long, int);
58 /*----------------------------------------------------------------------
59 save_get_default - return default folder name for saving
60 ----*/
61 char *
62 save_get_default(struct pine *state, ENVELOPE *e, long int rawno,
63 char *section, CONTEXT_S **cntxt)
65 int context_was_set;
67 if(!cntxt)
68 return("");
70 context_was_set = ((*cntxt) != NULL);
72 /* start with the default save context */
73 if(!(*cntxt)
74 && ((*cntxt) = default_save_context(state->context_list)) == NULL)
75 (*cntxt) = state->context_list;
77 if(!e || ps_global->save_msg_rule == SAV_RULE_LAST
78 || ps_global->save_msg_rule == SAV_RULE_DEFLT){
79 if(ps_global->save_msg_rule == SAV_RULE_LAST && ps_global->last_save_context){
80 if(!context_was_set)
81 (*cntxt) = ps_global->last_save_context;
83 else{
84 strncpy(ps_global->last_save_folder,
85 ps_global->VAR_DEFAULT_SAVE_FOLDER,
86 sizeof(ps_global->last_save_folder)-1);
87 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
90 * If the user entered "inbox" as their default save folder it is very
91 * likely they meant the real inbox, not the inbox in the primary collection.
93 if(!context_was_set
94 && !strucmp(ps_global->inbox_name, ps_global->last_save_folder))
95 (*cntxt) = state->context_list;
98 else{
99 save_get_fldr_from_env(ps_global->last_save_folder,
100 sizeof(ps_global->last_save_folder),
101 e, state, rawno, section);
102 /* somebody expunged current message */
103 if(sp_expunge_count(ps_global->mail_stream))
104 return(NULL);
107 return(ps_global->last_save_folder);
112 /*----------------------------------------------------------------------
113 Grope through envelope to find default folder name to save to
115 Args: fbuf -- Buffer to return result in
116 nfbuf -- Size of fbuf
117 e -- The envelope to look in
118 state -- Usual pine state
119 rawmsgno -- Raw c-client sequence number of message
120 section -- Mime section of header data (for message/rfc822)
122 Result: The appropriate default folder name is copied into fbuf.
123 ----*/
124 void
125 save_get_fldr_from_env(char *fbuf, int nfbuf, ENVELOPE *e, struct pine *state,
126 long int rawmsgno, char *section)
128 char fakedomain[2];
129 ADDRESS *tmp_adr = NULL;
130 char buf[MAX(MAXFOLDER,MAX_NICKNAME) + 1];
131 char *bufp = NULL;
132 char *folder_name = NULL;
133 static char botch[] = "programmer botch, unknown message save rule";
134 unsigned save_msg_rule;
136 if(!e)
137 return;
139 /* copy this because we might change it below */
140 save_msg_rule = state->save_msg_rule;
142 /* first get the relevant address to base the folder name on */
143 switch(save_msg_rule){
144 case SAV_RULE_FROM:
145 case SAV_RULE_NICK_FROM:
146 case SAV_RULE_NICK_FROM_DEF:
147 case SAV_RULE_FCC_FROM:
148 case SAV_RULE_FCC_FROM_DEF:
149 case SAV_RULE_RN_FROM:
150 case SAV_RULE_RN_FROM_DEF:
151 tmp_adr = e->from ? copyaddr(e->from)
152 : e->sender ? copyaddr(e->sender) : NULL;
153 break;
155 case SAV_RULE_SENDER:
156 case SAV_RULE_NICK_SENDER:
157 case SAV_RULE_NICK_SENDER_DEF:
158 case SAV_RULE_FCC_SENDER:
159 case SAV_RULE_FCC_SENDER_DEF:
160 case SAV_RULE_RN_SENDER:
161 case SAV_RULE_RN_SENDER_DEF:
162 tmp_adr = e->sender ? copyaddr(e->sender)
163 : e->from ? copyaddr(e->from) : NULL;
164 break;
166 case SAV_RULE_REPLYTO:
167 case SAV_RULE_NICK_REPLYTO:
168 case SAV_RULE_NICK_REPLYTO_DEF:
169 case SAV_RULE_FCC_REPLYTO:
170 case SAV_RULE_FCC_REPLYTO_DEF:
171 case SAV_RULE_RN_REPLYTO:
172 case SAV_RULE_RN_REPLYTO_DEF:
173 tmp_adr = e->reply_to ? copyaddr(e->reply_to)
174 : e->from ? copyaddr(e->from)
175 : e->sender ? copyaddr(e->sender) : NULL;
176 break;
178 case SAV_RULE_RECIP:
179 case SAV_RULE_NICK_RECIP:
180 case SAV_RULE_NICK_RECIP_DEF:
181 case SAV_RULE_FCC_RECIP:
182 case SAV_RULE_FCC_RECIP_DEF:
183 case SAV_RULE_RN_RECIP:
184 case SAV_RULE_RN_RECIP_DEF:
185 /* news */
186 if(state->mail_stream && IS_NEWS(state->mail_stream)){
187 char *tmp_a_string, *ng_name;
189 fakedomain[0] = '@';
190 fakedomain[1] = '\0';
192 /* find the news group name */
193 if((ng_name = strstr(state->mail_stream->mailbox,"#news")) != NULL)
194 ng_name += 6;
195 else
196 ng_name = state->mail_stream->mailbox; /* shouldn't happen */
198 /* copy this string so rfc822_parse_adrlist can't blast it */
199 tmp_a_string = cpystr(ng_name);
200 /* make an adr */
201 rfc822_parse_adrlist(&tmp_adr, tmp_a_string, fakedomain);
202 fs_give((void **)&tmp_a_string);
203 if(tmp_adr && tmp_adr->host && tmp_adr->host[0] == '@')
204 tmp_adr->host[0] = '\0';
206 /* not news */
207 else{
208 static char *fields[] = {"Resent-To", NULL};
209 char *extras, *values[sizeof(fields)/sizeof(fields[0])];
211 extras = pine_fetchheader_lines(state->mail_stream, rawmsgno,
212 section, fields);
213 if(extras){
214 long i;
216 memset(values, 0, sizeof(fields));
217 simple_header_parse(extras, fields, values);
218 fs_give((void **)&extras);
220 for(i = 0; i < sizeof(fields)/sizeof(fields[0]); i++)
221 if(values[i]){
222 if(tmp_adr) /* take last matching value */
223 mail_free_address(&tmp_adr);
225 /* build a temporary address list */
226 fakedomain[0] = '@';
227 fakedomain[1] = '\0';
228 rfc822_parse_adrlist(&tmp_adr, values[i], fakedomain);
229 fs_give((void **)&values[i]);
233 if(!tmp_adr)
234 tmp_adr = e->to ? copyaddr(e->to) : NULL;
237 break;
239 default:
240 alpine_panic(botch);
241 break;
244 /* For that address, lookup the fcc or nickname from address book */
245 switch(save_msg_rule){
246 case SAV_RULE_NICK_FROM:
247 case SAV_RULE_NICK_SENDER:
248 case SAV_RULE_NICK_REPLYTO:
249 case SAV_RULE_NICK_RECIP:
250 case SAV_RULE_FCC_FROM:
251 case SAV_RULE_FCC_SENDER:
252 case SAV_RULE_FCC_REPLYTO:
253 case SAV_RULE_FCC_RECIP:
254 case SAV_RULE_NICK_FROM_DEF:
255 case SAV_RULE_NICK_SENDER_DEF:
256 case SAV_RULE_NICK_REPLYTO_DEF:
257 case SAV_RULE_NICK_RECIP_DEF:
258 case SAV_RULE_FCC_FROM_DEF:
259 case SAV_RULE_FCC_SENDER_DEF:
260 case SAV_RULE_FCC_REPLYTO_DEF:
261 case SAV_RULE_FCC_RECIP_DEF:
262 switch(save_msg_rule){
263 case SAV_RULE_NICK_FROM:
264 case SAV_RULE_NICK_SENDER:
265 case SAV_RULE_NICK_REPLYTO:
266 case SAV_RULE_NICK_RECIP:
267 case SAV_RULE_NICK_FROM_DEF:
268 case SAV_RULE_NICK_SENDER_DEF:
269 case SAV_RULE_NICK_REPLYTO_DEF:
270 case SAV_RULE_NICK_RECIP_DEF:
271 bufp = get_nickname_from_addr(tmp_adr, buf, sizeof(buf));
272 break;
274 case SAV_RULE_FCC_FROM:
275 case SAV_RULE_FCC_SENDER:
276 case SAV_RULE_FCC_REPLYTO:
277 case SAV_RULE_FCC_RECIP:
278 case SAV_RULE_FCC_FROM_DEF:
279 case SAV_RULE_FCC_SENDER_DEF:
280 case SAV_RULE_FCC_REPLYTO_DEF:
281 case SAV_RULE_FCC_RECIP_DEF:
282 bufp = get_fcc_from_addr(tmp_adr, buf, sizeof(buf));
283 break;
286 if(bufp && *bufp){
287 istrncpy(fbuf, bufp, nfbuf - 1);
288 fbuf[nfbuf - 1] = '\0';
290 else
291 /* fall back to non-nick/non-fcc version of rule */
292 switch(save_msg_rule){
293 case SAV_RULE_NICK_FROM:
294 case SAV_RULE_FCC_FROM:
295 save_msg_rule = SAV_RULE_FROM;
296 break;
298 case SAV_RULE_NICK_SENDER:
299 case SAV_RULE_FCC_SENDER:
300 save_msg_rule = SAV_RULE_SENDER;
301 break;
303 case SAV_RULE_NICK_REPLYTO:
304 case SAV_RULE_FCC_REPLYTO:
305 save_msg_rule = SAV_RULE_REPLYTO;
306 break;
308 case SAV_RULE_NICK_RECIP:
309 case SAV_RULE_FCC_RECIP:
310 save_msg_rule = SAV_RULE_RECIP;
311 break;
313 default:
314 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
315 fbuf[nfbuf - 1] = '\0';
316 break;
319 break;
322 /* Realname */
323 switch(save_msg_rule){
324 case SAV_RULE_RN_FROM_DEF:
325 case SAV_RULE_RN_FROM:
326 case SAV_RULE_RN_SENDER_DEF:
327 case SAV_RULE_RN_SENDER:
328 case SAV_RULE_RN_RECIP_DEF:
329 case SAV_RULE_RN_RECIP:
330 case SAV_RULE_RN_REPLYTO_DEF:
331 case SAV_RULE_RN_REPLYTO:
332 /* Fish out the realname */
333 if(tmp_adr && tmp_adr->personal && tmp_adr->personal[0])
334 folder_name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
335 SIZEOF_20KBUF, tmp_adr->personal);
337 if(folder_name && folder_name[0]){
338 istrncpy(fbuf, folder_name, nfbuf - 1);
339 fbuf[nfbuf - 1] = '\0';
341 else{ /* fall back to other behaviors */
342 switch(save_msg_rule){
343 case SAV_RULE_RN_FROM:
344 save_msg_rule = SAV_RULE_FROM;
345 break;
347 case SAV_RULE_RN_SENDER:
348 save_msg_rule = SAV_RULE_SENDER;
349 break;
351 case SAV_RULE_RN_RECIP:
352 save_msg_rule = SAV_RULE_RECIP;
353 break;
355 case SAV_RULE_RN_REPLYTO:
356 save_msg_rule = SAV_RULE_REPLYTO;
357 break;
359 default:
360 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
361 fbuf[nfbuf - 1] = '\0';
362 break;
366 break;
369 /* get the username out of the mailbox for this address */
370 switch(save_msg_rule){
371 case SAV_RULE_FROM:
372 case SAV_RULE_SENDER:
373 case SAV_RULE_REPLYTO:
374 case SAV_RULE_RECIP:
376 * Fish out the user's name from the mailbox portion of
377 * the address and put it in folder.
379 folder_name = (tmp_adr && tmp_adr->mailbox && tmp_adr->mailbox[0])
380 ? tmp_adr->mailbox : NULL;
381 if(!get_uname(folder_name, fbuf, nfbuf)){
382 istrncpy(fbuf, ps_global->VAR_DEFAULT_SAVE_FOLDER, nfbuf - 1);
383 fbuf[nfbuf - 1] = '\0';
386 break;
389 if(tmp_adr)
390 mail_free_address(&tmp_adr);
394 /*----------------------------------------------------------------------
395 Do the work of actually saving messages to a folder
397 Args: state -- pine state struct (for stream pointers)
398 stream -- source stream, which msgmap refers to
399 context -- context to interpret name in if not fully qualified
400 folder -- The folder to save the message in
401 msgmap -- message map of currently selected messages
402 flgs -- Possible bits are
403 SV_DELETE - delete after saving
404 SV_FOR_FILT - called from filtering function, not save
405 SV_FIX_DELS - remove Del mark before saving
406 SV_INBOXWOCNTXT - "inbox" is interpreted without context making
407 it the one-true inbox instead
409 Result: Returns number of messages saved
411 Note: There's a bit going on here; temporary clearing of deleted flags
412 since they are *not* preserved, picking or creating the stream for
413 copy or append, and dealing with errors...
414 We try to preserve user keywords by setting them in the destination.
415 ----*/
416 long
417 save(struct pine *state, MAILSTREAM *stream, CONTEXT_S *context, char *folder,
418 MSGNO_S *msgmap, int flgs)
420 int rv, rc, j, our_stream = 0, cancelled = 0;
421 int delete, filter, k, worry_about_keywords = 0;
422 char *save_folder, *seq, *flags = NULL, date[64], tmp[MAILTMPLEN];
423 long i, nmsgs, rawno;
424 size_t len = 0;
425 STORE_S *so = NULL;
426 MAILSTREAM *save_stream = NULL;
427 MESSAGECACHE *mc;
429 delete = flgs & SV_DELETE;
430 filter = flgs & SV_FOR_FILT;
432 if(strucmp(folder, state->inbox_name) == 0 && flgs & SV_INBOXWOCNTXT){
433 save_folder = state->VAR_INBOX_PATH;
434 context = NULL;
436 else
437 save_folder = folder;
440 * Because the COPY/APPEND command doesn't always create keywords when they
441 * aren't already defined in a mailbox, we need to ensure that the keywords
442 * exist in the destination (are defined and settable) before we do the copies.
443 * Here's what the code is doing
445 * If we have keywords set in the source messages
446 * Add a dummy message to destination mailbox
448 * for each keyword that is set in the set of messages we're saving
449 * set the keyword in that message (thus creating it)
451 * remember deleted messages
452 * undelete them
453 * delete dummy message
454 * expunge
455 * delete remembered messages
457 * After that the assumption is that the keywords will be saved by a
458 * COPY command. We need to set the flags string ourself for appends.
461 /* are any keywords set in the source messages? */
462 for(i = mn_first_cur(msgmap); !worry_about_keywords && i > 0L; i = mn_next_cur(msgmap)){
463 rawno = mn_m2raw(msgmap, i);
464 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
465 ? mail_elt(stream, rawno) : NULL;
466 if(mc && mc->user_flags)
467 worry_about_keywords++;
470 if(worry_about_keywords){
471 MAILSTREAM *dstn_stream = NULL;
472 int already_open = 0;
473 int we_blocked_reuse = 0;
476 * Possible problem created by our stream re-use
477 * strategy. If we are going to open a new stream
478 * here, we want to be sure not to re-use the
479 * stream we are saving _from_, so take it out of the
480 * re-use pool before we call open.
482 if(sp_flagged(stream, SP_USEPOOL)){
483 we_blocked_reuse++;
484 sp_unflag(stream, SP_USEPOOL);
487 /* see if there is a stream open already */
488 if(!dstn_stream){
489 dstn_stream = context_already_open_stream(context,
490 save_folder,
491 AOS_RW_ONLY);
492 already_open = dstn_stream ? 1 : 0;
495 if(!dstn_stream)
496 dstn_stream = context_open(context, NULL,
497 save_folder,
498 SP_USEPOOL | SP_TEMPUSE,
499 NULL);
501 if(dstn_stream && dstn_stream->kwd_create){
502 imapuid_t dummy_uid = 0L;
503 long dummy_msgno = 0L, delete_count;
504 int expbits, set;
505 char *flags = NULL;
506 char *user_flag_name, *duser_flag_name;
508 /* find keywords that need to be defined */
509 for(k = 0; stream_to_user_flag_name(stream, k); k++){
510 user_flag_name = stream_to_user_flag_name(stream, k);
511 if(user_flag_name && user_flag_name[0]){
512 /* is this flag set in any of the save set? */
513 for(set = 0, i = mn_first_cur(msgmap);
514 !set && i > 0L;
515 i = mn_next_cur(msgmap)){
516 rawno = mn_m2raw(msgmap, i);
517 if(user_flag_is_set(stream, rawno, user_flag_name))
518 set++;
521 if(set){
523 * The flag may already be defined in this
524 * mailbox. Check for that first.
526 for(j = 0; stream_to_user_flag_name(dstn_stream, j); j++){
527 duser_flag_name = stream_to_user_flag_name(dstn_stream, j);
528 if(duser_flag_name && duser_flag_name[0]
529 && !strucmp(duser_flag_name, user_flag_name)){
530 set = 0;
531 break;
536 if(set){
537 if(flags == NULL){
538 len = strlen(user_flag_name) + 1;
539 flags = (char *) fs_get((len+1) * sizeof(char));
540 snprintf(flags, len+1, "%s ", user_flag_name);
542 else{
543 char *p;
544 size_t newlen;
546 newlen = strlen(user_flag_name) + 1;
547 len += newlen;
548 fs_resize((void **) &flags, (len+1) * sizeof(char));
549 p = flags + strlen(flags);
550 snprintf(p, newlen+1, "%s ", user_flag_name);
556 if(flags){
557 char *p;
558 size_t newlen;
559 STRING msg;
560 char dummymsg[1000];
561 char *id = NULL;
562 char *idused;
563 appenduid_t *au = NULL;
565 newlen = strlen("\\DELETED");
566 len += newlen;
567 fs_resize((void **) &flags, (len+1) * sizeof(char));
568 p = flags + strlen(flags);
569 snprintf(p, newlen+1, "%s", "\\DELETED");
571 id = oauth2_generate_state();
572 idused = id ? id : "<xyz>";
573 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);
576 * We need to get the uid of the message we are about to
577 * append so that we can delete it when we're done and
578 * so we don't affect other messages.
581 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream)){
582 au = mail_parameters(NIL, GET_APPENDUID, NIL);
583 mail_parameters(NIL, SET_APPENDUID, (void *) appenduid_cb);
586 INIT(&msg, mail_string, (void *) dummymsg, strlen(dummymsg));
587 if(pine_mail_append(dstn_stream, dstn_stream->mailbox, &msg)){
589 (void) pine_mail_ping(dstn_stream);
591 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
592 dummy_uid = get_last_append_uid();
594 if(dummy_uid == 0L){
595 dummy_msgno = get_msgno_by_msg_id(dstn_stream, idused,
596 sp_msgmap(dstn_stream));
597 if(dummy_msgno <= 0L || dummy_msgno > dstn_stream->nmsgs)
598 dummy_msgno = dstn_stream->nmsgs;
600 rawno = mn_m2raw(sp_msgmap(dstn_stream), dummy_msgno);
601 if(rawno > 0L && rawno <= dstn_stream->nmsgs)
602 dummy_uid = mail_uid(dstn_stream, rawno);
604 if(dummy_uid == 0L)
605 dummy_msgno = dstn_stream->nmsgs;
609 * We need to remember which messages are deleted,
610 * undelete them, do the expunge, then delete them again.
612 delete_count = count_flagged(dstn_stream, F_DEL);
613 if(delete_count){
614 for(i = 1L; i <= dstn_stream->nmsgs; i++)
615 if(((mc = mail_elt(dstn_stream, i)) && mc->valid && mc->deleted)
616 || (mc && !mc->valid && mc->searched)){
617 mc->sequence = 1;
618 expbits = MSG_EX_DELETE;
619 msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
621 else if((mc = mail_elt(dstn_stream, i)) != NULL)
622 mc->sequence = 0;
624 if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
625 mail_flag(dstn_stream, seq, "\\DELETED", ST_SILENT);
626 fs_give((void **) &seq);
630 if(dummy_uid > 0L)
631 mail_flag(dstn_stream, ulong2string(dummy_uid),
632 flags, ST_SET | ST_UID | ST_SILENT);
633 else
634 mail_flag(dstn_stream, ulong2string(dummy_msgno),
635 flags, ST_SET | ST_SILENT);
637 ps_global->mm_log_error = 0;
638 ps_global->expunge_in_progress = 1;
639 mail_expunge(dstn_stream);
640 ps_global->expunge_in_progress = 0;
642 if(delete_count){
643 for(i = 1L; i <= dstn_stream->nmsgs; i++)
644 if((mc = mail_elt(dstn_stream, i)) != NULL){
645 mc->sequence
646 = (msgno_exceptions(dstn_stream, i, "0", &expbits, FALSE)
647 && (expbits & MSG_EX_DELETE));
650 * Remove the EX_DELETE bit in case we're still using
651 * this stream.
653 if(mc->sequence){
654 expbits &= ~MSG_EX_DELETE;
655 msgno_exceptions(dstn_stream, i, "0", &expbits, TRUE);
659 if((seq = build_sequence(dstn_stream, NULL, NULL)) != NULL){
660 mail_flag(dstn_stream, seq, "\\DELETED", ST_SET | ST_SILENT);
661 fs_give((void **) &seq);
666 if(is_imap_stream(dstn_stream) && LEVELUIDPLUS (dstn_stream))
667 mail_parameters(NIL, SET_APPENDUID, (void *) au);
669 if(id)
670 fs_give((void **) &id);
672 fs_give((void **) &flags);
676 if(dstn_stream && !already_open)
677 pine_mail_close(dstn_stream);
679 if(we_blocked_reuse)
680 sp_set_flags(stream, sp_flags(stream) | SP_USEPOOL);
684 * If any of the messages have exceptional attachment handling
685 * we have to fall thru below to do the APPEND by hand...
687 if(!msgno_any_deletedparts(stream, msgmap)){
688 int loc_to_loc = 0;
691 * Compare the current stream (the save's source) and the stream
692 * the destination folder will need...
694 context_apply(tmp, context, save_folder, sizeof(tmp));
697 * If we're going to be doing a cross-format copy we're better off
698 * using the else code below that knows how to do multi-append.
699 * The part in the if is leaving save_stream set to NULL in the
700 * case that the stream is local and the folder is local and they
701 * are different formats (like unix and tenex). That will cause us
702 * to fall thru to the APPEND case which is faster than using
703 * copy which will use our imap_proxycopy which doesn't know
704 * about multiappend.
706 loc_to_loc = stream && stream->dtb
707 && stream->dtb->flags & DR_LOCAL && !IS_REMOTE(tmp);
708 if(!loc_to_loc || (stream->dtb->valid && (*stream->dtb->valid)(tmp)))
709 save_stream = loc_to_loc ? stream
710 : context_same_stream(context, save_folder, stream);
713 /* if needed, this'll get set in mm_notify */
714 ps_global->try_to_create = 0;
715 rv = rc = 0;
716 nmsgs = 0L;
719 * At this point, if we have a save_stream, then none of the messages
720 * being saved involve special handling that would require our use
721 * of mail_append, so go with mail_copy since in the IMAP case it
722 * means no data on the wire...
724 if(save_stream){
725 char *dseq = NULL, *oseq;
727 if((flgs & SV_FIX_DELS) &&
728 (dseq = currentf_sequence(stream, msgmap, F_DEL, NULL,
729 0, NULL, NULL)))
730 mail_flag(stream, dseq, "\\DELETED", 0L);
732 seq = currentf_sequence(stream, msgmap, 0L, &nmsgs, 0, NULL, NULL);
733 if(!(flgs & SV_PRESERVE)
734 && (F_ON(F_AGG_SEQ_COPY, ps_global)
735 || (mn_get_sort(msgmap) == SortArrival && !mn_get_revsort(msgmap)))){
738 * currentf_sequence() above lit all the elt "sequence"
739 * bits of the interesting messages. Now, build a sequence
740 * that preserves sort order...
742 oseq = build_sequence(stream, msgmap, &nmsgs);
744 else{
745 oseq = NULL; /* no single sequence! */
746 nmsgs = 0L;
747 i = mn_first_cur(msgmap); /* set first to copy */
751 while(!(rv = (int) context_copy(context, save_stream,
752 oseq ? oseq : long2string(mn_m2raw(msgmap, i)),
753 save_folder))){
754 if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
755 break; /* c-client returned error? */
757 if((context && context->use & CNTXT_INCMNG)
758 && context_isambig(save_folder)){
759 q_status_message(SM_ORDER, 3, 5,
760 _("Can only save to existing folders in Incoming Collection"));
761 break;
764 ps_global->try_to_create = 0; /* reset for next time */
765 if((j = create_for_save(context, save_folder)) < 1){
766 if(j < 0)
767 cancelled = 1; /* user cancels */
769 break;
773 if(rv){ /* failure or finished? */
774 if(oseq) /* all done? */
775 break;
776 else
777 nmsgs++;
779 else{ /* failure! */
780 if(oseq)
781 nmsgs = 0L; /* nothing copy'd */
783 break;
786 while((i = mn_next_cur(msgmap)) > 0L);
788 if(rv && delete) /* delete those saved */
789 mail_flag(stream, seq, "\\DELETED", ST_SET);
790 else if(dseq) /* or restore previous state */
791 mail_flag(stream, dseq, "\\DELETED", ST_SET);
793 if(dseq) /* clean up */
794 fs_give((void **)&dseq);
796 if(oseq)
797 fs_give((void **)&oseq);
799 fs_give((void **)&seq);
801 else{
803 * Special handling requires mail_append. See if we can
804 * re-use stream source messages are on...
806 save_stream = context_same_stream(context, save_folder, stream);
809 * IF the destination's REMOTE, open a stream here so c-client
810 * doesn't have to open it for each aggregate save...
812 if(!save_stream){
813 if(context_apply(tmp, context, save_folder, sizeof(tmp))[0] == '{'
814 && (save_stream = context_open(context, NULL,
815 save_folder,
816 OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
817 NULL)))
818 our_stream = 1;
822 * Allocate a storage object to temporarily store the message
823 * object in. Below it'll get mapped into a c-client STRING struct
824 * in preparation for handing off to context_append...
826 if(!(so = so_get(CharStar, NULL, WRITE_ACCESS))){
827 dprint((1, "Can't allocate store for save: %s\n",
828 error_description(errno)));
829 q_status_message(SM_ORDER | SM_DING, 3, 4,
830 "Problem creating space for message text.");
834 * get a sequence of invalid elt's so we can get their flags...
836 if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
837 mail_fetch_fast(stream, seq, 0L);
838 fs_give((void **) &seq);
842 * If we're supposed set the deleted flag, clear the elt bit
843 * we'll use to build the sequence later...
845 if(delete)
846 for(i = 1L; i <= stream->nmsgs; i++)
847 if((mc = mail_elt(stream, i)) != NULL)
848 mc->sequence = 0;
850 nmsgs = 0L;
852 if(pith_opt_save_size_changed_prompt)
853 (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
856 * if there is more than one message, do multiappend.
857 * otherwise, we can use our already open stream.
859 if(!save_stream || !is_imap_stream(save_stream) ||
860 (LEVELMULTIAPPEND(save_stream) && mn_total_cur(msgmap) > 1)){
861 APPENDPACKAGE pkg;
862 STRING msg;
864 pkg.stream = stream;
865 /* tell save_fetch_append_cb whether or not to leave deleted flag */
866 pkg.flags = flgs & SV_FIX_DELS ? NULL : cpystr("\\DELETED");
867 pkg.date = date;
868 pkg.msg = &msg;
869 pkg.msgmap = msgmap;
871 if ((pkg.so = so) && ((pkg.msgno = mn_first_cur(msgmap)) > 0L)) {
872 so_truncate(so, 0L);
875 * we've gotta make sure this is a stream that we've
876 * opened ourselves.
878 rc = 0;
879 while(!(rv = context_append_multiple(context,
880 our_stream ? save_stream
881 : NULL, save_folder,
882 save_fetch_append_cb,
883 &pkg,
884 stream))) {
886 if(rc++ || !ps_global->try_to_create)
887 break;
888 if((context && context->use & CNTXT_INCMNG)
889 && context_isambig(save_folder)){
890 q_status_message(SM_ORDER, 3, 5,
891 _("Can only save to existing folders in Incoming Collection"));
892 break;
895 ps_global->try_to_create = 0;
896 if((j = create_for_save(context, save_folder)) < 1){
897 if(j < 0)
898 cancelled = 1;
899 break;
903 if(pkg.flags)
904 fs_give((void **) &pkg.flags);
906 ps_global->noshow_error = 0;
908 if(rv){
910 * Success! Count it, and if it's not already deleted and
911 * it's supposed to be, mark it to get deleted later...
913 for(i = mn_first_cur(msgmap); so && i > 0L;
914 i = mn_next_cur(msgmap)){
915 nmsgs++;
916 if(delete){
917 rawno = mn_m2raw(msgmap, i);
918 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
919 ? mail_elt(stream, rawno) : NULL;
920 if(mc && !mc->deleted)
921 mc->sequence = 1; /* mark for later deletion */
926 else
927 cancelled = 1; /* No messages to append! */
929 if(sp_expunge_count(stream))
930 cancelled = 1; /* All bets are off! */
932 else
933 for(i = mn_first_cur(msgmap); so && i > 0L; i = mn_next_cur(msgmap)){
934 int preserve_these_flags;
936 so_truncate(so, 0L);
938 rawno = mn_m2raw(msgmap, i);
939 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
940 ? mail_elt(stream, rawno) : NULL;
942 /* always preserve these flags */
943 preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
944 /* maybe preserve deleted flag */
945 preserve_these_flags |= flgs & SV_FIX_DELS ? 0 : F_DEL;
946 flags = flag_string(stream, rawno, preserve_these_flags);
948 if(mc && mc->day)
949 mail_date(date, mc);
950 else
951 *date = '\0';
953 rv = save_fetch_append(stream, mn_m2raw(msgmap, i),
954 NULL, save_stream, save_folder, context,
955 mc ? mc->rfc822_size : 0L, flags, date, so);
957 if(flags)
958 fs_give((void **) &flags);
960 if(sp_expunge_count(stream))
961 rv = -1; /* All bets are off! */
963 if(rv == 1){
965 * Success! Count it, and if it's not already deleted and
966 * it's supposed to be, mark it to get deleted later...
968 nmsgs++;
969 if(delete){
970 rawno = mn_m2raw(msgmap, i);
971 mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
972 ? mail_elt(stream, rawno) : NULL;
973 if(mc && !mc->deleted)
974 mc->sequence = 1; /* mark for later deletion */
977 else{
978 if(rv == -1)
979 cancelled = 1; /* else horrendous failure */
981 break;
985 if(pith_opt_save_size_changed_prompt)
986 (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
988 if(our_stream)
989 pine_mail_close(save_stream);
991 if(so)
992 so_give(&so);
994 if(delete && (seq = build_sequence(stream, NULL, NULL))){
995 mail_flag(stream, seq, "\\DELETED", ST_SET);
996 fs_give((void **)&seq);
1000 ps_global->try_to_create = 0; /* reset for next time */
1001 if(!cancelled && nmsgs < mn_total_cur(msgmap)){
1002 dprint((1, "FAILED save of msg %s (c-client sequence #)\n",
1003 long2string(mn_m2raw(msgmap, mn_get_cur(msgmap)))));
1004 if((mn_total_cur(msgmap) > 1L) && nmsgs != 0){
1005 /* this shouldn't happen cause it should be all or nothing */
1006 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1007 "%ld of %ld messages saved before error occurred",
1008 nmsgs, mn_total_cur(msgmap));
1009 dprint((1, "\t%s\n", tmp_20k_buf));
1010 q_status_message(SM_ORDER, 3, 5, tmp_20k_buf);
1012 else if(mn_total_cur(msgmap) == 1){
1013 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1014 "%s to folder \"%s\" FAILED",
1015 filter ? "Filter" : "Save",
1016 strsquish(tmp_20k_buf+500, 500, folder, 35));
1017 dprint((1, "\t%s\n", tmp_20k_buf));
1018 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
1020 else{
1021 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
1022 "%s of %s messages to folder \"%s\" FAILED",
1023 filter ? "Filter" : "Save", comatose(mn_total_cur(msgmap)),
1024 strsquish(tmp_20k_buf+500, 500, folder, 35));
1025 dprint((1, "\t%s\n", tmp_20k_buf));
1026 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
1030 return(nmsgs);
1034 /* Append message callback
1035 * Accepts: MAIL stream
1036 * append data package
1037 * pointer to return initial flags
1038 * pointer to return message internal date
1039 * pointer to return stringstruct of message or NIL to stop
1040 * Returns: T if success (have message or stop), NIL if error
1043 long save_fetch_append_cb(MAILSTREAM *stream, void *data, char **flags,
1044 char **date, STRING **message)
1046 unsigned long size = 0;
1047 APPENDPACKAGE *pkg = (APPENDPACKAGE *) data;
1048 MESSAGECACHE *mc;
1049 char *fetch;
1050 int rc;
1051 unsigned long raw, hlen, tlen, mlen;
1053 if(pkg->so && (pkg->msgno > 0L)) {
1054 raw = mn_m2raw(pkg->msgmap, pkg->msgno);
1055 mc = (raw > 0L && pkg->stream && raw <= pkg->stream->nmsgs)
1056 ? mail_elt(pkg->stream, raw) : NULL;
1057 if(mc){
1058 int preserve_these_flags = F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD;
1060 size = mc->rfc822_size;
1063 * If the caller wants us to preserve the state of the
1064 * \DELETED flag then pkg->flags will be \DELETED, otherwise
1065 * let it be undeleted. Fix from Eduardo Chappa.
1067 if(pkg->flags){
1068 if(strstr(pkg->flags,"\\DELETED"))
1069 preserve_these_flags |= F_DEL;
1071 /* not used anymore */
1072 fs_give((void **) &pkg->flags);
1075 pkg->flags = flag_string(pkg->stream, raw, preserve_these_flags);
1078 if(mc && mc->day)
1079 mail_date(pkg->date, mc);
1080 else
1081 *pkg->date = '\0';
1082 if((fetch = mail_fetch_header(pkg->stream, raw, NULL, NULL, &hlen,
1083 FT_PEEK)) != NULL){
1084 if(!*pkg->date)
1085 saved_date(pkg->date, fetch);
1087 else
1088 return(0); /* fetchtext writes error */
1090 rc = MSG_EX_DELETE; /* "rc" overloaded */
1091 if(msgno_exceptions(pkg->stream, raw, NULL, &rc, 0)){
1092 char section[64];
1093 int failure = 0;
1094 BODY *body;
1095 gf_io_t pc;
1097 size = 0; /* all bets off, abort sanity test */
1098 gf_set_so_writec(&pc, pkg->so);
1100 section[0] = '\0';
1101 if(!pine_mail_fetch_structure(pkg->stream, raw, &body, 0))
1102 return(0);
1104 if(msgno_part_deleted(pkg->stream, raw, "")){
1105 tlen = 0;
1106 failure = !save_ex_replace_body(fetch, &hlen, body, pc);
1108 else
1109 failure = !(so_nputs(pkg->so, fetch, (long) hlen)
1110 && save_ex_output_body(pkg->stream, raw, section,
1111 body, &tlen, pc));
1113 gf_clear_so_writec(pkg->so);
1115 if(failure)
1116 return(0);
1118 q_status_message(SM_ORDER, 3, 3,
1119 /* TRANSLATORS: A warning to user that the message parts
1120 they deleted will not be included in the copy they
1121 are now saving to. */
1122 _("NOTE: Deleted message parts NOT included in saved copy!"));
1125 else{
1126 if(!so_nputs(pkg->so, fetch, (long) hlen))
1127 return(0);
1129 fetch = pine_mail_fetch_text(pkg->stream, raw, NULL, &tlen, FT_PEEK);
1131 if(!(fetch && so_nputs(pkg->so, fetch, tlen)))
1132 return(0);
1135 so_seek(pkg->so, 0L, 0); /* rewind just in case */
1136 mlen = hlen + tlen;
1138 if(size && mlen < size){
1139 char buf[128];
1141 if(sp_dead_stream(pkg->stream)){
1142 snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
1143 q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
1144 dprint((1, "save_fetch_append_cb: %s\n", buf));
1145 return(0);
1147 else{
1148 if(pith_opt_save_size_changed_prompt
1149 && (*pith_opt_save_size_changed_prompt)(mn_raw2m(pkg->msgmap, raw), 0) == 'y'){
1150 snprintf(buf, sizeof(buf),
1151 "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
1152 raw, size, mlen);
1153 dprint((1, "save_fetch_append_cb: %s", buf));
1154 snprintf(buf, sizeof(buf),
1155 "Message to save shrank: source msg # %ld may be saved incorrectly",
1156 mn_raw2m(pkg->msgmap, raw));
1157 if(F_OFF(F_IGNORE_SIZE, ps_global))
1158 q_status_message(SM_ORDER, 0, 3, buf);
1160 else{
1161 snprintf(buf, sizeof(buf),
1162 "Message to save shrank: raw msg # %ld went from announced size %ld to actual size %ld",
1163 raw, size, mlen);
1164 dprint((1, "save_fetch_append_cb: %s", buf));
1165 return(0);
1170 INIT(pkg->msg, mail_string, (void *)so_text(pkg->so), mlen);
1171 *message = pkg->msg;
1172 /* Next message */
1173 pkg->msgno = mn_next_cur(pkg->msgmap);
1175 else /* No more messages */
1176 *message = NIL;
1178 *flags = pkg->flags;
1179 *date = (pkg->date && *pkg->date) ? pkg->date : NIL;
1180 return LONGT; /* Return success */
1184 /*----------------------------------------------------------------------
1185 FETCH an rfc822 message header and body and APPEND to destination folder
1187 Args:
1190 Result:
1192 ----*/
1194 save_fetch_append(MAILSTREAM *stream, long int raw, char *sect,
1195 MAILSTREAM *save_stream, char *save_folder, CONTEXT_S *context,
1196 long unsigned int size, char *flags, char *date, STORE_S *so)
1198 int rc, rv, old_imap_server = 0;
1199 long j;
1200 char *fetch;
1201 unsigned long hlen, tlen, mlen;
1202 STRING msg;
1204 if((fetch = mail_fetch_header(stream, raw, sect, NULL, &hlen, FT_PEEK)) != NULL){
1206 * If there's no date string, then caller found the
1207 * MESSAGECACHE for this message element didn't already have it.
1208 * So, parse the "internal date" by hand since fetchstructure
1209 * hasn't been called yet for this particular message, and
1210 * we don't want to call it now just to get the date since
1211 * the full header has what we want. Likewise, don't even
1212 * think about calling mail_fetchfast either since it also
1213 * wants to load mc->rfc822_size (which we could actually
1214 * use but...), which under some drivers is *very*
1215 * expensive to acquire (can you say NNTP?)...
1217 if(!*date)
1218 saved_date(date, fetch);
1220 else
1221 return(0); /* fetchtext writes error */
1223 rc = MSG_EX_DELETE; /* "rc" overloaded */
1224 if(msgno_exceptions(stream, raw, NULL, &rc, 0)){
1225 char section[64];
1226 int failure = 0;
1227 BODY *body;
1228 gf_io_t pc;
1230 size = 0; /* all bets off, abort sanity test */
1231 gf_set_so_writec(&pc, so);
1233 if(sect && *sect){
1234 snprintf(section, sizeof(section), "%s.1", sect);
1235 if(!(body = mail_body(stream, raw, (unsigned char *) section)))
1236 return(0);
1238 else{
1239 section[0] = '\0';
1240 if(!pine_mail_fetch_structure(stream, raw, &body, 0))
1241 return(0);
1245 * Walk the MIME structure looking for exceptional segments,
1246 * writing them in the requested fashion.
1248 * First, though, check for the easy case...
1250 if(msgno_part_deleted(stream, raw, sect ? sect : "")){
1251 tlen = 0;
1252 failure = !save_ex_replace_body(fetch, &hlen, body, pc);
1254 else{
1256 * Otherwise, roll up your sleeves and get to work...
1257 * start by writing msg header and then the processed body
1259 failure = !(so_nputs(so, fetch, (long) hlen)
1260 && save_ex_output_body(stream, raw, section,
1261 body, &tlen, pc));
1264 gf_clear_so_writec(so);
1266 if(failure)
1267 return(0);
1269 q_status_message(SM_ORDER, 3, 3,
1270 _("NOTE: Deleted message parts NOT included in saved copy!"));
1273 else{
1274 /* First, write the header we just fetched... */
1275 if(!so_nputs(so, fetch, (long) hlen))
1276 return(0);
1278 old_imap_server = is_imap_stream(stream) && !modern_imap_stream(stream);
1280 /* Second, go fetch the corresponding text... */
1281 fetch = pine_mail_fetch_text(stream, raw, sect, &tlen,
1282 !old_imap_server ? FT_PEEK : 0);
1285 * Special handling in case we're fetching a Message/rfc822
1286 * segment and we're talking to an old server...
1288 if(fetch && *fetch == '\0' && sect && (hlen + tlen) != size){
1289 so_seek(so, 0L, 0);
1290 fetch = pine_mail_fetch_body(stream, raw, sect, &tlen, 0L);
1294 * Pre IMAP4 servers can't do a non-peeking mail_fetch_text,
1295 * so if the message we are saving from was originally unseen,
1296 * we have to change it back to unseen. Flags contains the
1297 * string "SEEN" if the original message was seen.
1299 if(old_imap_server && (!flags || !srchstr(flags,"SEEN"))){
1300 char seq[20];
1302 strncpy(seq, long2string(raw), sizeof(seq));
1303 seq[sizeof(seq)-1] = '\0';
1304 mail_flag(stream, seq, "\\SEEN", 0);
1307 /* If fetch succeeded, write the result */
1308 if(!(fetch && so_nputs(so, fetch, tlen)))
1309 return(0);
1312 so_seek(so, 0L, 0); /* rewind just in case */
1315 * Set up a c-client string driver so we can hand the
1316 * collected text down to mail_append.
1318 * NOTE: We only test the size if and only if we already
1319 * have it. See, in some drivers, especially under
1320 * dos, its too expensive to get the size (full
1321 * header and body text fetch plus MIME parse), so
1322 * we only verify the size if we already know it.
1324 mlen = hlen + tlen;
1326 if(size && mlen < size){
1327 char buf[128];
1329 if(sp_dead_stream(stream)){
1330 snprintf(buf, sizeof(buf), _("Cannot save because current folder is Closed"));
1331 q_status_message(SM_ORDER | SM_DING, 3, 4, buf);
1332 dprint((1, "save_fetch_append: %s", buf));
1333 return(0);
1335 else{
1336 if(pith_opt_save_size_changed_prompt
1337 && (*pith_opt_save_size_changed_prompt)(mn_raw2m(sp_msgmap(stream), raw), 0) == 'y'){
1338 snprintf(buf, sizeof(buf),
1339 "Message to save shrank! (raw msg# %ld: %ld -> %ld): User says continue",
1340 raw, size, mlen);
1341 dprint((1, "save_fetch_append: %s", buf));
1342 snprintf(buf, sizeof(buf),
1343 "Message to save shrank: source msg # %ld may be saved incorrectly",
1344 mn_raw2m(sp_msgmap(stream), raw));
1345 q_status_message(SM_ORDER, 0, 3, buf);
1347 else{
1348 snprintf(buf, sizeof(buf),
1349 "Message to save shrank: source raw msg # %ld went from announced size %ld to actual size %ld",
1350 raw, size, mlen);
1351 dprint((1, "save_fetch_append: %s", buf));
1352 return(0);
1357 INIT(&msg, mail_string, (void *)so_text(so), mlen);
1359 rc = 0;
1360 while(!(rv = (int) context_append_full(context, save_stream,
1361 save_folder, flags,
1362 *date ? date : NULL,
1363 &msg))){
1364 if(rc++ || !ps_global->try_to_create) /* abysmal failure! */
1365 break; /* c-client returned error? */
1367 if(context && (context->use & CNTXT_INCMNG)
1368 && context_isambig(save_folder)){
1369 q_status_message(SM_ORDER, 3, 5,
1370 _("Can only save to existing folders in Incoming Collection"));
1371 break;
1374 ps_global->try_to_create = 0; /* reset for next time */
1375 if((j = create_for_save(context, save_folder)) < 1){
1376 if(j < 0)
1377 rv = -1; /* user cancelled */
1379 break;
1382 SETPOS((&msg), 0L); /* reset string driver */
1385 return(rv);
1390 * save_ex_replace_body -
1392 * NOTE : hlen points to a cell that has the byte count of "hdr" on entry
1393 * *BUT* which is to contain the count of written bytes on exit
1396 save_ex_replace_body(char *hdr, long unsigned int *hlen, struct mail_bodystruct *body, gf_io_t pc)
1398 unsigned long len;
1401 * "X-" out the given MIME headers unless they're
1402 * the same as we're going to substitute...
1404 if(body->type == TYPETEXT
1405 && (!body->subtype || !strucmp(body->subtype, "plain"))
1406 && body->encoding == ENC7BIT){
1407 if(!gf_nputs(hdr, *hlen, pc)) /* write out header */
1408 return(0);
1410 else{
1411 int bol = 1;
1414 * write header, "X-"ing out transport headers bothersome to
1415 * software but potentially useful to the human recipient...
1417 for(len = *hlen; len; len--){
1418 if(bol){
1419 unsigned long n;
1421 bol = 0;
1422 if(save_ex_mask_types(hdr, &n, pc))
1423 *hlen += n; /* add what we inserted */
1424 else
1425 break;
1428 if(*hdr == '\015' && *(hdr+1) == '\012'){
1429 bol++; /* remember beginning of line */
1430 len--; /* account for LF */
1431 if(gf_nputs(hdr, 2, pc))
1432 hdr += 2;
1433 else
1434 break;
1436 else if(!(*pc)(*hdr++))
1437 break;
1440 if(len) /* bytes remain! */
1441 return(0);
1444 /* Now, blat out explanatory text as the body... */
1445 if(save_ex_explain_body(body, &len, pc)){
1446 *hlen += len;
1447 return(1);
1449 else
1450 return(0);
1455 save_ex_output_body(MAILSTREAM *stream, long int raw, char *section,
1456 struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
1458 char *txtp, newsect[128];
1459 unsigned long ilen;
1461 txtp = mail_fetch_mime(stream, raw, section, len, FT_PEEK);
1463 if(msgno_part_deleted(stream, raw, section))
1464 return(save_ex_replace_body(txtp, len, body, pc));
1466 if(body->type == TYPEMULTIPART){
1467 #define BOUNDARYLEN 128
1468 char *subsect, boundary[BOUNDARYLEN];
1469 int n, blen;
1470 PART *part = body->nested.part;
1471 PARAMETER *param;
1473 /* Locate supplied multipart boundary */
1474 for (param = body->parameter; param; param = param->next)
1475 if (!strucmp(param->attribute, "boundary")){ /* BOUNDARYLEN == sizeof(boundary) */
1476 snprintf(boundary, sizeof(boundary), "--%.*s\015\012", BOUNDARYLEN-10,
1477 param->value);
1478 blen = strlen(boundary);
1479 break;
1482 if(!param){
1483 q_status_message(SM_ORDER|SM_DING, 3, 3, "Missing MIME boundary");
1484 return(0);
1488 * BUG: if multi/digest and a message deleted (which we'll
1489 * change to text/plain), we need to coerce this composite
1490 * type to multi/mixed !!
1492 if(!gf_nputs(txtp, *len, pc)) /* write MIME header */
1493 return(0);
1495 /* Prepare to specify sub-sections */
1496 strncpy(newsect, section, sizeof(newsect));
1497 newsect[sizeof(newsect)-1] = '\0';
1498 subsect = &newsect[n = strlen(newsect)];
1499 if(n > 2 && !strcmp(&newsect[n-2], ".0"))
1500 subsect--;
1501 else if(n){
1502 if((subsect-newsect) < sizeof(newsect))
1503 *subsect++ = '.';
1506 n = 1;
1507 do { /* for each part */
1508 strncpy(subsect, int2string(n++), sizeof(newsect)-(subsect-newsect));
1509 newsect[sizeof(newsect)-1] = '\0';
1510 if(gf_puts(boundary, pc)
1511 && save_ex_output_body(stream, raw, newsect,
1512 &part->body, &ilen, pc))
1513 *len += blen + ilen;
1514 else
1515 return(0);
1517 while ((part = part->next) != NULL); /* until done */
1518 /* BOUNDARYLEN = sizeof(boundary) */
1519 snprintf(boundary, sizeof(boundary), "--%.*s--\015\012", BOUNDARYLEN-10,param->value);
1520 *len += blen + 2;
1521 return(gf_puts(boundary, pc));
1524 /* Start by writing the part's MIME header */
1525 if(!gf_nputs(txtp, *len, pc))
1526 return(0);
1528 if(body->type == TYPEMESSAGE
1529 && (!body->subtype || !strucmp(body->subtype, "rfc822"))){
1530 /* write RFC 822 message's header */
1531 if((txtp = mail_fetch_header(stream,raw,section,NULL,&ilen,FT_PEEK))
1532 && gf_nputs(txtp, ilen, pc))
1533 *len += ilen;
1534 else
1535 return(0);
1537 /* then go deal with its body parts */
1538 snprintf(newsect, sizeof(newsect), "%.10s%s%s", section, section ? "." : "",
1539 (body->nested.msg->body->type == TYPEMULTIPART) ? "0" : "1");
1540 if(save_ex_output_body(stream, raw, newsect,
1541 body->nested.msg->body, &ilen, pc)){
1542 *len += ilen;
1543 return(1);
1546 return(0);
1549 /* Write corresponding body part */
1550 if((txtp = pine_mail_fetch_body(stream, raw, section, &ilen, FT_PEEK))
1551 && gf_nputs(txtp, (long) ilen, pc) && gf_puts("\015\012", pc)){
1552 *len += ilen + 2;
1553 return(1);
1556 return(0);
1560 /*----------------------------------------------------------------------
1561 Mask off any header entries we don't want to show up in exceptional saves
1563 Args: hdr -- pointer to start of a header line
1564 pc -- function to write the prefix
1566 ----*/
1568 save_ex_mask_types(char *hdr, long unsigned int *len, gf_io_t pc)
1570 char *s = NULL;
1572 if(!struncmp(hdr, "content-type:", 13))
1573 s = "Content-Type: Text/Plain; charset=UTF-8\015\012X-";
1574 else if(!struncmp(hdr, "content-description:", 20))
1575 s = "Content-Description: Deleted Attachment\015\012X-";
1576 else if(!struncmp(hdr, "content-transfer-encoding:", 26)
1577 || !struncmp(hdr, "content-disposition:", 20))
1578 s = "X-";
1580 return((*len = s ? strlen(s) : 0) ? gf_puts(s, pc) : 1);
1585 save_ex_explain_body(struct mail_bodystruct *body, long unsigned int *len, gf_io_t pc)
1587 unsigned long ilen;
1588 char **blurbp;
1589 static char *blurb[] = {
1590 N_("The following attachment was DELETED when this message was saved:"),
1591 NULL
1594 *len = 0;
1595 for(blurbp = blurb; *blurbp; blurbp++)
1596 if(save_ex_output_line(_(*blurbp), &ilen, pc))
1597 *len += ilen;
1598 else
1599 return(0);
1601 ilen = 0; /* maybe save_ex_explain_parts should set this to zero instead */
1602 if(!save_ex_explain_parts(body, 0, &ilen, pc))
1603 return(0);
1605 *len += ilen;
1606 return(1);
1611 save_ex_explain_parts(struct mail_bodystruct *body, int depth, long unsigned int *len, gf_io_t pc)
1613 char *tmp, namebuf[MAILTMPLEN], descbuf[MAILTMPLEN];
1614 unsigned long ilen, tmplen;
1615 char *name = parameter_val(body->parameter, "name");
1617 if(!name){
1618 if(body->disposition.type &&
1619 !strucmp(body->disposition.type, "ATTACHMENT"))
1620 name = parameter_val(body->disposition.parameter, "filename");
1622 if(name)
1623 rfc1522_decode_to_utf8((unsigned char *)namebuf, sizeof(namebuf), name);
1625 if(body->description && *body->description)
1626 rfc1522_decode_to_utf8((unsigned char *)descbuf, sizeof(descbuf), body->description);
1628 ilen = 0;
1629 if(body->type == TYPEMULTIPART) { /* multipart gets special handling */
1630 PART *part = body->nested.part; /* first body part */
1632 *len = 0;
1633 if(body->description && *body->description){
1634 tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1
1635 + strlen(body->subtype ? body->subtype : "Unknown")
1636 + strlen(name ? " (Name=\"" : "")
1637 + strlen(name ? namebuf : "")
1638 + strlen(name ? "\"" : "") + strlen(_(" segment described as "))
1639 + strlen(descbuf) + strlen(_(" containing:")) + 1;
1640 tmp = fs_get((tmplen + 1)*sizeof(char));
1641 sprintf(tmp, "%s%s/%s%s%s%s%s%s%s", _("A "),
1642 body_type_names(body->type), body->subtype ? body->subtype : "Unknown",
1643 name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "",
1644 _(" segment described as "), descbuf, _(" containing:"));
1645 if(!save_ex_output_text(tmp, depth, len, pc))
1646 return(0);
1648 else{
1649 tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1
1650 + strlen(body->subtype ? body->subtype : "Unknown")
1651 + strlen(name ? " (Name=\"" : "")
1652 + strlen(name ? namebuf : "")
1653 + strlen(name ? "\"" : "") + strlen(_(" segment containing:")) + 1;
1654 tmp = fs_get((tmplen + 1)*sizeof(char));
1655 sprintf(tmp, "%s%s/%s%s%s%s%s", _("A "),
1656 body_type_names(body->type), body->subtype ? body->subtype : "Unknown",
1657 name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "",
1658 _(" segment containing:"));
1661 if(save_ex_output_text(tmp, depth, &ilen, pc))
1662 *len += ilen;
1663 else
1664 return(0);
1666 depth++;
1667 do /* for each part */
1668 if(save_ex_explain_parts(&part->body, depth, &ilen, pc))
1669 *len += ilen;
1670 else
1671 return(0);
1672 while ((part = part->next) != NULL); /* until done */
1674 else{
1675 char *comatosep = comatose((body->encoding == ENCBASE64)
1676 ? ((body->size.bytes * 3)/4)
1677 : body->size.bytes);
1678 tmplen = strlen(_("A ")) + strlen(body_type_names(body->type)) + 1
1679 + strlen(body->subtype && *body->subtype ? body->subtype : "Unknown")
1680 + strlen(name ? " (Name=\"" : "")
1681 + strlen(name ? namebuf : "")
1682 + strlen(name ? "\"" : "") + strlen(_(" segment of about "))
1683 + strlen(comatosep) + strlen(_(" bytes")) + 1
1684 + strlen(body->description && *body->description ? _(" described as \"") : "")
1685 + strlen(body->description && *body->description ? descbuf : "")
1686 + strlen(body->description && *body->description ? "\"": "")
1687 + 1;
1688 tmp = fs_get((tmplen + 1)*sizeof(char));
1689 sprintf(tmp, "%s%s/%s%s%s%s%s%s%s%s%s%s%s",
1690 _("A "),
1691 body_type_names(body->type),
1692 body->subtype && *body->subtype ? body->subtype : "Unknown",
1693 name ? " (Name=\"" : "", name ? namebuf : "", name ? "\")" : "",
1694 _(" segment of about "),
1695 comatosep,
1696 _(" bytes"),
1697 body->description && *body->description ? "," : ".",
1698 body->description && *body->description ? " described as \"" : "",
1699 body->description && *body->description ? descbuf : "",
1700 body->description && *body->description ? "\"" : "");
1702 if(save_ex_output_text(tmp, depth, &ilen, pc))
1703 *len += ilen;
1704 else
1705 return(0);
1708 return(1);
1711 /* save output one line at the time. This uses save_ex_output_line, which
1712 * only allows 68 characters of text per line, so what we do is to get justify
1713 * the input text to 68 characters long. If it is not possible to do this,
1714 * we chop the line at 68 characters and move the remaining text to the next
1715 * line.
1718 save_ex_output_text(char *text, int depth, unsigned long *len, gf_io_t pc)
1720 int starti, i, startpos, pos, rv;
1721 char tmp[100]; /* a number bigger than 68, we justify text here. */
1722 unsigned long ilen;
1724 rv = 0;
1725 pos = 0;
1726 do {
1727 ilen = 0;
1728 sprintf(tmp, "%*.*s", depth, depth, " ");
1729 startpos = pos;
1730 for(starti = i = depth; i < 68 && text[pos] != '\0'; i++, pos++){
1731 tmp[i] = text[pos];
1732 if(tmp[i] == ' '){ /* save when we reach a space */
1733 starti = i;
1734 startpos = pos;
1737 tmp[i] = '\0';
1738 if(i == 68){
1739 if(text[pos] != '\0' && text[pos] != ' '){ /* if we are not at the end of a word */
1740 if(startpos < pos && starti != depth){ /* rewind */
1741 tmp[starti] = '\0';
1742 pos = startpos;
1746 rv += save_ex_output_line(tmp, &ilen, pc);
1747 *len += ilen;
1749 while (text[pos] != '\0');
1750 return rv;
1754 save_ex_output_line(char *line, long unsigned int *len, gf_io_t pc)
1756 snprintf(tmp_20k_buf, SIZEOF_20KBUF, " [ %-*.*s ]\015\012", 68, 68, line);
1757 *len = strlen(tmp_20k_buf);
1758 return(gf_puts(tmp_20k_buf, pc));
1762 /*----------------------------------------------------------------------
1763 Save() helper function to create canonical date string from given header
1765 Args: date -- buf to receive canonical date string
1766 header -- rfc822 header to fish date string from
1768 Result: date filled with canonicalized date in header, or null string
1769 ----*/
1770 void
1771 saved_date(char *date, char *header)
1773 char *d, *p, c;
1774 MESSAGECACHE elt;
1776 *date = '\0';
1777 if((toupper((unsigned char)(*(d = header)))
1778 == 'D' && !strncmp(d, "date:", 5))
1779 || (d = srchstr(header, "\015\012date:"))){
1780 for(d += 7; *d == ' '; d++)
1781 ; /* skip white space */
1783 if((p = strstr(d, "\015\012")) != NULL){
1784 for(; p > d && *p == ' '; p--)
1785 ; /* skip white space */
1787 c = *p;
1788 *p = '\0'; /* tie off internal date */
1791 if(mail_parse_date(&elt, (unsigned char *) d)) /* normalize it */
1792 mail_date(date, &elt);
1794 if(p) /* restore header */
1795 *p = c;
1800 MAILSTREAM *
1801 save_msg_stream(CONTEXT_S *context, char *folder, int *we_opened)
1803 char tmp[MAILTMPLEN];
1804 MAILSTREAM *save_stream = NULL;
1806 if(IS_REMOTE(context_apply(tmp, context, folder, sizeof(tmp)))
1807 && !(save_stream = sp_stream_get(tmp, SP_MATCH))
1808 && !(save_stream = sp_stream_get(tmp, SP_SAME))){
1809 if((save_stream = context_open(context, NULL, folder,
1810 OP_HALFOPEN | SP_USEPOOL | SP_TEMPUSE,
1811 NULL)) != NULL)
1812 *we_opened = 1;
1815 return(save_stream);
1819 /*----------------------------------------------------------------------
1820 Offer to create a non-existent folder to copy message[s] into
1822 Args: context -- context to create folder in
1823 folder -- name of folder to create
1825 Result: 0 if create failed (c-client writes error)
1826 1 if create successful
1827 -1 if user declines to create folder
1828 ----*/
1830 create_for_save(CONTEXT_S *context, char *folder)
1832 int ret;
1834 if(pith_opt_save_create_prompt
1835 && (ret = (*pith_opt_save_create_prompt)(context, folder, 1)) != 1)
1836 return(ret);
1838 return(context_create(context, NULL, folder));