* Clear history when closing alpine, for commands that keep it.
[alpine.git] / pith / reply.c
blob398ee63d1aded3f374b87e59f2926c4554a1f375
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2018 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
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/reply.h"
21 #include "../pith/send.h"
22 #include "../pith/init.h"
23 #include "../pith/state.h"
24 #include "../pith/conf.h"
25 #include "../pith/remote.h"
26 #include "../pith/status.h"
27 #include "../pith/mailview.h"
28 #include "../pith/filter.h"
29 #include "../pith/newmail.h"
30 #include "../pith/bldaddr.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/mimedesc.h"
33 #include "../pith/detach.h"
34 #include "../pith/help.h"
35 #include "../pith/pipe.h"
36 #include "../pith/addrstring.h"
37 #include "../pith/news.h"
38 #include "../pith/util.h"
39 #include "../pith/pattern.h"
40 #include "../pith/detoken.h"
41 #include "../pith/stream.h"
42 #include "../pith/busy.h"
43 #include "../pith/readfile.h"
44 #include "../pith/text.h"
45 #include "../pith/list.h"
46 #include "../pith/ablookup.h"
47 #include "../pith/mailcmd.h"
48 #include "../pith/margin.h"
49 #include "../pith/smime.h"
53 * Internal prototypes
55 void bounce_mask_header(char **, char *);
58 int (*pith_opt_replyto_prompt)(void);
59 int (*pith_opt_reply_to_all_prompt)(int *);
63 * standard type of storage object used for body parts...
65 #define PART_SO_TYPE CharStar
68 char *(*pith_opt_user_agent_prefix)(void);
70 /* compare two subjects and return if they are the same.
71 We compare stripped subjects, that is, those that do
72 not have re/fwd. Before we compare the subjects, we
73 decode them in case they were encoded.
74 Return value: 0 - not the same subject, 1 - same subject.
77 int
78 same_subject(char *s, char *t)
80 int rv = 0;
81 int i, j, len;
82 char *s1, *s2; /* holds decoded subjects from s and t */
83 char *u, *v;
85 if (s == NULL || t == NULL)
86 return s == t ? 1 : 0;
88 i = strlen(s); j = strlen(t);
89 len = i < j ? j : i;
90 u = fs_get(6*len+1);
91 v = fs_get(6*len+1);
92 s1 = (char *) rfc1522_decode_to_utf8((unsigned char *) u, 6*len + 1, s);
93 s2 = (char *) rfc1522_decode_to_utf8((unsigned char *) v, 6*len + 1, t);
94 mail_strip_subject(s1, &u);
95 mail_strip_subject(s2, &v);
97 rv = !strucmp(u, v);
99 fs_give((void **) &u);
100 fs_give((void **) &v);
101 return rv;
105 * reply_harvest -
107 * Returns: 1 if addresses successfully copied
108 * 0 on user cancel or error
110 * Input flags:
111 * RSF_FORCE_REPLY_TO
112 * RSF_QUERY_REPLY_ALL
113 * RSF_FORCE_REPLY_ALL
115 * Output flags:
116 * RSF_FORCE_REPLY_ALL
120 reply_harvest(struct pine *ps, long int msgno, char *section, ENVELOPE *env,
121 struct mail_address **saved_from, struct mail_address **saved_to,
122 struct mail_address **saved_cc, struct mail_address **saved_resent,
123 int *flags)
125 ADDRESS *ap, *ap2, *rep_address;
126 int ret = 0, sniff_resent = 0;
127 char *rep_field;
130 * If Reply-To is same as From just treat it like it was From.
131 * Otherwise, always use the reply-to if we're replying to more
132 * than one msg or say ok to using it, even if it's us.
133 * If there's no reply-to or it's the same as the from, assume
134 * that the user doesn't want to reply to himself, unless there's
135 * nobody else.
137 if(env->reply_to && !addr_lists_same(env->reply_to, env->from)
138 && (F_ON(F_AUTO_REPLY_TO, ps)
139 || ((*flags) & RSF_FORCE_REPLY_TO)
140 || (pith_opt_replyto_prompt && (*pith_opt_replyto_prompt)() == 'y'))){
141 rep_field = "reply-to";
142 rep_address = env->reply_to;
144 else{
145 rep_field = "From";
146 rep_address = env->from;
149 ap = reply_cp_addr(ps, msgno, section, rep_field, *saved_from,
150 (ADDRESS *) NULL, rep_address, RCA_NOT_US);
152 if(ret == 'x') {
153 cmd_cancelled("Reply");
154 return(0);
157 reply_append_addr(saved_from, ap);
159 /*--------- check for other recipients ---------*/
160 if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){
162 if((ap = reply_cp_addr(ps, msgno, section, "To", *saved_to,
163 *saved_from, env->to, RCA_NOT_US)) != NULL)
164 reply_append_addr(saved_to, ap);
166 if((ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc,
167 *saved_from, env->cc, RCA_NOT_US)) != NULL)
168 reply_append_addr(saved_cc, ap);
171 * In these cases, we either need to look at the resent headers
172 * to include in the reply-to-all, or to decide whether or not
173 * we need to ask the reply-to-all question.
175 if(((*flags) & RSF_FORCE_REPLY_ALL)
176 || (((*flags) & RSF_QUERY_REPLY_ALL)
177 && ((!(*saved_from) && !(*saved_cc))
178 || (*saved_from && !(*saved_to) && !(*saved_cc))))){
180 sniff_resent++;
181 if((ap2 = reply_resent(ps, msgno, section)) != NULL){
183 * look for bogus addr entries and replace
185 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
186 *saved_from, ap2, RCA_NOT_US)) != NULL)
188 reply_append_addr(saved_resent, ap);
190 mail_free_address(&ap2);
195 * It makes sense to ask reply-to-all now.
197 if(((*flags) & RSF_QUERY_REPLY_ALL)
198 && ((*saved_from && (*saved_to || *saved_cc || *saved_resent))
199 || (*saved_cc || *saved_resent))){
200 *flags &= ~RSF_QUERY_REPLY_ALL;
201 if(pith_opt_reply_to_all_prompt
202 && (*pith_opt_reply_to_all_prompt)(flags) < 0){
203 cmd_cancelled("Reply");
204 return(0);
209 * If we just answered yes to the reply-to-all question and
210 * we still haven't collected the resent headers, do so now.
212 if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent
213 && (ap2 = reply_resent(ps, msgno, section))){
215 * look for bogus addr entries and replace
217 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
218 *saved_from, ap2, RCA_NOT_US)) != NULL)
219 reply_append_addr(saved_resent, ap);
221 mail_free_address(&ap2);
225 return(1);
229 /*----------------------------------------------------------------------
230 Return a pointer to a copy of the given address list
231 filtering out those already in the "mask" lists and ourself.
233 Args: mask1 -- Don't copy if in this list
234 mask2 -- or if in this list
235 source -- List to be copied
236 us_too -- Don't filter out ourself.
237 flags -- RCA_NOT_US copy all addrs except for our own
238 RCA_ALL copy all addrs, including our own
239 RCA_ONLY_US copy only addrs that are our own
241 ---*/
242 ADDRESS *
243 reply_cp_addr(struct pine *ps, long int msgno, char *section, char *field,
244 struct mail_address *mask1, struct mail_address *mask2,
245 struct mail_address *source, int flags)
247 ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail;
249 /* can only choose one of these flags values */
250 assert(!((flags & RCA_ALL && flags & RCA_ONLY_US)
251 || (flags & RCA_ALL && flags & RCA_NOT_US)
252 || (flags & RCA_ONLY_US && flags & RCA_NOT_US)));
254 for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next)
255 if(tmp1->host && tmp1->host[0] == '.'){
256 char *h, *fields[2];
258 fields[0] = field;
259 fields[1] = NULL;
260 if((h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL,
261 msgno, section, fields)) != NULL){
262 char *p, fname[32];
263 int q;
265 q = strlen(h);
267 strncpy(fname, field, sizeof(fname)-2);
268 fname[sizeof(fname)-2] = '\0';
269 strncat(fname, ":", sizeof(fname)-strlen(fname)-1);
270 fname[sizeof(fname)-1] = '\0';
272 for(p = h; (p = strstr(p, fname)) != NULL; )
273 rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */
275 sqznewlines(h); /* blat out CR's & LF's */
276 for(p = h; (p = strchr(p, TAB)) != NULL; )
277 *p++ = ' '; /* turn TABs to whitespace */
279 if(*h){
280 long l;
281 size_t ll;
283 ret = (ADDRESS *) fs_get(sizeof(ADDRESS));
284 memset(ret, 0, sizeof(ADDRESS));
286 /* get rid of leading white space */
287 for(p = h; *p == SPACE; p++)
290 if(p != h){
291 memmove(h, p, l = strlen(p));
292 h[l] = '\0';
295 /* base64 armor plate the gunk to protect against
296 * c-client quoting in output.
298 p = (char *) rfc822_binary(h, strlen(h),
299 (unsigned long *) &l);
300 sqznewlines(p);
301 fs_give((void **) &h);
303 * Seems like the 4 ought to be a 2, but I'll leave it
304 * to be safe, in case something else adds 2 chars later.
306 ll = strlen(p) + 4;
307 ret->mailbox = (char *) fs_get(ll * sizeof(char));
308 snprintf(ret->mailbox, ll, "&%s", p);
309 ret->mailbox[ll-1] = '\0';
310 fs_give((void **) &p);
311 ret->host = cpystr(RAWFIELD);
315 return(ret);
318 ret_tail = &ret;
319 for(source = first_addr(source); source; source = source->next){
320 for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next)
321 if(address_is_same(source, tmp1)) /* it is in mask1, skip it */
322 break;
324 for(tmp2 = first_addr(mask2); !tmp1 && tmp2; tmp2 = tmp2->next)
325 if(address_is_same(source, tmp2)) /* it is in mask2, skip it */
326 break;
329 * If there's no match in masks and this address satisfies the
330 * flags requirement, copy it.
332 if(!tmp1 && !tmp2 /* no mask match */
333 && ((flags & RCA_ALL) /* including everybody */
334 || (flags & RCA_ONLY_US && address_is_us(source, ps))
335 || (flags & RCA_NOT_US && !address_is_us(source, ps)))){
336 tmp1 = source->next;
337 source->next = NULL; /* only copy one addr! */
338 *ret_tail = rfc822_cpy_adr(source);
339 ret_tail = &(*ret_tail)->next;
340 source->next = tmp1; /* restore rest of list */
344 return(ret);
348 ACTION_S *
349 set_role_from_msg(struct pine *ps, long int rflags, long int msgno, char *section)
351 ACTION_S *role = NULL;
352 PAT_S *pat = NULL;
353 SEARCHSET *ss = NULL;
354 PAT_STATE pstate;
356 if(!nonempty_patterns(rflags, &pstate))
357 return(role);
359 if(msgno > 0L){
360 ss = mail_newsearchset();
361 ss->first = ss->last = (unsigned long)msgno;
364 /* Go through the possible roles one at a time until we get a match. */
365 pat = first_pattern(&pstate);
367 /* calculate this message's score if needed */
368 if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES &&
369 get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF)
370 (void)calculate_some_scores(ps->mail_stream, ss, 0);
372 while(!role && pat){
373 if(match_pattern(pat->patgrp, ps->mail_stream, ss, section,
374 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
375 if(!pat->action || pat->action->bogus)
376 break;
378 role = pat->action;
380 else
381 pat = next_pattern(&pstate);
384 if(ss)
385 mail_free_searchset(&ss);
387 return(role);
392 * reply_seed - fill in reply header
395 void
396 reply_seed(struct pine *ps, ENVELOPE *outgoing, ENVELOPE *env,
397 struct mail_address *saved_from, struct mail_address *saved_to,
398 struct mail_address *saved_cc, struct mail_address *saved_resent,
399 char **fcc, int replytoall, char **errmsg)
401 ADDRESS **to_tail, **cc_tail;
403 to_tail = &outgoing->to;
404 cc_tail = &outgoing->cc;
406 if(saved_from){
407 /* Put Reply-To or From in To. */
408 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
409 (ADDRESS *) NULL, saved_from, RCA_ALL);
410 if(replytoall){
411 if(ps->reply.preserve_fields){
412 while(*to_tail)
413 to_tail = &(*to_tail)->next;
415 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
416 (ADDRESS *) NULL, saved_to, RCA_ALL);
418 while(*to_tail)
419 to_tail = &(*to_tail)->next;
421 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
422 outgoing->to, saved_resent, RCA_ALL);
424 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
425 outgoing->to, saved_cc, RCA_ALL);
427 else{ /* and the rest in cc */
428 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
429 outgoing->to, saved_to, RCA_ALL);
430 while(*cc_tail) /* stay on last address */
431 cc_tail = &(*cc_tail)->next;
433 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
434 outgoing->to, saved_cc, RCA_ALL);
435 while(*cc_tail)
436 cc_tail = &(*cc_tail)->next;
438 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
439 outgoing->to, saved_resent, RCA_ALL);
443 else if(saved_to){
444 /* No From (maybe from us), put To in To. */
445 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
446 (ADDRESS *)NULL, saved_to, RCA_ALL);
447 /* and the rest in cc */
448 if(replytoall){
449 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
450 outgoing->to, saved_cc, RCA_ALL);
451 while(*cc_tail)
452 cc_tail = &(*cc_tail)->next;
454 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
455 outgoing->to, saved_resent, RCA_ALL);
458 else{
459 /* No From or To, put everything else in To if replytoall, */
460 if(replytoall){
461 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
462 (ADDRESS *) NULL, saved_cc, RCA_ALL);
463 while(*to_tail)
464 to_tail = &(*to_tail)->next;
466 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
467 (ADDRESS *) NULL, saved_resent, RCA_ALL);
469 /* else, reply to original From which must be us */
470 else{
472 * Put self in To if in original From.
474 if(!outgoing->newsgroups)
475 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
476 (ADDRESS *) NULL, env->from, RCA_ALL);
480 /* add any missing personal data */
481 reply_fish_personal(outgoing, env);
483 /* get fcc */
484 if(fcc && outgoing->to && outgoing->to->host[0] != '.'){
485 *fcc = get_fcc_based_on_to(outgoing->to);
487 else if(fcc && outgoing->newsgroups){
488 char *newsgroups_returned = NULL;
489 int rv;
491 rv = news_grouper(outgoing->newsgroups, &newsgroups_returned, errmsg, fcc, NULL);
492 if(rv != -1 &&
493 strcmp(outgoing->newsgroups, newsgroups_returned)){
494 fs_give((void **)&outgoing->newsgroups);
495 outgoing->newsgroups = newsgroups_returned;
497 else
498 fs_give((void **) &newsgroups_returned);
503 /*----------------------------------------------------------------------
504 Test the given address lists for equivalence
506 Args: x -- First address list for comparison
507 y -- Second address for comparison
509 ---*/
511 addr_lists_same(struct mail_address *x, struct mail_address *y)
513 for(x = first_addr(x), y = first_addr(y);
514 x && y;
515 x = first_addr(x->next), y = first_addr(y->next)){
516 if(!address_is_same(x, y))
517 return(0);
520 return(!x && !y); /* true if ran off both lists */
524 /*----------------------------------------------------------------------
525 Test the given address against those in the given envelope's to, cc
527 Args: addr -- address for comparison
528 env -- envelope to compare against
530 ---*/
532 addr_in_env(struct mail_address *addr, ENVELOPE *env)
534 ADDRESS *ap;
536 for(ap = env ? env->to : NULL; ap; ap = ap->next)
537 if(address_is_same(addr, ap))
538 return(1);
540 for(ap = env ? env->cc : NULL; ap; ap = ap->next)
541 if(address_is_same(addr, ap))
542 return(1);
544 return(0); /* not found! */
548 /*----------------------------------------------------------------------
549 Add missing personal info dest from src envelope
551 Args: dest -- envelope to add personal info to
552 src -- envelope to get personal info from
554 NOTE: This is just kind of a courtesy function. It's really not adding
555 anything needed to get the mail thru, but it is nice for the user
556 under some odd circumstances.
557 ---*/
558 void
559 reply_fish_personal(ENVELOPE *dest, ENVELOPE *src)
561 ADDRESS *da, *sa;
563 for(da = dest ? dest->to : NULL; da; da = da->next){
564 if(da->personal && !da->personal[0])
565 fs_give((void **)&da->personal);
567 for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next)
568 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
569 da->personal = cpystr(sa->personal);
571 for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
572 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
573 da->personal = cpystr(sa->personal);
576 for(da = dest ? dest->cc : NULL; da; da = da->next){
577 if(da->personal && !da->personal[0])
578 fs_give((void **)&da->personal);
580 for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next)
581 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
582 da->personal = cpystr(sa->personal);
584 for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
585 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
586 da->personal = cpystr(sa->personal);
591 /*----------------------------------------------------------------------
592 Given a header field and envelope, build "References: " header data
594 Args:
596 Returns:
597 ---*/
598 char *
599 reply_build_refs(ENVELOPE *env)
601 int len, id_len, first_ref_len = 0, foldslop;
602 char *p, *refs = NULL, *h = env->references;
603 char *first_ref = NULL, *tail_refs = NULL;
606 if(!(env->message_id && (id_len = strlen(env->message_id))))
607 return(NULL);
609 if(h){
611 * The length we have to work with doesn't seem to appear in any
612 * standards. Steve Jones says that in comp.news discussions he
613 * has seen 1024 as the longest length of a header value.
614 * In the inn news source we find MAXHEADERSIZE = 1024. It appears
615 * that is the maximum length of the header value, including
616 * newlines for folded lines (that is, the newlines are counted).
617 * We'll be conservative and figure every reference will take up a
618 * line of its own when we fold. We'll also count 2 for CRLF instead
619 * of just one for LF just to be safe. hubert 2001-jan
620 * J.B. Moreno <planb@newsreaders.com> says "The server limit is
621 * more commonly encountered at 999/1000 bytes [...]". So we'll
622 * back off to 999 instead of 1024.
624 #define MAXHEADERSIZE (999)
626 /* count the total number of potential folds, max of 2 bytes each */
627 for(foldslop = 2, p = h; (p = strstr(p+1, "> <")); )
628 foldslop += 2;
630 if((len=strlen(h)) + 1+id_len + foldslop >= MAXHEADERSIZE
631 && (p = strstr(h, "> <"))){
633 * If the references line is so long that we are going to have
634 * to delete some of the references, delete the 2nd, 3rd, ...
635 * We don't want to delete the first message in the thread.
637 p[1] = '\0';
638 first_ref = cpystr(h);
639 first_ref_len = strlen(first_ref)+1; /* len includes space */
640 p[1] = ' ';
641 tail_refs = p+2;
642 /* get rid of 2nd, 3rd, ... until it fits */
643 while((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
644 foldslop >= MAXHEADERSIZE
645 && (p = strstr(tail_refs, "> <"))){
646 tail_refs = p + 2;
647 foldslop -= 2;
651 * If the individual references are seriously long, somebody
652 * is messing with us and we don't care if it works right.
654 if((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
655 foldslop >= MAXHEADERSIZE){
656 first_ref_len = len = 0;
657 tail_refs = NULL;
658 if(first_ref)
659 fs_give((void **)&first_ref);
662 else
663 tail_refs = h;
665 refs = (char *)fs_get((first_ref_len + 1+id_len + len + 1) *
666 sizeof(char));
667 snprintf(refs, first_ref_len + 1+id_len + len + 1, "%s%s%s%s%s",
668 first_ref ? first_ref : "",
669 first_ref ? " " : "",
670 tail_refs ? tail_refs : "",
671 tail_refs ? " " : "",
672 env->message_id);
673 refs[first_ref_len + 1+id_len + len] = '\0';
676 if(!refs && id_len)
677 refs = cpystr(env->message_id);
679 if(first_ref)
680 fs_give((void **)&first_ref);
682 return(refs);
687 /*----------------------------------------------------------------------
688 Snoop for any Resent-* headers, and return an ADDRESS list
690 Args: stream --
691 msgno --
693 Returns: either NULL if no Resent-* or parsed ADDRESS struct list
694 ---*/
695 ADDRESS *
696 reply_resent(struct pine *pine_state, long int msgno, char *section)
698 #define RESENTFROM 0
699 #define RESENTTO 1
700 #define RESENTCC 2
701 ADDRESS *rlist = NULL, **a, **b;
702 char *hdrs, *values[RESENTCC+1];
703 int i;
704 static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL};
705 static char *fakedomain = "@";
707 if((hdrs = pine_fetchheader_lines(pine_state->mail_stream,
708 msgno, section, fields)) != NULL){
709 memset(values, 0, (RESENTCC+1) * sizeof(char *));
710 simple_header_parse(hdrs, fields, values);
711 for(i = RESENTFROM; i <= RESENTCC; i++)
712 rfc822_parse_adrlist(&rlist, values[i],
713 (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state))
714 ? fakedomain : pine_state->maildomain);
716 /* pare dup's ... */
717 for(a = &rlist; *a; a = &(*a)->next) /* compare every address */
718 for(b = &(*a)->next; *b; ) /* to the others */
719 if(address_is_same(*a, *b)){
720 ADDRESS *t = *b;
722 if(!(*a)->personal){ /* preserve personal name */
723 (*a)->personal = (*b)->personal;
724 (*b)->personal = NULL;
727 *b = t->next;
728 t->next = NULL;
729 mail_free_address(&t);
731 else
732 b = &(*b)->next;
735 if(hdrs)
736 fs_give((void **) &hdrs);
738 return(rlist);
742 /*----------------------------------------------------------------------
743 Format and return subject suitable for the reply command
745 Args: subject -- subject to build reply subject for
746 buf -- buffer to use for writing. If non supplied, alloc one.
747 buflen -- length of buf if supplied, else ignored
749 Returns: with either "Re:" prepended or not, if already there.
750 returned string is allocated.
751 ---*/
752 char *
753 reply_subject(char *subject, char *buf, size_t buflen)
755 size_t l = (subject && *subject) ? 4*strlen(subject) : 10;
756 char *tmp = fs_get(l + 1), *decoded, *p;
758 if(!buf){
759 buflen = l + 5;
760 buf = fs_get(buflen);
763 /* decode any 8bit into tmp buffer */
764 decoded = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp, l+1, subject);
766 buf[0] = '\0';
767 if(decoded /* already "re:" ? */
768 && (decoded[0] == 'R' || decoded[0] == 'r')
769 && (decoded[1] == 'E' || decoded[1] == 'e')){
771 if(decoded[2] == ':')
772 snprintf(buf, buflen, "%.*s", (int)(buflen-1), subject);
773 else if((decoded[2] == '[') && (p = strchr(decoded, ']'))){
774 p++;
775 while(*p && isspace((unsigned char)*p)) p++;
776 if(p[0] == ':')
777 snprintf(buf, buflen, "%.*s", (int)(buflen-1), subject);
781 if(!buf[0])
782 snprintf(buf, buflen, "Re: %.*s", (int)(buflen-1),
783 (subject && *subject) ? subject : "your mail");
785 buf[buflen-1] = '\0';
787 fs_give((void **) &tmp);
788 return(buf);
792 /*----------------------------------------------------------------------
793 return initials for the given personal name
795 Args: name -- Personal name to extract initials from
797 Returns: pointer to name overwritten with initials
798 ---*/
799 char *
800 reply_quote_initials(char *name)
802 char *s = name,
803 *w = name;
804 int i, j;
805 CBUF_S cbuf;
806 UCS ucs;
808 cbuf.cbuf[i = 0] = '\0';
809 cbuf.cbufp = cbuf.cbuf;
810 cbuf.cbufend = cbuf.cbuf;
812 /* while there are still characters to look at */
813 while(s && *s){
814 /* skip to next initial */
815 while(*s && (unsigned char) *s == ' ')
816 s++;
818 if(!utf8_to_ucs4_oneatatime((unsigned char) *s++ & 0xff, &cbuf, &ucs, NULL)){
819 i++;
820 continue;
823 /* skip over non-alpha stuff */
824 if(i == 0 && !isalnum((unsigned char) cbuf.cbuf[0]))
825 goto reset_cbuf;
827 /* copy cbuf */
828 for(j = 0; j <= i; j++) *w++ = cbuf.cbuf[j];
830 /* skip to end of this piece of name */
831 while(*s && (unsigned char) *s != ' ')
832 s++;
834 reset_cbuf:
835 cbuf.cbuf[i = 0] = '\0';
836 cbuf.cbufp = cbuf.cbuf;
837 cbuf.cbufend = cbuf.cbuf;
840 if(w)
841 *w = '\0';
843 return(name);
847 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
848 * tokens being substituted for (only in the size of buf below).
850 #define MAX_SUBSTITUTION 6
851 #define MAX_PREFIX 63
852 static char *from_token = "_FROM_";
853 static char *nick_token = "_NICK_";
854 static char *init_token = "_INIT_";
856 /*----------------------------------------------------------------------
857 return a quoting string, "> " by default, for replied text
859 Args: env -- envelope of message being replied to
861 Returns: malloc'd array containing quoting string, freed by caller
862 ---*/
863 char *
864 reply_quote_str(ENVELOPE *env)
866 char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1];
868 strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1);
869 buf[sizeof(buf)-1] = '\0';
871 /* set up the prefix to quote included text */
872 if((p = strstr(buf, from_token)) != NULL){
873 repl = (env && env->from && env->from->mailbox) ? env->from->mailbox
874 : "";
875 strncpy(pbf, repl, sizeof(pbf)-1);
876 pbf[sizeof(pbf)-1] = '\0';;
877 rplstr(p, sizeof(buf)-(p-buf), strlen(from_token), pbf);
880 if((p = strstr(buf, nick_token)) != NULL){
881 repl = (env &&
882 env->from &&
883 env->from &&
884 get_nickname_from_addr(env->from, tmp_20k_buf, 1000))
885 ? tmp_20k_buf : "";
886 strncpy(pbf, repl, sizeof(pbf)-1);
887 pbf[sizeof(pbf)-1] = '\0';;
888 rplstr(p, sizeof(buf)-(p-buf), strlen(nick_token), pbf);
891 if((p = strstr(buf, init_token)) != NULL){
892 char *q = NULL;
893 char buftmp[MAILTMPLEN];
895 snprintf(buftmp, sizeof(buftmp), "%.200s",
896 (env && env->from && env->from->personal) ? env->from->personal : "");
897 buftmp[sizeof(buftmp)-1] = '\0';
899 repl = (env && env->from && env->from->personal)
900 ? reply_quote_initials(q = cpystr((char *)rfc1522_decode_to_utf8(
901 (unsigned char *)tmp_20k_buf,
902 SIZEOF_20KBUF, buftmp)))
903 : "";
905 istrncpy(pbf, repl, sizeof(pbf)-1);
906 pbf[sizeof(pbf)-1] = '\0';;
907 rplstr(p, sizeof(buf)-(p-buf), strlen(init_token), pbf);
908 if(q)
909 fs_give((void **)&q);
912 prefix = removing_quotes(cpystr(buf));
914 return(prefix);
918 reply_quote_str_contains_tokens(void)
920 return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] &&
921 (strstr(ps_global->VAR_REPLY_STRING, from_token) ||
922 strstr(ps_global->VAR_REPLY_STRING, nick_token) ||
923 strstr(ps_global->VAR_REPLY_STRING, init_token)));
927 /*----------------------------------------------------------------------
928 Build the body for the message number/part being replied to
930 Args:
932 Result: BODY structure suitable for sending
934 ----------------------------------------------------------------------*/
935 BODY *
936 reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
937 long int msgno, char *sect_prefix, void *msgtext, char *prefix,
938 int plustext, ACTION_S *role, int toplevel, REDRAFT_POS_S **redraft_pos)
940 #define SECTBUFLEN 256
941 char *p, *sig = NULL, *section, sect_buf[SECTBUFLEN];
942 BODY *body = NULL, *tmp_body = NULL;
943 PART *part;
944 gf_io_t pc;
945 int impl, template_len = 0, leave_cursor_at_top = 0, reply_raw_body = 0;
947 if(sect_prefix) /* SECTBUFLEN = sizeof(sect_buf) */
948 snprintf(section = sect_buf, sizeof(sect_buf), "%.*s.1", SECTBUFLEN-1, sect_prefix);
949 else
950 section = "1";
952 sect_buf[sizeof(sect_buf)-1] = '\0';
954 if(ps_global->full_header == 2
955 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
956 reply_raw_body = 1;
958 gf_set_so_writec(&pc, (STORE_S *) msgtext);
960 if(toplevel){
961 char *filtered;
963 impl = 0;
964 filtered = detoken(role, env, 0,
965 ps_global->reply.signature_bottom,
966 0, redraft_pos, &impl);
967 if(filtered){
968 if(*filtered){
969 so_puts((STORE_S *)msgtext, filtered);
970 if(impl == 1)
971 template_len = strlen(filtered);
972 else if(impl == 2)
973 leave_cursor_at_top++;
976 fs_give((void **)&filtered);
978 else
979 impl = 1;
981 else
982 impl = 1;
984 if(toplevel &&
985 (sig = reply_signature(role, env, redraft_pos, &impl)) &&
986 !ps_global->reply.signature_bottom){
989 * If CURSORPOS was set explicitly in sig_file, and there was a
990 * template file before that, we need to adjust the offset by the
991 * length of the template file. However, if the template had
992 * a set CURSORPOS in it then impl was 2 before getting to the
993 * signature, so offset wouldn't have been reset by the signature
994 * CURSORPOS and offset would already be correct. That case will
995 * be ok here because template_len will be 0 and adding it does
996 * nothing. If template
997 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
998 * by the CURSORPOS in the sig. In that case we have to adjust the
999 * offset. That's what the next line does. It adjusts it if
1000 * template_len is nonzero and if CURSORPOS was set in sig_file.
1002 if(impl == 2)
1003 (*redraft_pos)->offset += template_len;
1005 if(*sig)
1006 so_puts((STORE_S *)msgtext, sig);
1009 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
1010 * is set, we won't have used it yet and want it to be non-NULL.
1012 fs_give((void **)&sig);
1016 * Only put cursor in sig if there is a cursorpos there but not
1017 * one in the template, and sig-at-bottom.
1019 if(!(sig && impl == 2 && !leave_cursor_at_top))
1020 leave_cursor_at_top++;
1022 if(plustext){
1023 if(!orig_body
1024 || orig_body->type == TYPETEXT
1025 || reply_raw_body
1026 || !ps_global->reply.keep_attach){
1027 char *charset = NULL;
1029 /*------ Simple text-only message ----*/
1030 body = mail_newbody();
1031 body->type = TYPETEXT;
1032 body->contents.text.data = msgtext;
1033 reply_delimiter(env, role, pc);
1034 if(ps_global->reply.include_header)
1035 reply_forward_header(stream, msgno, sect_prefix,
1036 env, pc, prefix);
1038 if(!orig_body || reply_raw_body || reply_body_text(orig_body, &tmp_body)){
1039 BODY *bodyp = NULL;
1041 bodyp = reply_raw_body ? NULL : tmp_body;
1044 * We set the charset in the outgoing message to the same
1045 * as the one in the message we're replying to unless it
1046 * is the unknown charset. We do that in order to attempt
1047 * to preserve the same charset in the reply if possible.
1048 * It may be safer to just set it to whatever we want instead
1049 * but then the receiver may not be able to read it.
1051 if(bodyp
1052 && (charset = parameter_val(bodyp->parameter, "charset"))
1053 && strucmp(charset, UNKNOWN_CHARSET))
1054 set_parameter(&body->parameter, "charset", charset);
1056 if(charset)
1057 fs_give((void **) &charset);
1059 get_body_part_text(stream, bodyp, msgno,
1060 bodyp ? (p = body_partno(stream, msgno, bodyp))
1061 : sect_prefix,
1062 0L, pc, prefix, NULL, GBPT_NONE);
1063 if(bodyp && p)
1064 fs_give((void **) &p);
1066 else{
1067 gf_puts(NEWLINE, pc);
1068 gf_puts(" [NON-Text Body part not included]", pc);
1069 gf_puts(NEWLINE, pc);
1072 else if(orig_body->type == TYPEMULTIPART){
1073 /*------ Message is Multipart ------*/
1074 if(orig_body->subtype
1075 && !strucmp(orig_body->subtype, "signed")
1076 && orig_body->nested.part){
1077 /* operate only on the signed part */
1078 body = reply_body(stream, env,
1079 &orig_body->nested.part->body,
1080 msgno, section, msgtext, prefix,
1081 plustext, role, 0, redraft_pos);
1083 else if(orig_body->subtype
1084 && !strucmp(orig_body->subtype, "alternative")){
1085 /* Set up the simple text reply */
1086 body = mail_newbody();
1087 body->type = TYPETEXT;
1088 body->contents.text.data = msgtext;
1090 if(reply_body_text(orig_body, &tmp_body)){
1091 reply_delimiter(env, role, pc);
1092 if(ps_global->reply.include_header)
1093 reply_forward_header(stream, msgno, sect_prefix,
1094 env, pc, prefix);
1096 get_body_part_text(stream, tmp_body, msgno,
1097 p = body_partno(stream,msgno,tmp_body),
1098 0L, pc, prefix, NULL, GBPT_NONE);
1099 if(p)
1100 fs_give((void **) &p);
1102 else
1103 q_status_message(SM_ORDER | SM_DING, 3, 3,
1104 "No suitable multipart text found for inclusion!");
1106 else{
1107 body = copy_body(NULL, orig_body);
1110 * whatever subtype it is, demote it
1111 * to plain old MIXED.
1113 if(body->subtype)
1114 fs_give((void **) &body->subtype);
1116 body->subtype = cpystr("Mixed");
1118 if(body->nested.part &&
1119 body->nested.part->body.type == TYPETEXT) {
1120 char *new_charset = NULL;
1122 /*---- First part of the message is text -----*/
1123 body->nested.part->body.contents.text.data = msgtext;
1124 if(body->nested.part->body.subtype &&
1125 strucmp(body->nested.part->body.subtype, "Plain")){
1126 fs_give((void **)&body->nested.part->body.subtype);
1127 body->nested.part->body.subtype = cpystr("Plain");
1129 reply_delimiter(env, role, pc);
1130 if(ps_global->reply.include_header)
1131 reply_forward_header(stream, msgno, sect_prefix,
1132 env, pc, prefix);
1134 if(!(get_body_part_text(stream,
1135 &orig_body->nested.part->body,
1136 msgno, section, 0L, pc, prefix,
1137 &new_charset, GBPT_NONE)
1138 && fetch_contents(stream, msgno, sect_prefix, body)))
1139 q_status_message(SM_ORDER | SM_DING, 3, 4,
1140 _("Error including all message parts"));
1141 else if(new_charset)
1142 set_parameter(&body->nested.part->body.parameter, "charset", new_charset);
1144 else if(orig_body->nested.part->body.type == TYPEMULTIPART
1145 && orig_body->nested.part->body.subtype
1146 && !strucmp(orig_body->nested.part->body.subtype,
1147 "alternative")
1148 && reply_body_text(&orig_body->nested.part->body,
1149 &tmp_body)){
1150 int partnum;
1152 reply_delimiter(env, role, pc);
1153 if(ps_global->reply.include_header)
1154 reply_forward_header(stream, msgno, sect_prefix,
1155 env, pc, prefix);
1156 /* SECTBUFLEN = sizeof(sect_buf) */
1157 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s",
1158 SECTBUFLEN/2-2,
1159 sect_prefix ? sect_prefix : "",
1160 sect_prefix ? "." : "",
1161 SECTBUFLEN/2-2,
1162 p = partno(orig_body, tmp_body));
1163 sect_buf[sizeof(sect_buf)-1] = '\0';
1164 fs_give((void **) &p);
1165 get_body_part_text(stream, tmp_body, msgno,
1166 sect_buf, 0L, pc, prefix,
1167 NULL, GBPT_NONE);
1169 part = body->nested.part->next;
1170 body->nested.part->next = NULL;
1171 mail_free_body_part(&body->nested.part);
1172 body->nested.part = mail_newbody_part();
1173 body->nested.part->body.type = TYPETEXT;
1174 body->nested.part->body.subtype = cpystr("Plain");
1175 body->nested.part->body.contents.text.data = msgtext;
1176 body->nested.part->next = part;
1177 /* SECTBUFLEN = sizeof(sect_buf) */
1178 for(partnum = 2; part != NULL; part = part->next){
1179 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d",
1180 SECTBUFLEN/2,
1181 sect_prefix ? sect_prefix : "",
1182 sect_prefix ? "." : "", partnum++);
1183 sect_buf[sizeof(sect_buf)-1] = '\0';
1185 if(!fetch_contents(stream, msgno,
1186 sect_buf, &part->body)){
1187 break;
1191 else {
1192 /*--- Fetch the original pieces ---*/
1193 if(!fetch_contents(stream, msgno, sect_prefix, body))
1194 q_status_message(SM_ORDER | SM_DING, 3, 4,
1195 _("Error including all message parts"));
1197 /*--- No text part, create a blank one ---*/
1198 part = mail_newbody_part();
1199 part->next = body->nested.part;
1200 body->nested.part = part;
1201 part->body.contents.text.data = msgtext;
1205 else{
1206 /*---- Single non-text message of some sort ----*/
1207 body = mail_newbody();
1208 body->type = TYPEMULTIPART;
1209 part = mail_newbody_part();
1210 body->nested.part = part;
1212 /*--- The first part, a blank text part to be edited ---*/
1213 part->body.type = TYPETEXT;
1214 part->body.contents.text.data = msgtext;
1216 /*--- The second part, what ever it is ---*/
1217 part->next = mail_newbody_part();
1218 part = part->next;
1219 part->body.id = generate_message_id();
1220 copy_body(&(part->body), orig_body);
1223 * the idea here is to fetch part into storage object
1225 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE,
1226 NULL,EDIT_ACCESS)) != NULL){
1227 if((p = pine_mail_fetch_body(stream, msgno, section,
1228 &part->body.size.bytes, NIL)) != NULL){
1229 so_nputs((STORE_S *)part->body.contents.text.data,
1230 p, part->body.size.bytes);
1232 else
1233 mail_free_body(&body);
1235 else
1236 mail_free_body(&body);
1239 else{
1240 /*--------- No text included --------*/
1241 body = mail_newbody();
1242 body->type = TYPETEXT;
1243 body->contents.text.data = msgtext;
1246 if(!leave_cursor_at_top){
1247 long cnt = 0L;
1248 unsigned char c;
1250 /* rewind and count chars to start of sig file */
1251 so_seek((STORE_S *)msgtext, 0L, 0);
1252 while(so_readc(&c, (STORE_S *)msgtext))
1253 cnt++;
1255 if(!*redraft_pos){
1256 *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
1257 memset((void *)*redraft_pos, 0,sizeof(**redraft_pos));
1258 (*redraft_pos)->hdrname = cpystr(":");
1262 * If explicit cursor positioning in sig file,
1263 * add offset to start of sig file plus offset into sig file.
1264 * Else, just offset to start of sig file.
1266 (*redraft_pos)->offset += cnt;
1269 if(sig){
1270 if(*sig)
1271 so_puts((STORE_S *)msgtext, sig);
1273 fs_give((void **)&sig);
1276 gf_clear_so_writec((STORE_S *) msgtext);
1278 return(body);
1283 * reply_part - first replyable multipart of a multipart.
1286 reply_body_text(struct mail_bodystruct *body, struct mail_bodystruct **new_body)
1288 if(body){
1289 switch(body->type){
1290 case TYPETEXT :
1291 *new_body = body;
1292 return(1);
1294 case TYPEMULTIPART :
1295 if(body->subtype && !strucmp(body->subtype, "alternative")){
1296 PART *part;
1297 int got_one = 0;
1299 if((part = body->nested.part) != NULL
1300 && part->body.type == TYPEMULTIPART)
1301 return reply_body_text(&body->nested.part->body, new_body);
1303 if(ps_global->force_prefer_plain
1304 || (!ps_global->force_no_prefer_plain
1305 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
1306 for(part = body->nested.part; part; part = part->next)
1307 if((!part->body.type || part->body.type == TYPETEXT)
1308 && (!part->body.subtype
1309 || !strucmp(part->body.subtype, "plain"))){
1310 *new_body = &part->body;
1311 return(1);
1316 * Else choose last alternative among plain or html parts.
1317 * Perhaps we should really be using mime_show() to make this
1318 * potentially more general than just plain or html.
1320 for(part = body->nested.part; part; part = part->next){
1321 if((!part->body.type || part->body.type == TYPETEXT)
1322 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
1324 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
1325 got_one++;
1326 *new_body = &part->body;
1330 if(got_one)
1331 return(1);
1333 else if(body->nested.part)
1334 /* NOTE: we're only interested in "first" part of mixed */
1335 return(reply_body_text(&body->nested.part->body, new_body));
1337 break;
1339 default:
1340 break;
1344 return(0);
1348 char *
1349 reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl)
1351 char *sig;
1352 size_t l;
1354 sig = detoken(role, env,
1355 2, ps_global->reply.signature_bottom ? 0 : 1, 1,
1356 redraft_pos, impl);
1358 if(!ps_global->reply.signature_bottom && (!sig || !*sig)){
1359 if(sig)
1360 fs_give((void **)&sig);
1362 l = 2 * strlen(NEWLINE);
1363 sig = (char *)fs_get((l+1) * sizeof(char));
1364 strncpy(sig, NEWLINE, l);
1365 sig[l] = '\0';
1366 strncat(sig, NEWLINE, l+1-1-strlen(sig));
1367 sig[l] = '\0';
1368 return(sig);
1371 return(sig);
1376 * Buf is at least size maxlen+1
1378 void
1379 get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1381 ADDRESS *addr = NULL;
1382 ADDRESS *last_to = NULL;
1383 ADDRESS *first_addr = NULL, *second_addr = NULL;
1384 ADDRESS *third_addr = NULL, *fourth_addr = NULL;
1385 int cntaddr, l;
1386 size_t orig_maxlen;
1387 char *p;
1389 buf[0] = '\0';
1391 switch(type){
1392 case iFrom:
1393 addr = env ? env->from : NULL;
1394 break;
1396 case iTo:
1397 addr = env ? env->to : NULL;
1398 break;
1400 case iCc:
1401 addr = env ? env->cc : NULL;
1402 break;
1404 case iSender:
1405 addr = env ? env->sender : NULL;
1406 break;
1409 * Recips is To and Cc togeter. We hook the two adrlists together
1410 * temporarily.
1412 case iRecips:
1413 addr = env ? env->to : NULL;
1414 /* Find end of To list */
1415 for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
1418 /* Make the end of To list point to cc list */
1419 if(last_to)
1420 last_to->next = (env ? env->cc : NULL);
1422 break;
1425 * Initials.
1427 case iInit:
1428 if(env && env->from && env->from->personal){
1429 char *name, *initials = NULL;
1431 name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1432 SIZEOF_20KBUF, env->from->personal);
1433 if(name == env->from->personal){
1434 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
1435 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
1436 name = tmp_20k_buf;
1439 if(name && *name){
1440 initials = reply_quote_initials(name);
1441 iutf8ncpy(buf, initials, maxlen);
1442 buf[maxlen] = '\0';
1446 return;
1448 default:
1449 break;
1452 orig_maxlen = maxlen;
1454 first_addr = addr;
1455 /* skip over rest of c-client group addr */
1456 if(first_addr && first_addr->mailbox && !first_addr->host){
1457 for(second_addr = first_addr->next;
1458 second_addr && second_addr->host;
1459 second_addr = second_addr->next)
1462 if(second_addr && !second_addr->host)
1463 second_addr = second_addr->next;
1465 else if(!(first_addr && first_addr->host && first_addr->host[0] == '.'))
1466 second_addr = first_addr ? first_addr->next : NULL;
1468 if(second_addr && second_addr->mailbox && !second_addr->host){
1469 for(third_addr = second_addr->next;
1470 third_addr && third_addr->host;
1471 third_addr = third_addr->next)
1474 if(third_addr && !third_addr->host)
1475 third_addr = third_addr->next;
1477 else if(!(second_addr && second_addr->host && second_addr->host[0] == '.'))
1478 third_addr = second_addr ? second_addr->next : NULL;
1480 if(third_addr && third_addr->mailbox && !third_addr->host){
1481 for(fourth_addr = third_addr->next;
1482 fourth_addr && fourth_addr->host;
1483 fourth_addr = fourth_addr->next)
1486 if(fourth_addr && !fourth_addr->host)
1487 fourth_addr = fourth_addr->next;
1489 else if(!(third_addr && third_addr->host && third_addr->host[0] == '.'))
1490 fourth_addr = third_addr ? third_addr->next : NULL;
1492 /* Just attempting to make a nice display */
1493 if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
1494 (first_addr->mailbox && first_addr->mailbox[0]))){
1495 if(second_addr){
1496 if((second_addr->personal && second_addr->personal[0]) ||
1497 (second_addr->mailbox && second_addr->mailbox[0])){
1498 if(third_addr){
1499 if((third_addr->personal && third_addr->personal[0]) ||
1500 (third_addr->mailbox && third_addr->mailbox[0])){
1501 if(fourth_addr)
1502 cntaddr = 4;
1503 else
1504 cntaddr = 3;
1506 else
1507 cntaddr = -1;
1509 else
1510 cntaddr = 2;
1512 else
1513 cntaddr = -1;
1515 else
1516 cntaddr = 1;
1518 else
1519 cntaddr = -1;
1521 p = buf;
1522 if(cntaddr == 1)
1523 a_little_addr_string(first_addr, p, maxlen);
1524 else if(cntaddr == 2){
1525 a_little_addr_string(first_addr, p, maxlen);
1526 maxlen -= (l=strlen(p));
1527 p += l;
1528 if(maxlen > 7){
1529 strncpy(p, " and ", maxlen);
1530 maxlen -= 5;
1531 p += 5;
1532 a_little_addr_string(second_addr, p, maxlen);
1535 else if(cntaddr == 3){
1536 a_little_addr_string(first_addr, p, maxlen);
1537 maxlen -= (l=strlen(p));
1538 p += l;
1539 if(maxlen > 7){
1540 strncpy(p, ", ", maxlen);
1541 maxlen -= 2;
1542 p += 2;
1543 a_little_addr_string(second_addr, p, maxlen);
1544 maxlen -= (l=strlen(p));
1545 p += l;
1546 if(maxlen > 7){
1547 strncpy(p, ", and ", maxlen);
1548 maxlen -= 6;
1549 p += 6;
1550 a_little_addr_string(third_addr, p, maxlen);
1554 else if(cntaddr > 3){
1555 a_little_addr_string(first_addr, p, maxlen);
1556 maxlen -= (l=strlen(p));
1557 p += l;
1558 if(maxlen > 7){
1559 strncpy(p, ", ", maxlen);
1560 maxlen -= 2;
1561 p += 2;
1562 a_little_addr_string(second_addr, p, maxlen);
1563 maxlen -= (l=strlen(p));
1564 p += l;
1565 if(maxlen > 7){
1566 strncpy(p, ", ", maxlen);
1567 maxlen -= 2;
1568 p += 2;
1569 a_little_addr_string(third_addr, p, maxlen);
1570 maxlen -= (l=strlen(p));
1571 p += l;
1572 if(maxlen >= 12)
1573 strncpy(p, ", and others", maxlen);
1574 else if(maxlen >= 3)
1575 strncpy(p, "...", maxlen);
1579 else if(addr){
1580 char *a_string;
1582 a_string = addr_list_string(addr, NULL, 0);
1583 iutf8ncpy(buf, a_string, maxlen);
1585 fs_give((void **)&a_string);
1588 if(last_to)
1589 last_to->next = NULL;
1591 buf[orig_maxlen] = '\0';
1596 * Buf is at least size maxlen+1
1598 void
1599 get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1601 int cntnews = 0, orig_maxlen;
1602 char *news = NULL, *p, *q;
1604 switch(type){
1605 case iNews:
1606 case iNewsAndTo:
1607 case iToAndNews:
1608 case iNewsAndRecips:
1609 case iRecipsAndNews:
1610 news = env ? env->newsgroups : NULL;
1611 break;
1613 case iCurNews:
1614 if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream))
1615 news = ps_global->cur_folder;
1617 break;
1619 default:
1620 break;
1623 orig_maxlen = maxlen;
1625 if(news){
1626 q = news;
1627 while(isspace((unsigned char)*q))
1628 q++;
1630 if(*q)
1631 cntnews++;
1633 while((q = strindex(q, ',')) != NULL){
1634 q++;
1635 while(isspace((unsigned char)*q))
1636 q++;
1638 if(*q)
1639 cntnews++;
1640 else
1641 break;
1645 if(cntnews == 1){
1646 istrncpy(buf, news, maxlen);
1647 buf[maxlen] = '\0';
1648 removing_leading_and_trailing_white_space(buf);
1650 else if(cntnews == 2){
1651 p = buf;
1652 q = news;
1653 while(isspace((unsigned char)*q))
1654 q++;
1656 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1657 *p++ = *q++;
1658 maxlen--;
1661 if(maxlen > 7){
1662 strncpy(p, " and ", maxlen);
1663 p += 5;
1664 maxlen -= 5;
1667 while(isspace((unsigned char)*q) || *q == ',')
1668 q++;
1670 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1671 *p++ = *q++;
1672 maxlen--;
1675 *p = '\0';
1677 istrncpy(tmp_20k_buf, buf, 10000);
1678 strncpy(buf, tmp_20k_buf, orig_maxlen);
1680 else if(cntnews > 2){
1681 char b[100];
1683 p = buf;
1684 q = news;
1685 while(isspace((unsigned char)*q))
1686 q++;
1688 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1689 *p++ = *q++;
1690 maxlen--;
1693 *p = '\0';
1694 snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1);
1695 b[sizeof(b)-1] = '\0';
1696 if(maxlen >= strlen(b))
1697 strncpy(p, b, maxlen);
1698 else if(maxlen >= 3)
1699 strncpy(p, "...", maxlen);
1701 buf[orig_maxlen] = '\0';
1703 istrncpy(tmp_20k_buf, buf, 10000);
1704 tmp_20k_buf[10000-1] = '\0';
1705 strncpy(buf, tmp_20k_buf, orig_maxlen);
1708 buf[orig_maxlen] = '\0';
1711 void
1712 shorten_subject(char *origsubj)
1714 char *s, *t, *u;
1715 int endlist = 0;
1717 if(origsubj == NULL || *origsubj == '\0')
1718 return;
1720 for(t=s=origsubj; *s ; s++){
1721 switch(*s){
1722 /* this transforms "A [B [C] D" into "A D" should this be
1723 * "A [B D"?
1725 case '[' : if((u = strchr(s+1,']')) != NULL){
1726 s = u;
1727 endlist = 1;
1729 else
1730 *t++ = *s;
1731 break;
1732 case ' ' : if(endlist == 0) *t++ = *s; break;
1733 default : endlist = 0; *t++ = *s; break;
1736 *t = '\0';
1740 * Buf is at least size maxlen+1
1742 char *
1743 get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen)
1745 char *space = NULL;
1746 IndexColType addrtype;
1748 buf[0] = '\0';
1750 switch(type){
1751 case iRDate: case iSDate: case iSTime: case iSTime24:
1752 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
1753 case iSDateIso: case iSDateIsoS:
1754 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1755 case iSDateTime:
1756 case iSDateTimeIso: case iSDateTimeIsoS:
1757 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1758 case iSDateTime24:
1759 case iSDateTimeIso24: case iSDateTimeIsoS24:
1760 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1761 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
1762 case iDay: case iDayOrdinal: case iDay2Digit:
1763 case iMonAbb: case iMonLong: case iMon: case iMon2Digit:
1764 case iYear: case iYear2Digit:
1765 case iDate: case iLDate:
1766 case iTimezone: case iDayOfWeekAbb: case iDayOfWeek:
1767 case iPrefDate: case iPrefTime: case iPrefDateTime:
1768 if(env && env->date && env->date[0] && maxlen >= 20)
1769 date_str((char *) env->date, type, 1, buf, maxlen+1, 0);
1771 break;
1773 case iCurDate:
1774 case iCurDateIso:
1775 case iCurDateIsoS:
1776 case iCurTime24:
1777 case iCurTime12:
1778 case iCurDay:
1779 case iCurDay2Digit:
1780 case iCurDayOfWeek:
1781 case iCurDayOfWeekAbb:
1782 case iCurMon:
1783 case iCurMon2Digit:
1784 case iCurMonLong:
1785 case iCurMonAbb:
1786 case iCurYear:
1787 case iCurYear2Digit:
1788 case iCurPrefDate:
1789 case iCurPrefDateTime:
1790 case iCurPrefTime:
1791 case iLstMon:
1792 case iLstMon2Digit:
1793 case iLstMonLong:
1794 case iLstMonAbb:
1795 case iLstMonYear:
1796 case iLstMonYear2Digit:
1797 case iLstYear:
1798 case iLstYear2Digit:
1799 if(maxlen >= 20)
1800 date_str(NULL, type, 1, buf, maxlen+1, 0);
1802 break;
1804 case iFrom:
1805 case iTo:
1806 case iCc:
1807 case iSender:
1808 case iRecips:
1809 case iInit:
1810 get_addr_data(env, type, buf, maxlen);
1811 break;
1813 case iRoleNick:
1814 if(role && role->nick){
1815 strncpy(buf, role->nick, maxlen);
1816 buf[maxlen] = '\0';
1818 break;
1820 case iNewLine:
1821 if(maxlen >= strlen(NEWLINE)){
1822 strncpy(buf, NEWLINE, maxlen);
1823 buf[maxlen] = '\0';
1825 break;
1827 case iAddress:
1828 case iMailbox:
1829 if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
1830 strlen(env->from->mailbox) <= maxlen){
1831 strncpy(buf, env->from->mailbox, maxlen);
1832 buf[maxlen] = '\0';
1833 if(type == iAddress &&
1834 env->from->host &&
1835 env->from->host[0] &&
1836 env->from->host[0] != '.' &&
1837 strlen(buf) + strlen(env->from->host) + 1 <= maxlen){
1838 strncat(buf, "@", maxlen+1-1-strlen(buf));
1839 buf[maxlen] = '\0';
1840 strncat(buf, env->from->host, maxlen+1-1-strlen(buf));
1841 buf[maxlen] = '\0';
1845 break;
1847 case iNews:
1848 case iCurNews:
1849 get_news_data(env, type, buf, maxlen);
1850 break;
1852 case iToAndNews:
1853 case iNewsAndTo:
1854 case iRecipsAndNews:
1855 case iNewsAndRecips:
1856 if(type == iToAndNews || type == iNewsAndTo)
1857 addrtype = iTo;
1858 else
1859 addrtype = iRecips;
1861 if(env && env->newsgroups){
1862 space = (char *)fs_get((maxlen+1) * sizeof(char));
1863 get_news_data(env, type, space, maxlen);
1866 get_addr_data(env, addrtype, buf, maxlen);
1868 if(space && *space && *buf){
1869 if(strlen(space) + strlen(buf) + 5 > maxlen){
1870 if(strlen(space) > maxlen/2)
1871 get_news_data(env, type, space, maxlen - strlen(buf) - 5);
1872 else
1873 get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
1876 if(type == iToAndNews || type == iRecipsAndNews)
1877 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space);
1878 else
1879 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf);
1881 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1883 strncpy(buf, tmp_20k_buf, maxlen);
1884 buf[maxlen] = '\0';
1886 else if(space && *space){
1887 strncpy(buf, space, maxlen);
1888 buf[maxlen] = '\0';
1891 if(space)
1892 fs_give((void **)&space);
1894 break;
1896 case iSubject:
1897 case iShortSubject:
1898 if(env && env->subject){
1899 size_t n, len;
1900 unsigned char *p, *tmp = NULL;
1902 if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){
1903 len = n+1;
1904 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
1906 else{
1907 len = SIZEOF_20KBUF;
1908 p = (unsigned char *)tmp_20k_buf;
1911 istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen);
1913 buf[maxlen] = '\0';
1915 if(tmp)
1916 fs_give((void **)&tmp);
1918 if(type == iShortSubject)
1919 shorten_subject(buf);
1922 break;
1924 case iMsgID:
1925 if(env && env->message_id){
1926 strncpy(buf, env->message_id, maxlen);
1927 buf[maxlen] = '\0';
1930 break;
1932 default:
1933 break;
1936 buf[maxlen] = '\0';
1937 return(buf);
1942 * reply_delimiter - output formatted reply delimiter for given envelope
1943 * with supplied character writing function.
1945 void
1946 reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc)
1948 #define MAX_DELIM 2000
1949 char buf[MAX_DELIM+1];
1950 char *p;
1951 char *filtered = NULL;
1952 int contains_newline_token = 0;
1955 if(!env)
1956 return;
1958 strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
1959 buf[MAX_DELIM] = '\0';
1960 /* preserve exact default behavior from before */
1961 if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
1962 struct date d;
1963 int include_date;
1965 parse_date((char *) env->date, &d);
1966 include_date = !(d.day == -1 || d.month == -1 || d.year == -1);
1967 if(include_date){
1968 gf_puts("On ", pc); /* All delims have... */
1969 if(d.wkday != -1){ /* "On day, date month year" */
1970 gf_puts(day_abbrev(d.wkday), pc); /* in common */
1971 gf_puts(", ", pc);
1974 gf_puts(int2string(d.day), pc);
1975 (*pc)(' ');
1976 gf_puts(month_abbrev(d.month), pc);
1977 (*pc)(' ');
1978 gf_puts(int2string(d.year), pc);
1981 if(env->from
1982 && ((env->from->personal && env->from->personal[0])
1983 || (env->from->mailbox && env->from->mailbox[0]))){
1984 char buftmp[MAILTMPLEN];
1986 a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1);
1987 if(include_date)
1988 gf_puts(", ", pc);
1990 gf_puts(buftmp, pc);
1991 gf_puts(" wrote:", pc);
1993 else{
1994 if(include_date)
1995 gf_puts(", it was written", pc);
1996 else
1997 gf_puts("It was written", pc);
2001 else{
2003 * This is here for backwards compatibility. There didn't used
2004 * to be a _NEWLINE_ token. The user would enter text that should
2005 * all fit on one line and then that was followed by two newlines.
2006 * Also, truncation occurs if it is long.
2007 * Now, if _NEWLINE_ is not in the text, same thing still works
2008 * the same. However, if _NEWLINE_ is in there, then all bets are
2009 * off and the user is on his or her own. No automatic newlines
2010 * are added, only those that come from the tokens. No truncation
2011 * is done, the user is trusted to get it right. Newlines may be
2012 * embedded so that the leadin is multi-line.
2014 contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL);
2015 filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role,
2016 NULL, NULL);
2018 /* try to truncate if too long */
2019 if(!contains_newline_token && filtered && utf8_width(filtered) > 80){
2020 int ended_with_colon = 0;
2021 int ended_with_quote = 0;
2022 int ended_with_quote_colon = 0;
2023 int l;
2025 l = strlen(filtered);
2027 if(filtered[l-1] == ':'){
2028 ended_with_colon = ':';
2029 if(filtered[l-2] == QUOTE || filtered[l-2] == '\'')
2030 ended_with_quote_colon = filtered[l-2];
2032 else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'')
2033 ended_with_quote = filtered[l-1];
2035 /* try to find space to break at */
2036 for(p = &filtered[75]; p > &filtered[60] &&
2037 !isspace((unsigned char)*p); p--)
2040 if(!isspace((unsigned char)*p))
2041 p = &filtered[75];
2043 *p++ = '.';
2044 *p++ = '.';
2045 *p++ = '.';
2046 if(ended_with_quote_colon){
2047 *p++ = ended_with_quote_colon;
2048 *p++ = ':';
2050 else if(ended_with_colon)
2051 *p++ = ended_with_colon;
2052 else if(ended_with_quote)
2053 *p++ = ended_with_quote;
2055 *p = '\0';
2058 if(filtered && *filtered)
2059 gf_puts(filtered, pc);
2062 /* and end with two newlines unless no leadin at all */
2063 if(!contains_newline_token){
2064 if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
2065 gf_puts(NEWLINE, pc);
2066 gf_puts(NEWLINE, pc);
2070 if(filtered)
2071 fs_give((void **)&filtered);
2075 void
2076 free_redraft_pos(REDRAFT_POS_S **redraft_pos)
2078 if(redraft_pos && *redraft_pos){
2079 if((*redraft_pos)->hdrname)
2080 fs_give((void **)&(*redraft_pos)->hdrname);
2082 fs_give((void **)redraft_pos);
2087 /*----------------------------------------------------------------------
2088 Build the body for the message number/part being forwarded as ATTACHMENT
2090 Args:
2092 Result: PARTS suitably attached to body
2094 ----------------------------------------------------------------------*/
2096 forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext)
2098 char *tmp_text;
2099 unsigned long len;
2100 BODY *b;
2102 *partp = mail_newbody_part();
2103 b = &(*partp)->body;
2104 b->type = TYPEMESSAGE;
2105 b->id = generate_message_id();
2106 b->description = cpystr("Forwarded Message");
2107 b->nested.msg = mail_newmsg();
2108 b->disposition.type = cpystr("inline");
2110 /*---- Package each message in a storage object ----*/
2111 if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
2112 && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
2113 && *tmp_text){
2114 so_puts((STORE_S *) b->contents.text.data, tmp_text);
2116 b->size.bytes = strlen(tmp_text);
2117 so_puts((STORE_S *) b->contents.text.data, "\015\012");
2118 if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){
2119 so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
2120 b->size.bytes += len;
2121 return(1);
2125 return(0);
2130 * forward_delimiter - return delimiter for forwarded text
2132 void
2133 forward_delimiter(gf_io_t pc)
2135 gf_puts(NEWLINE, pc);
2136 /* TRANSLATORS: When a message is forwarded by the user this is the
2137 text that shows where the forwarded part of the message begins. */
2138 gf_puts(_("---------- Forwarded message ----------"), pc);
2139 gf_puts(NEWLINE, pc);
2143 /*----------------------------------------------------------------------
2144 Wrapper for header formatting tool
2146 Args: stream --
2147 msgno --
2148 env --
2149 pc --
2150 prefix --
2152 Result: header suitable for reply/forward text written using "pc"
2154 ----------------------------------------------------------------------*/
2155 void
2156 reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env,
2157 gf_io_t pc, char *prefix)
2159 int rv;
2160 HEADER_S h;
2161 char **list, **new_list = NULL;
2163 list = ps_global->VAR_VIEW_HEADERS;
2166 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2167 * the user doesn't inadvertently forward the BCC header.
2169 if(list && list[0]){
2170 int i, cnt = 0;
2171 char **p;
2173 while(list[cnt++])
2176 p = new_list = (char **) fs_get((cnt+1) * sizeof(char *));
2178 for(i=0; list[i]; i++)
2179 if(strucmp(list[i], "bcc"))
2180 *p++ = cpystr(list[i]);
2182 *p = NULL;
2184 if(new_list && new_list[0])
2185 list = new_list;
2189 HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC);
2190 if((rv = format_header(stream, msgno, part, env, &h,
2191 prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){
2192 if(rv == 1)
2193 gf_puts(" [Error fetching message header data]", pc);
2195 else
2196 gf_puts(NEWLINE, pc); /* write header delimiter */
2198 if(new_list)
2199 free_list_array(&new_list);
2203 /*----------------------------------------------------------------------
2204 Build the subject for the message number being forwarded
2206 Args: pine_state -- The usual pine structure
2207 msgno -- The message number to build subject for
2209 Result: malloc'd string containing new subject or NULL on error
2211 ----------------------------------------------------------------------*/
2212 char *
2213 forward_subject(ENVELOPE *env, int flags)
2215 size_t l;
2216 char *p, buftmp[MAILTMPLEN];
2218 if(!env)
2219 return(NULL);
2221 dprint((9, "checking subject: \"%s\"\n",
2222 env->subject ? env->subject : "NULL"));
2224 if(env->subject && env->subject[0]){ /* add (fwd)? */
2225 snprintf(buftmp, sizeof(buftmp), "%s", env->subject);
2226 buftmp[sizeof(buftmp)-1] = '\0';
2227 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2228 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
2229 SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp)
2230 strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF);
2232 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2234 removing_trailing_white_space(tmp_20k_buf);
2235 if((l = strlen(tmp_20k_buf)) < 1000 &&
2236 (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){
2237 snprintf(tmp_20k_buf+2000, SIZEOF_20KBUF-2000, "%s (fwd)", tmp_20k_buf);
2238 tmp_20k_buf[SIZEOF_20KBUF-2000-1] = '\0';
2239 memmove(tmp_20k_buf, tmp_20k_buf+2000, strlen(tmp_20k_buf+2000));
2240 tmp_20k_buf[strlen(tmp_20k_buf+2000)] = '\0';
2244 * HACK: composer can't handle embedded double quotes in attachment
2245 * comments so we substitute two single quotes.
2247 if(flags & FS_CONVERT_QUOTES)
2248 while((p = strchr(tmp_20k_buf, QUOTE)) != NULL)
2249 (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''");
2251 return(cpystr(tmp_20k_buf));
2255 return(cpystr("Forwarded mail...."));
2259 /*----------------------------------------------------------------------
2260 Build the body for the message number/part being forwarded
2262 Args:
2264 Result: BODY structure suitable for sending
2266 ----------------------------------------------------------------------*/
2267 BODY *
2268 forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
2269 long int msgno, char *sect_prefix, void *msgtext, int flags)
2271 BODY *body = NULL, *text_body, *tmp_body;
2272 PART *part;
2273 gf_io_t pc;
2274 char *tmp_text, *section, sect_buf[256];
2275 int forward_raw_body = 0;
2278 * Check to see if messages got expunged out from underneath us. This
2279 * could have happened during the prompt to the user asking whether to
2280 * include the message as an attachment. Either the message is gone or
2281 * it might be at a different sequence number. We'd better bail.
2283 if(ps_global->full_header == 2
2284 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
2285 forward_raw_body = 1;
2286 if(sp_expunge_count(stream))
2287 return(NULL);
2289 if(sect_prefix && forward_raw_body == 0)
2290 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
2291 else if(sect_prefix && forward_raw_body)
2292 section = sect_prefix;
2293 else if(!sect_prefix && forward_raw_body)
2294 section = NULL;
2295 else
2296 section = "1";
2298 sect_buf[sizeof(sect_buf)-1] = '\0';
2300 gf_set_so_writec(&pc, (STORE_S *) msgtext);
2301 if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) {
2302 char *charset = NULL;
2304 /*---- Message has a single text part -----*/
2305 body = mail_newbody();
2306 body->type = TYPETEXT;
2307 body->contents.text.data = msgtext;
2308 if(orig_body
2309 && (charset = parameter_val(orig_body->parameter, "charset")))
2310 set_parameter(&body->parameter, "charset", charset);
2312 if(charset)
2313 fs_give((void **) &charset);
2315 if(!(flags & FWD_ANON)){
2316 forward_delimiter(pc);
2317 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
2320 if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body,
2321 msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){
2322 mail_free_body(&body);
2323 return(NULL);
2326 else if(orig_body->type == TYPEMULTIPART) {
2327 if(orig_body->subtype
2328 && ((!strucmp(orig_body->subtype, "signed") && orig_body->nested.part)
2329 #ifdef SMIME
2330 || (!strucmp(orig_body->subtype, "mixed")
2331 && orig_body->nested.part
2332 && orig_body->nested.part->body.type == TYPEMULTIPART
2333 && orig_body->nested.part->body.subtype
2334 && (!strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2335 || !strucmp(orig_body->nested.part->body.subtype, "signed")))
2336 || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2337 #endif /* SMIME */
2339 /* only operate on the signed data (not the signature) */
2340 body = forward_body(stream, env, &orig_body->nested.part->body,
2341 msgno,
2342 orig_body->nested.part->body.type == TYPEMULTIPART
2343 ? section : sect_prefix, msgtext, flags);
2345 /*---- Message is multipart ----*/
2346 else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
2347 "alternative")
2348 && (body = forward_multi_alt(stream, env, orig_body, msgno,
2349 sect_prefix, msgtext,
2350 pc, flags)))){
2351 /*--- Copy the body and entire structure ---*/
2352 body = copy_body(NULL, orig_body);
2355 * whatever subtype it is, demote it
2356 * to plain old MIXED.
2358 if(body->subtype)
2359 fs_give((void **) &body->subtype);
2361 body->subtype = cpystr("Mixed");
2363 /*--- The text part of the message ---*/
2364 if(!body->nested.part){
2365 q_status_message(SM_ORDER | SM_DING, 3, 6,
2366 "Error referencing body part 1");
2367 mail_free_body(&body);
2369 else if(body->nested.part->body.type == TYPETEXT) {
2370 char *new_charset = NULL;
2372 /*--- The first part is text ----*/
2373 text_body = &body->nested.part->body;
2374 text_body->contents.text.data = msgtext;
2375 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
2376 /* this text is going to the composer, it should be Plain */
2377 fs_give((void **)&text_body->subtype);
2378 text_body->subtype = cpystr("PLAIN");
2380 if(!(flags & FWD_ANON)){
2381 forward_delimiter(pc);
2382 reply_forward_header(stream, msgno,
2383 sect_prefix, env, pc, "");
2386 if(!(get_body_part_text(stream, &orig_body->nested.part->body,
2387 msgno, section, 0L, pc,
2388 NULL, &new_charset, GBPT_NONE)
2389 && fetch_contents(stream, msgno, sect_prefix, body)))
2390 mail_free_body(&body);
2391 else if(new_charset)
2392 set_parameter(&text_body->parameter, "charset", new_charset);
2394 /* BUG: ? matter that we're not setting body.size.bytes */
2396 else if(body->nested.part->body.type == TYPEMULTIPART
2397 && body->nested.part->body.subtype
2398 && !strucmp(body->nested.part->body.subtype, "alternative")
2399 && (tmp_body = forward_multi_alt(stream, env,
2400 &body->nested.part->body,
2401 msgno, sect_prefix,
2402 msgtext, pc,
2403 flags | FWD_NESTED))){
2404 /* for the forward_multi_alt call above, we want to pass
2405 * sect_prefix instead of section so we can obtain the header.
2406 * Set the FWD_NESTED flag so we fetch the right body_part.
2408 int partnum;
2410 part = body->nested.part->next;
2411 body->nested.part->next = NULL;
2412 mail_free_body_part(&body->nested.part);
2413 body->nested.part = mail_newbody_part();
2414 body->nested.part->body = *tmp_body;
2415 body->nested.part->next = part;
2417 for(partnum = 2; part != NULL; part = part->next){
2418 snprintf(sect_buf, sizeof(sect_buf), "%s%s%d",
2419 sect_prefix ? sect_prefix : "",
2420 sect_prefix ? "." : "", partnum++);
2421 sect_buf[sizeof(sect_buf)-1] = '\0';
2423 if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
2424 mail_free_body(&body);
2425 break;
2429 else {
2430 if(fetch_contents(stream, msgno, sect_prefix, body)){
2431 /*--- Create a new blank text part ---*/
2432 part = mail_newbody_part();
2433 part->next = body->nested.part;
2434 body->nested.part = part;
2435 part->body.contents.text.data = msgtext;
2437 else
2438 mail_free_body(&body);
2442 else {
2443 /*---- A single part message, not of type text ----*/
2444 body = mail_newbody();
2445 body->type = TYPEMULTIPART;
2446 part = mail_newbody_part();
2447 body->nested.part = part;
2449 /*--- The first part, a blank text part to be edited ---*/
2450 part->body.type = TYPETEXT;
2451 part->body.contents.text.data = msgtext;
2453 /*--- The second part, what ever it is ---*/
2454 part->next = mail_newbody_part();
2455 part = part->next;
2456 part->body.id = generate_message_id();
2457 copy_body(&(part->body), orig_body);
2460 * the idea here is to fetch part into storage object
2462 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
2463 EDIT_ACCESS)) != NULL){
2464 if((tmp_text = pine_mail_fetch_body(stream, msgno, section,
2465 &part->body.size.bytes, NIL)) != NULL)
2466 so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
2467 part->body.size.bytes);
2468 else
2469 mail_free_body(&body);
2471 else
2472 mail_free_body(&body);
2475 gf_clear_so_writec((STORE_S *) msgtext);
2477 return(body);
2483 * bounce_msg_body - build body from specified message suitable
2484 * for sending as bounced message
2486 char *
2487 bounce_msg_body(MAILSTREAM *stream,
2488 long int rawno,
2489 char *part,
2490 char **to,
2491 char *subject,
2492 ENVELOPE **outgoingp,
2493 BODY **bodyp,
2494 int *seenp)
2496 char *h, *p, *errstr = NULL;
2497 int i;
2498 STORE_S *msgtext;
2499 gf_io_t pc;
2501 *outgoingp = mail_newenvelope();
2502 (*outgoingp)->message_id = generate_message_id();
2503 (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail....");
2506 * Fill in destination if we were given one. If so, note that we
2507 * call p_s_s() below such that it won't prompt...
2509 if(to && *to){
2510 static char *fakedomain = "@";
2511 char *tmp_a_string;
2513 /* rfc822_parse_adrlist feels free to destroy input so copy */
2514 tmp_a_string = cpystr(*to);
2515 rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string,
2516 (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
2517 ? fakedomain : ps_global->maildomain);
2518 fs_give((void **) &tmp_a_string);
2521 /* build remail'd header */
2522 if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){
2523 for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++)
2524 i++;
2526 /* allocate it */
2527 (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);
2530 * copy it, "X-"ing out transport headers bothersome to
2531 * software but potentially useful to the human recipient...
2533 p = (*outgoingp)->remail;
2534 bounce_mask_header(&p, h);
2536 if(*h == '\015' && *(h+1) == '\012'){
2537 *p++ = *h++; /* copy CR LF */
2538 *p++ = *h++;
2539 bounce_mask_header(&p, h);
2541 while((*p++ = *h++) != '\0');
2543 /* BUG: else complain? */
2545 /* NOT bound for the composer, so no need for PicoText */
2546 if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){
2547 mail_free_envelope(outgoingp);
2548 return(_("Error allocating message text"));
2551 /* mark object for special handling */
2552 so_attr(msgtext, "rawbody", "1");
2555 * Build a fake body description. It's ignored by pine_rfc822_header,
2556 * but we need to set it to something that makes set_mime_types
2557 * not sniff it and pine_rfc822_output_body not re-encode it.
2558 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2559 * problems unless something tries to access body_encodings[] using
2560 * it without proper precautions. We don't want to use ENCOTHER
2561 * cause that tells set_mime_types to sniff it, and we don't want to
2562 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2563 * it. When there's time, it'd be nice to clean this interaction
2564 * up...
2566 *bodyp = mail_newbody();
2567 (*bodyp)->type = TYPETEXT;
2568 (*bodyp)->encoding = ENCMAX + 1;
2569 (*bodyp)->subtype = cpystr("Plain");
2570 (*bodyp)->contents.text.data = (void *) msgtext;
2571 gf_set_so_writec(&pc, msgtext);
2573 if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){
2574 MESSAGECACHE *mc;
2576 if((mc = mail_elt(stream, rawno)) != NULL)
2577 *seenp = mc->seen;
2580 /* pass NULL body to force mail_fetchtext */
2581 if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE))
2582 errstr = _("Error fetching message contents. Can't Bounce message");
2584 gf_clear_so_writec(msgtext);
2586 return(errstr);
2591 /*----------------------------------------------------------------------
2592 Mask off any header entries we don't want xport software to see
2594 Args: d -- destination string pointer pointer
2595 s -- source string pointer pointer
2597 Postfix uses Delivered-To to detect loops.
2598 Received line counting is also used to detect loops in places.
2600 ----*/
2601 void
2602 bounce_mask_header(char **d, char *s)
2604 if(((*s == 'R' || *s == 'r')
2605 && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8)
2606 || !struncmp(s+1, "eturn-Path", 10)))
2607 || !struncmp(s, "Delivered-To:", 13)){
2608 *(*d)++ = 'X'; /* write mask */
2609 *(*d)++ = '-';
2614 /*----------------------------------------------------------------------
2615 Fetch and format text for forwarding
2617 Args: stream -- Mail stream to fetch text from
2618 body -- Body structure of message being forwarded
2619 msg_no -- Message number of text for forward
2620 part_no -- Part number of text to forward
2621 partial -- If this is > 0 a partial fetch will be done and it will
2622 be done using FT_PEEK so the message will remain unseen.
2623 pc -- Function to write to
2624 prefix -- Prefix for each line
2625 ret_charset -- If we translate to another charset return that
2626 new charset here
2628 Returns: true if OK, false if problem occured while filtering
2630 If the text is richtext, it will be converted to plain text, since there's
2631 no rich text editing capabilities in Pine (yet).
2633 It's up to calling routines to plug in signature appropriately
2635 As with all internal text, NVT end-of-line conventions are observed.
2636 DOESN'T sanity check the prefix given!!!
2637 ----*/
2639 get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body,
2640 long int msg_no, char *part_no, long partial, gf_io_t pc,
2641 char *prefix, char **ret_charset, unsigned flags)
2643 int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0;
2644 FILTLIST_S filters[12];
2645 long len;
2646 char *err, *charset, *prefix_p = NULL;
2647 int filtcnt = 0;
2648 char *free_this = NULL;
2649 DELQ_S dq;
2651 memset(filters, 0, sizeof(filters));
2652 if(ret_charset)
2653 *ret_charset = NULL;
2655 if(!pc_is_picotext(pc))
2656 we_cancel = busy_cue(NULL, NULL, 1);
2658 /* if null body, we must be talking to a non-IMAP2bis server.
2659 * No MIME parsing provided, so we just grab the message text...
2661 if(body == NULL){
2662 char *text, *decode_error;
2663 gf_io_t gc;
2664 SourceType src = CharStar;
2665 int rv = 0;
2667 (void) pine_mail_fetchstructure(stream, msg_no, NULL);
2669 if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){
2670 gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0);
2672 gf_filter_init(); /* no filters needed */
2673 if(prefix)
2674 gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
2675 if((decode_error = gf_pipe(gc, pc)) != NULL){
2676 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s",
2677 NEWLINE, NEWLINE,
2678 decode_error, NEWLINE);
2679 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2680 gf_puts(tmp_20k_buf, pc);
2681 rv++;
2684 else{
2685 gf_puts(NEWLINE, pc);
2686 gf_puts(_(" [ERROR fetching text of message]"), pc);
2687 gf_puts(NEWLINE, pc);
2688 gf_puts(NEWLINE, pc);
2689 rv++;
2692 if(we_cancel)
2693 cancel_busy_cue(-1);
2695 return(rv == 0);
2698 charset = parameter_val(body->parameter, "charset");
2700 if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
2701 if(ret_charset)
2702 *ret_charset = "UTF-8";
2705 if(charset)
2706 fs_give((void **) &charset);
2709 * just use detach, but add an auxiliary filter to insert prefix,
2710 * and, perhaps, digest richtext
2712 if(ps_global->full_header != 2
2713 && !ps_global->postpone_no_flow
2714 && (!body->subtype || !strucmp(body->subtype, "plain"))){
2715 char *parmval;
2717 flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
2718 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
2719 && (!prefix || (strucmp(prefix,"> ") == 0)
2720 || strucmp(prefix, ">") == 0));
2721 if((parmval = parameter_val(body->parameter,
2722 "format")) != NULL){
2723 if(!strucmp(parmval, "flowed")){
2724 wrapflags |= GFW_FLOWED;
2726 fs_give((void **) &parmval);
2727 if((parmval = parameter_val(body->parameter, "delsp")) != NULL){
2728 if(!strucmp(parmval, "yes")){
2729 filters[filtcnt++].filter = gf_preflow;
2730 wrapflags |= GFW_DELSP;
2733 fs_give((void **) &parmval);
2737 * if there's no prefix we're forwarding text
2738 * otherwise it's reply text. unless the user's
2739 * tied our hands, alter the prefix to continue flowed
2740 * formatting...
2742 if(flow_res && ps_global->reply.use_flowed)
2743 wrapflags |= GFW_FLOW_RESULT;
2745 filters[filtcnt].filter = gf_wrap;
2747 * The 80 will cause longer lines than what is likely
2748 * set by composer_fillcol, so we'll have longer
2749 * quoted and forwarded lines than the lines we type.
2750 * We're fine with that since the alternative is the
2751 * possible wrapping of lines we shouldn't have, which
2752 * is mitigated by this higher 80 limit.
2754 * If we were to go back to using composer_fillcol,
2755 * the correct value is composer_fillcol + 1, pine
2756 * is off-by-one from pico.
2758 filters[filtcnt++].data = gf_wrap_filter_opt(
2759 MAX(MIN(ps_global->ttyo->screen_cols
2760 - (prefix ? strlen(prefix) : 0),
2761 80 - (prefix ? strlen(prefix) : 0)),
2762 30), /* doesn't have to be 30 */
2763 990, /* 998 is the SMTP limit */
2764 NULL, 0, wrapflags);
2769 * if not flowed, remove trailing whitespace to reduce
2770 * confusion, since we're sending out as flowed if we
2771 * can. At some future point, we might try
2772 * plugging in a user-option-controlled heuristic
2773 * flowing filter
2775 * We also want to fold "> " quotes so we get the
2776 * attributions correct.
2778 if(flow_res && ps_global->reply.use_flowed && prefix && !strucmp(prefix, "> "))
2779 *(prefix_p = prefix + 1) = '\0';
2780 ps_global->reply.use_flowed = 1; /* reset for next call */
2781 if(!(wrapflags & GFW_FLOWED)
2782 && flow_res){
2783 filters[filtcnt].filter = gf_line_test;
2784 filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL);
2786 filters[filtcnt].filter = gf_line_test;
2787 filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL);
2790 else if(body->subtype){
2791 int plain_opt = 1;
2793 if(strucmp(body->subtype,"richtext") == 0){
2794 filters[filtcnt].filter = gf_rich2plain;
2795 filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
2797 else if(strucmp(body->subtype,"enriched") == 0){
2798 filters[filtcnt].filter = gf_enriched2plain;
2799 filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
2801 else if(strucmp(body->subtype,"html") == 0){
2802 if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){
2803 filters[filtcnt].filter = gf_html2plain;
2804 filters[filtcnt++].data = gf_html2plain_opt(NULL,
2805 ps_global->ttyo->screen_cols,
2806 non_messageview_margin(),
2807 NULL, NULL, GFHP_STRIPPED);
2812 if(prefix){
2813 if(ps_global->reply.strip_signature){
2814 dashdata = 0;
2815 filters[filtcnt].filter = gf_line_test;
2816 filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata);
2819 filters[filtcnt].filter = gf_prefix;
2820 filters[filtcnt++].data = gf_prefix_opt(prefix);
2822 if(wrapflags & GFW_FLOWED || flow_res){
2823 filters[filtcnt].filter = gf_line_test;
2824 filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL);
2828 if(flags & GBPT_DELQUOTES){
2829 memset(&dq, 0, sizeof(dq));
2830 dq.lines = Q_DEL_ALL;
2831 dq.is_flowed = 0;
2832 dq.indent_length = 0;
2833 dq.saved_line = &free_this;
2834 dq.handlesp = NULL;
2835 dq.do_color = 0;
2836 dq.delete_all = 1;
2838 filters[filtcnt].filter = gf_line_test;
2839 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
2842 err = detach(stream, msg_no, part_no, partial, &len, pc,
2843 filters[0].filter ? filters : NULL,
2844 ((flags & GBPT_PEEK) ? FT_PEEK : 0)
2845 | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0));
2847 if(free_this)
2848 fs_give((void **) &free_this);
2850 if(prefix_p)
2851 *prefix_p = ' ';
2853 if (err != (char *) NULL)
2854 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2855 q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
2856 err, (void *) msg_no);
2858 if(we_cancel)
2859 cancel_busy_cue(-1);
2861 return((int) len);
2866 quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local)
2868 char *p;
2870 if(*line == '>'){
2871 for(p = line; *p; p++){
2872 if(isspace((unsigned char) *p)){
2873 if(*(p+1) == '>')
2874 ins = gf_line_test_new_ins(ins, p, "", -1);
2876 else if(*p != '>')
2877 break;
2881 return(0);
2886 twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2888 char *p, *ws = NULL;
2890 for(p = line; *p; p++){
2891 /* don't strip trailing space on signature line */
2892 if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))
2893 break;
2895 if(isspace((unsigned char) *p)){
2896 if(!ws)
2897 ws = p;
2899 else
2900 ws = NULL;
2903 if(ws)
2904 ins = gf_line_test_new_ins(ins, ws, "", -(p - ws));
2906 return(0);
2910 post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local)
2912 char *p;
2914 for(p = line; *p; p++)
2915 if(*p != '>'){
2916 if(p != line && *p != ' ')
2917 ins = gf_line_test_new_ins(ins, p, " ", 1);
2919 break;
2922 return(0);
2927 sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2929 if(*((int *)local)
2930 || (*line == '-' && *(line+1) == '-'
2931 && *(line+2) == ' ' && !*(line+3))){
2932 *((int *) local) = 1;
2933 return(2); /* skip this line! */
2936 return(0);
2940 /*----------------------------------------------------------------------
2941 return the c-client reference name for the given end_body part
2942 ----*/
2943 char *
2944 body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body)
2946 BODY *body;
2948 (void) pine_mail_fetchstructure(stream, msgno, &body);
2949 return(partno(body, end_body));
2953 /*----------------------------------------------------------------------
2954 return the c-client reference name for the given end_body part
2955 ----*/
2956 char *
2957 partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body)
2959 #define PARTTMPLEN 64
2960 PART *part;
2961 int num = 0;
2962 char tmp[PARTTMPLEN], *p = NULL;
2964 if(body && body->type == TYPEMULTIPART) {
2965 part = body->nested.part; /* first body part */
2967 do { /* for each part */
2968 num++; /* PARTTMPLEN = sizeof(tmp) */
2969 if(&part->body == end_body || (p = partno(&part->body, end_body))){
2970 snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "",
2971 PARTTMPLEN-10, (p) ? p : "");
2972 tmp[sizeof(tmp)-1] = '\0';
2973 if(p)
2974 fs_give((void **)&p);
2976 return(cpystr(tmp));
2978 } while ((part = part->next) != NULL); /* until done */
2980 return(NULL);
2982 else if(body && body->type == TYPEMESSAGE && body->subtype
2983 && !strucmp(body->subtype, "rfc822")){
2984 return(partno(body->nested.msg->body, end_body));
2987 return((body == end_body) ? cpystr("1") : NULL);
2991 /*----------------------------------------------------------------------
2992 Fill in the contents of each body part
2994 Args: stream -- Stream the message is on
2995 msgno -- Message number the body structure is for
2996 section -- body section associated with body pointer
2997 body -- Body pointer to fill in
2999 Result: 1 if all went OK, 0 if there was a problem
3001 This function copies the contents from an original message/body to
3002 a new message/body. It recurses down all multipart levels.
3004 If one or more part (but not all) can't be fetched, a status message
3005 will be queued.
3006 ----*/
3008 fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body)
3010 char *tp;
3011 int got_one = 0;
3013 if(!body->id)
3014 body->id = generate_message_id();
3016 if(body->type == TYPEMULTIPART){
3017 char subsection[256], *subp;
3018 int n, last_one = 10; /* remember worst case */
3019 PART *part = body->nested.part;
3021 if(!(part = body->nested.part))
3022 return(0);
3024 subp = subsection;
3025 if(section && *section){
3026 for(n = 0;
3027 n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++)
3030 *subp++ = '.';
3033 n = 1;
3034 do {
3035 snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++);
3036 subsection[sizeof(subsection)-1] = '\0';
3037 got_one = fetch_contents(stream, msgno, subsection, &part->body);
3038 last_one = MIN(last_one, got_one);
3040 while((part = part->next) != NULL);
3042 return(last_one);
3045 if(body->contents.text.data)
3046 return(1); /* already taken care of... */
3048 if(body->type == TYPEMESSAGE){
3049 if(body->subtype && strucmp(body->subtype,"external-body")){
3051 * the idea here is to fetch everything into storage objects
3053 body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
3054 EDIT_ACCESS);
3055 if(body->contents.text.data
3056 && (tp = pine_mail_fetch_body(stream, msgno, section,
3057 &body->size.bytes, NIL))){
3058 so_truncate((STORE_S *)body->contents.text.data,
3059 body->size.bytes + 2048);
3060 so_nputs((STORE_S *)body->contents.text.data, tp,
3061 body->size.bytes);
3062 got_one = 1;
3064 else
3065 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3066 _("Error fetching part %s"), section);
3067 } else {
3068 got_one = 1;
3070 } else {
3072 * the idea here is to fetch everything into storage objects
3073 * so, grab one, then fetch the body part
3075 body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
3076 if(body->contents.text.data
3077 && (tp=pine_mail_fetch_body(stream, msgno, section,
3078 &body->size.bytes, NIL))){
3079 so_truncate((STORE_S *)body->contents.text.data,
3080 body->size.bytes + 2048);
3081 so_nputs((STORE_S *)body->contents.text.data, tp,
3082 body->size.bytes);
3083 got_one = 1;
3085 else
3086 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3087 _("Error fetching part %s"), section);
3090 return(got_one);
3094 /*----------------------------------------------------------------------
3095 Copy the body structure
3097 Args: new_body -- Pointer to already allocated body, or NULL, if none
3098 old_body -- The Body to copy
3101 This is traverses the body structure recursively copying all elements.
3102 The new_body parameter can be NULL in which case a new body is
3103 allocated. Alternatively it can point to an already allocated body
3104 structure. This is used when copying body parts since a PART includes a
3105 BODY. The contents fields are *not* filled in.
3106 ----*/
3108 BODY *
3109 copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body)
3111 if(old_body == NULL)
3112 return(NULL);
3114 if(new_body == NULL)
3115 new_body = mail_newbody();
3117 new_body->type = old_body->type;
3118 new_body->encoding = old_body->encoding;
3120 if(old_body->subtype)
3121 new_body->subtype = cpystr(old_body->subtype);
3123 new_body->parameter = copy_parameters(old_body->parameter);
3125 if(old_body->id)
3126 new_body->id = cpystr(old_body->id);
3128 if(old_body->description)
3129 new_body->description = cpystr(old_body->description);
3131 if(old_body->disposition.type)
3132 new_body->disposition.type = cpystr(old_body->disposition.type);
3134 new_body->disposition.parameter
3135 = copy_parameters(old_body->disposition.parameter);
3137 new_body->size = old_body->size;
3139 if(new_body->type == TYPEMESSAGE
3140 && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
3141 new_body->nested.msg = mail_newmsg();
3142 new_body->nested.msg->body
3143 = copy_body(NULL, old_body->nested.msg->body);
3145 else if(new_body->type == TYPEMULTIPART) {
3146 PART **new_partp, *old_part;
3148 new_partp = &new_body->nested.part;
3149 for(old_part = old_body->nested.part;
3150 old_part != NULL;
3151 old_part = old_part->next){
3152 *new_partp = mail_newbody_part();
3153 copy_body(&(*new_partp)->body, &old_part->body);
3154 new_partp = &(*new_partp)->next;
3158 return(new_body);
3162 /*----------------------------------------------------------------------
3163 Copy the MIME parameter list
3165 Allocates storage for new part, and returns pointer to new paramter
3166 list. If old_p is NULL, NULL is returned.
3167 ----*/
3168 PARAMETER *
3169 copy_parameters(PARAMETER *old_p)
3171 PARAMETER *new_p, *p1, *p2;
3173 if(old_p == NULL)
3174 return((PARAMETER *)NULL);
3176 new_p = p2 = NULL;
3177 for(p1 = old_p; p1 != NULL; p1 = p1->next){
3178 set_parameter(&p2, p1->attribute, p1->value);
3179 if(new_p == NULL)
3180 new_p = p2;
3183 return(new_p);
3187 /*----------------------------------------------------------------------
3188 Make a complete copy of an envelope and all it's fields
3190 Args: e -- the envelope to copy
3192 Result: returns the new envelope, or NULL, if the given envelope was NULL
3194 ----*/
3196 ENVELOPE *
3197 copy_envelope(register ENVELOPE *e)
3199 register ENVELOPE *e2;
3201 if(!e)
3202 return(NULL);
3204 e2 = mail_newenvelope();
3205 e2->remail = e->remail ? cpystr(e->remail) : NULL;
3206 e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
3207 e2->date = e->date ? (unsigned char *)cpystr((char *) e->date)
3208 : NULL;
3209 e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL;
3210 e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL;
3211 e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL;
3212 e2->subject = e->subject ? cpystr(e->subject) : NULL;
3213 e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL;
3214 e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL;
3215 e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL;
3216 e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL;
3217 e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL;
3218 e2->message_id = e->message_id ? cpystr(e->message_id) : NULL;
3219 e2->references = e->references ? cpystr(e->references) : NULL;
3220 e2->followup_to = e->followup_to ? cpystr(e->references) : NULL;
3221 return(e2);
3225 /*----------------------------------------------------------------------
3226 Generate the "In-reply-to" text from message header
3228 Args: message -- Envelope of original message
3230 Result: returns an alloc'd string or NULL if there is a problem
3231 ----*/
3232 char *
3233 reply_in_reply_to(ENVELOPE *env)
3235 return((env && env->message_id) ? cpystr(env->message_id) : NULL);
3239 /*----------------------------------------------------------------------
3240 Generate a unique message id string.
3242 Args: ps -- The usual pine structure
3244 Result: Alloc'd unique string is returned
3246 Uniqueness is gaurenteed by using the host name, process id, date to the
3247 second and a single unique character
3248 *----------------------------------------------------------------------*/
3249 char *
3250 generate_message_id(void)
3252 static short osec = 0, cnt = 0;
3253 char idbuf[128];
3254 char *id;
3255 time_t now;
3256 struct tm *now_x;
3257 char *hostpart = NULL;
3258 char *alpine_name = NULL;
3259 char *alpine_version = NULL;
3260 char *system_os = NULL;
3262 now = time((time_t *)0);
3263 now_x = localtime(&now);
3265 if(now_x->tm_sec == osec)
3266 cnt++;
3267 else{
3268 cnt = 0;
3269 osec = now_x->tm_sec;
3272 if(F_ON(F_ROT13_MESSAGE_ID, ps_global)){
3273 hostpart = rot13(ps_global->hostname);
3274 alpine_name = rot13("alpine");
3275 alpine_version = rot5n(ALPINE_VERSION);
3276 system_os = rot13(SYSTYPE);
3277 } else {
3278 hostpart = cpystr(ps_global->hostname);
3279 alpine_name = cpystr("alpine");
3280 alpine_version = cpystr(ALPINE_VERSION);
3281 system_os = cpystr(SYSTYPE);
3284 if(!hostpart)
3285 hostpart = cpystr("huh");
3287 snprintf(idbuf, sizeof(idbuf), "<%.6s.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
3288 alpine_name, system_os, alpine_version, (now_x->tm_year) % 100, now_x->tm_mon + 1,
3289 now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec,
3290 cnt, getpid(), hostpart);
3291 idbuf[sizeof(idbuf)-1] = '\0';
3293 id = cpystr(idbuf);
3295 if(hostpart) fs_give((void **) &hostpart);
3296 if(alpine_name) fs_give((void **) & alpine_name);
3297 if(alpine_version) fs_give((void **)&alpine_version);
3298 if(system_os) fs_give((void **)&system_os);
3300 return(id);
3304 char *
3305 generate_user_agent(void)
3307 char buf[128];
3308 char rev[128];
3310 if(F_ON(F_QUELL_USERAGENT, ps_global))
3311 return(NULL);
3313 snprintf(buf, sizeof(buf),
3314 "%sAlpine %s (%s %s)",
3315 (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "",
3316 ALPINE_VERSION, SYSTYPE,
3317 get_alpine_revision_string(rev, sizeof(rev)));
3319 return(cpystr(buf));
3323 char *
3324 rot13(char *src)
3326 char byte, cap, *p, *ret = NULL;
3328 if(src && *src){
3329 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3330 p = ret;
3331 while((byte = *src++) != '\0'){
3332 cap = byte & 32;
3333 byte &= ~cap;
3334 *p++ = ((byte >= 'A') && (byte <= 'Z')
3335 ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap;
3338 *p = '\0';
3341 return(ret);
3344 char *
3345 rot5n(char *src)
3347 char byte, *p, *ret = NULL;
3349 if(src && *src){
3350 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3351 p = ret;
3352 while((byte = *src++) != '\0')
3353 *p++ = ((byte >= '0') && (byte <= '9')
3354 ? ((byte - '0' + 5) % 10 + '0') : byte);
3355 *p = '\0';
3358 return(ret);
3362 /*----------------------------------------------------------------------
3363 Return the first true address pointer (modulo group syntax allowance)
3365 Args: addr -- Address list
3367 Result: First real address pointer, or NULL
3368 ----------------------------------------------------------------------*/
3369 ADDRESS *
3370 first_addr(struct mail_address *addr)
3372 while(addr && !addr->host)
3373 addr = addr->next;
3375 return(addr);
3379 /*----------------------------------------------------------------------
3380 lit -- this is the source
3381 prenewlines -- prefix the file contents with this many newlines
3382 postnewlines -- postfix the file contents with this many newlines
3383 is_sig -- this is a signature (not a template)
3384 decode_constants -- change C-style constants into their values
3385 ----*/
3386 char *
3387 get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants)
3389 char *sig = NULL;
3392 * Should make this smart enough not to do the copying and double
3393 * allocation of space.
3395 if(lit){
3396 char *tmplit = NULL, *p, *q, *d, save;
3397 size_t len;
3399 if(decode_constants){
3400 tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char));
3401 tmplit[0] = '\0';
3402 cstring_to_string(lit, tmplit);
3404 else
3405 tmplit = cpystr(lit);
3407 len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE);
3408 sig = (char *) fs_get((len+1) * sizeof(char));
3409 memset(sig, 0, len+1);
3410 d = sig;
3411 while(prenewlines--)
3412 sstrncpy(&d, NEWLINE, len-(d-sig));
3414 if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
3415 !sigdashes_are_present(tmplit)){
3416 sstrncpy(&d, SIGDASHES, len-(d-sig));
3417 sstrncpy(&d, NEWLINE, len-(d-sig));
3420 sig[len] = '\0';
3422 p = tmplit;
3423 while(*p){
3424 /* get a line */
3425 q = strpbrk(p, "\n\r");
3426 if(q){
3427 save = *q;
3428 *q = '\0';
3432 * Strip trailing space if we are doing a signature and
3433 * this line is not sigdashes.
3435 if(is_sig && strcmp(p, SIGDASHES))
3436 removing_trailing_white_space(p);
3438 while((d-sig) <= len && (*d = *p++) != '\0')
3439 d++;
3441 if(q){
3442 if((d-sig) <= len)
3443 *d++ = save;
3445 p = q+1;
3447 else
3448 break;
3451 while(postnewlines--)
3452 sstrncpy(&d, NEWLINE, len-(d-sig));
3454 sig[len] = '\0';
3456 if((d-sig) <= len)
3457 *d = '\0';
3459 if(tmplit)
3460 fs_give((void **) &tmplit);
3463 return(sig);
3468 sigdashes_are_present(char *sig)
3470 char *p;
3472 p = srchstr(sig, SIGDASHES);
3473 while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
3474 (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
3475 p = srchstr(p+1, SIGDASHES);
3477 return(p ? 1 : 0);
3481 /*----------------------------------------------------------------------
3482 Acquire the pinerc defined signature file pathname
3484 ----*/
3485 char *
3486 signature_path(char *sname, char *sbuf, size_t len)
3488 *sbuf = '\0';
3489 if(sname && *sname){
3490 size_t spl = strlen(sname);
3491 if(IS_REMOTE(sname)){
3492 if(spl < len - 1)
3493 strncpy(sbuf, sname, len-1);
3495 else if(is_absolute_path(sname)){
3496 strncpy(sbuf, sname, len-1);
3497 sbuf[len-1] = '\0';
3498 fnexpand(sbuf, len);
3500 else if(ps_global->VAR_OPER_DIR){
3501 if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
3502 build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len);
3504 else{
3505 char *lc = last_cmpnt(ps_global->pinerc);
3507 sbuf[0] = '\0';
3508 if(lc != NULL){
3509 strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc));
3510 sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0';
3513 strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0));
3514 sbuf[len-1] = '\0';
3518 return(*sbuf ? sbuf : NULL);
3522 char *
3523 simple_read_remote_file(char *name, char *subtype)
3525 int try_cache;
3526 REMDATA_S *rd;
3527 char *file = NULL;
3530 dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?"));
3533 * We could parse the name here to find what type it is. So far we
3534 * only have type RemImap.
3536 rd = rd_create_remote(RemImap, name, subtype,
3537 NULL, _("Error: "), _("Can't fetch remote configuration."));
3538 if(!rd)
3539 goto bail_out;
3541 try_cache = rd_read_metadata(rd);
3543 if(rd->access == MaybeRorW){
3544 if(rd->read_status == 'R')
3545 rd->access = ReadOnly;
3546 else
3547 rd->access = ReadWrite;
3550 if(rd->access != NoExists){
3552 rd_check_remvalid(rd, 1L);
3555 * If the cached info says it is readonly but
3556 * it looks like it's been fixed now, change it to readwrite.
3558 if(rd->read_status == 'R'){
3560 * We go to this trouble since readonly sigfiles
3561 * are likely a mistake. They are usually supposed to be
3562 * readwrite so we open it and check if it's been fixed.
3564 rd_check_readonly_access(rd);
3565 if(rd->read_status == 'W'){
3566 rd->access = ReadWrite;
3567 rd->flags |= REM_OUTOFDATE;
3569 else
3570 rd->access = ReadOnly;
3573 if(rd->flags & REM_OUTOFDATE){
3574 if(rd_update_local(rd) != 0){
3576 dprint((1,
3577 "simple_read_remote_file: rd_update_local failed\n"));
3579 * Don't give up altogether. We still may be
3580 * able to use a cached copy.
3583 else{
3584 dprint((7,
3585 "%s: copied remote to local (%ld)\n",
3586 rd->rn ? rd->rn : "?", (long)rd->last_use));
3590 if(rd->access == ReadWrite)
3591 rd->flags |= DO_REMTRIM;
3594 /* If we couldn't get to remote folder, try using the cached copy */
3595 if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){
3596 if(try_cache){
3597 rd->access = ReadOnly;
3598 rd->flags |= USE_OLD_CACHE;
3599 q_status_message(SM_ORDER, 3, 4,
3600 "Can't contact remote server, using cached copy");
3601 dprint((2,
3602 "Can't open remote file %s, using local cached copy %s readonly\n",
3603 rd->rn ? rd->rn : "?",
3604 rd->lf ? rd->lf : "?"));
3606 else{
3607 rd->flags &= ~DO_REMTRIM;
3608 goto bail_out;
3612 file = read_file(rd->lf, READ_FROM_LOCALE);
3614 bail_out:
3615 if(rd)
3616 rd_close_remdata(&rd);
3618 return(file);
3621 /* special handling for messages that contain a mixed part in the
3622 * multipart alternative section.
3624 BODY *
3625 forward_multi_alt_mixed(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3626 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3628 #define FWDTMPLEN 256
3629 BODY *body = NULL, *text_body = NULL;
3630 PART *part = NULL;
3631 char prefix_buf[FWDTMPLEN];
3632 char *new_charset = NULL;
3633 int partnum;
3634 char *section, sect_buf[256];
3635 int forward_raw_body = 0;
3637 if(orig_body
3638 && orig_body->type == TYPEMULTIPART
3639 && orig_body->subtype
3640 && !strucmp(orig_body->subtype, "alternative"))
3641 for(part = orig_body->nested.part, partnum = 1;
3642 part;
3643 part = part->next, partnum++)
3644 if(part->body.type == TYPEMULTIPART
3645 && part->body.subtype
3646 && !strucmp(part->body.subtype, "MIXED"))
3647 break;
3649 if(part == NULL) return NULL;
3651 snprintf(prefix_buf, sizeof(prefix_buf), "%.*s%s%s%d",
3652 FWDTMPLEN/2, sect_prefix ? sect_prefix : "",
3653 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3654 partnum);
3655 prefix_buf[sizeof(prefix_buf)-1] = '\0';
3657 if(ps_global->full_header == 2
3658 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
3659 forward_raw_body = 1;
3660 if(sp_expunge_count(stream))
3661 return(NULL);
3663 if(sect_prefix && forward_raw_body == 0)
3664 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
3665 else if(sect_prefix && forward_raw_body)
3666 section = sect_prefix;
3667 else if(!sect_prefix && forward_raw_body)
3668 section = NULL;
3669 else
3670 section = "1";
3671 sect_buf[sizeof(sect_buf)-1] = '\0';
3673 body = copy_body(NULL, &part->body);
3675 /*--- The text part of the message ---*/
3676 if(!body->nested.part){
3677 q_status_message(SM_ORDER | SM_DING, 3, 6,
3678 "Error referencing body part 1");
3679 mail_free_body(&body);
3681 else if(body->nested.part->body.type == TYPETEXT) {
3682 char *new_charset = NULL;
3684 /*--- The first part is text ----*/
3685 text_body = &body->nested.part->body;
3686 text_body->contents.text.data = msgtext;
3687 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
3688 /* this text is going to the composer, it should be Plain */
3689 fs_give((void **)&text_body->subtype);
3690 text_body->subtype = cpystr("PLAIN");
3692 if(!(flags & FWD_ANON)){
3693 forward_delimiter(pc);
3694 reply_forward_header(stream, msgno,
3695 sect_prefix, env, pc, "");
3698 if(!(get_body_part_text(stream, &part->body,
3699 msgno, section, 0L, pc,
3700 NULL, &new_charset, GBPT_NONE)
3701 && fetch_contents(stream, msgno, prefix_buf, body)))
3702 mail_free_body(&body);
3703 else if(new_charset)
3704 set_parameter(&text_body->parameter, "charset", new_charset);
3706 return(body);
3710 /*----------------------------------------------------------------------
3711 Build the body for the multipart/alternative part
3713 Args:
3715 Result:
3717 ----------------------------------------------------------------------*/
3718 BODY *
3719 forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3720 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3722 #define FWDTMPLEN 256
3723 BODY *body = NULL;
3724 PART *part = NULL, *bestpart = NULL;
3725 char tmp_buf[FWDTMPLEN];
3726 char *new_charset = NULL;
3727 int partnum, bestpartnum;
3729 /* try multipart mixed first */
3730 if((body = forward_multi_alt_mixed(stream, env, orig_body,
3731 msgno, sect_prefix, msgtext, pc, flags)) != NULL)
3732 return body;
3734 if(ps_global->force_prefer_plain
3735 || (!ps_global->force_no_prefer_plain
3736 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
3737 for(part = orig_body->nested.part, partnum = 1;
3738 part;
3739 part = part->next, partnum++)
3740 if((!part->body.type || part->body.type == TYPETEXT)
3741 && (!part->body.subtype
3742 || !strucmp(part->body.subtype, "plain")))
3743 break;
3747 * Else choose last alternative among plain or html parts.
3748 * Perhaps we should really be using mime_show() to make this
3749 * potentially more general than just plain or html.
3751 if(!part){
3752 for(part = orig_body->nested.part, partnum = 1;
3753 part;
3754 part = part->next, partnum++){
3755 if((!part->body.type || part->body.type == TYPETEXT)
3756 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
3758 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
3759 bestpart = part;
3760 bestpartnum = partnum;
3764 part = bestpart;
3765 partnum = bestpartnum;
3769 * IF something's interesting insert it
3770 * AND forget the rest of the multipart
3772 if(part){
3773 body = mail_newbody();
3774 body->type = TYPETEXT;
3775 body->contents.text.data = msgtext;
3777 /* record character set, flowing, etc */
3778 body->parameter = copy_parameters(part->body.parameter);
3779 body->size.bytes = part->body.size.bytes;
3781 if(!(flags & FWD_ANON)){
3782 forward_delimiter(pc);
3783 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
3785 /* FWDTMPLEN = sizeof(tmp_buf) */
3786 snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d",
3787 FWDTMPLEN/2, sect_prefix ? sect_prefix : "",
3788 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3789 partnum);
3790 tmp_buf[sizeof(tmp_buf)-1] = '\0';
3791 get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc,
3792 NULL, &new_charset, GBPT_NONE);
3795 * get_body_part_text translated the data to a new charset.
3796 * We need to record that fact in body.
3798 if(new_charset)
3799 set_parameter(&body->parameter, "charset", new_charset);
3801 else
3802 q_status_message(SM_ORDER | SM_DING, 3, 3,
3803 "No suitable part found. Forwarding as attachment");
3805 return(body);
3809 void
3810 reply_append_addr(struct mail_address **dest, struct mail_address *src)
3812 for( ; *dest; dest = &(*dest)->next)
3815 *dest = src;