* NTLM authentication support with the ntlm library, in Unix systems.
[alpine.git] / pith / reply.c
blob58b10b2729ab1e22028bfffa6cc0eae562dd14ca
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-2017 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(ps_global->force_prefer_plain
1300 || (!ps_global->force_no_prefer_plain
1301 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
1302 for(part = body->nested.part; part; part = part->next)
1303 if((!part->body.type || part->body.type == TYPETEXT)
1304 && (!part->body.subtype
1305 || !strucmp(part->body.subtype, "plain"))){
1306 *new_body = &part->body;
1307 return(1);
1312 * Else choose last alternative among plain or html parts.
1313 * Perhaps we should really be using mime_show() to make this
1314 * potentially more general than just plain or html.
1316 for(part = body->nested.part; part; part = part->next){
1317 if((!part->body.type || part->body.type == TYPETEXT)
1318 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
1320 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
1321 got_one++;
1322 *new_body = &part->body;
1326 if(got_one)
1327 return(1);
1329 else if(body->nested.part)
1330 /* NOTE: we're only interested in "first" part of mixed */
1331 return(reply_body_text(&body->nested.part->body, new_body));
1333 break;
1335 default:
1336 break;
1340 return(0);
1344 char *
1345 reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl)
1347 char *sig;
1348 size_t l;
1350 sig = detoken(role, env,
1351 2, ps_global->reply.signature_bottom ? 0 : 1, 1,
1352 redraft_pos, impl);
1354 if(!ps_global->reply.signature_bottom && (!sig || !*sig)){
1355 if(sig)
1356 fs_give((void **)&sig);
1358 l = 2 * strlen(NEWLINE);
1359 sig = (char *)fs_get((l+1) * sizeof(char));
1360 strncpy(sig, NEWLINE, l);
1361 sig[l] = '\0';
1362 strncat(sig, NEWLINE, l+1-1-strlen(sig));
1363 sig[l] = '\0';
1364 return(sig);
1367 return(sig);
1372 * Buf is at least size maxlen+1
1374 void
1375 get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1377 ADDRESS *addr = NULL;
1378 ADDRESS *last_to = NULL;
1379 ADDRESS *first_addr = NULL, *second_addr = NULL;
1380 ADDRESS *third_addr = NULL, *fourth_addr = NULL;
1381 int cntaddr, l;
1382 size_t orig_maxlen;
1383 char *p;
1385 buf[0] = '\0';
1387 switch(type){
1388 case iFrom:
1389 addr = env ? env->from : NULL;
1390 break;
1392 case iTo:
1393 addr = env ? env->to : NULL;
1394 break;
1396 case iCc:
1397 addr = env ? env->cc : NULL;
1398 break;
1400 case iSender:
1401 addr = env ? env->sender : NULL;
1402 break;
1405 * Recips is To and Cc togeter. We hook the two adrlists together
1406 * temporarily.
1408 case iRecips:
1409 addr = env ? env->to : NULL;
1410 /* Find end of To list */
1411 for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
1414 /* Make the end of To list point to cc list */
1415 if(last_to)
1416 last_to->next = (env ? env->cc : NULL);
1418 break;
1421 * Initials.
1423 case iInit:
1424 if(env && env->from && env->from->personal){
1425 char *name, *initials = NULL;
1427 name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1428 SIZEOF_20KBUF, env->from->personal);
1429 if(name == env->from->personal){
1430 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
1431 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
1432 name = tmp_20k_buf;
1435 if(name && *name){
1436 initials = reply_quote_initials(name);
1437 iutf8ncpy(buf, initials, maxlen);
1438 buf[maxlen] = '\0';
1442 return;
1444 default:
1445 break;
1448 orig_maxlen = maxlen;
1450 first_addr = addr;
1451 /* skip over rest of c-client group addr */
1452 if(first_addr && first_addr->mailbox && !first_addr->host){
1453 for(second_addr = first_addr->next;
1454 second_addr && second_addr->host;
1455 second_addr = second_addr->next)
1458 if(second_addr && !second_addr->host)
1459 second_addr = second_addr->next;
1461 else if(!(first_addr && first_addr->host && first_addr->host[0] == '.'))
1462 second_addr = first_addr ? first_addr->next : NULL;
1464 if(second_addr && second_addr->mailbox && !second_addr->host){
1465 for(third_addr = second_addr->next;
1466 third_addr && third_addr->host;
1467 third_addr = third_addr->next)
1470 if(third_addr && !third_addr->host)
1471 third_addr = third_addr->next;
1473 else if(!(second_addr && second_addr->host && second_addr->host[0] == '.'))
1474 third_addr = second_addr ? second_addr->next : NULL;
1476 if(third_addr && third_addr->mailbox && !third_addr->host){
1477 for(fourth_addr = third_addr->next;
1478 fourth_addr && fourth_addr->host;
1479 fourth_addr = fourth_addr->next)
1482 if(fourth_addr && !fourth_addr->host)
1483 fourth_addr = fourth_addr->next;
1485 else if(!(third_addr && third_addr->host && third_addr->host[0] == '.'))
1486 fourth_addr = third_addr ? third_addr->next : NULL;
1488 /* Just attempting to make a nice display */
1489 if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
1490 (first_addr->mailbox && first_addr->mailbox[0]))){
1491 if(second_addr){
1492 if((second_addr->personal && second_addr->personal[0]) ||
1493 (second_addr->mailbox && second_addr->mailbox[0])){
1494 if(third_addr){
1495 if((third_addr->personal && third_addr->personal[0]) ||
1496 (third_addr->mailbox && third_addr->mailbox[0])){
1497 if(fourth_addr)
1498 cntaddr = 4;
1499 else
1500 cntaddr = 3;
1502 else
1503 cntaddr = -1;
1505 else
1506 cntaddr = 2;
1508 else
1509 cntaddr = -1;
1511 else
1512 cntaddr = 1;
1514 else
1515 cntaddr = -1;
1517 p = buf;
1518 if(cntaddr == 1)
1519 a_little_addr_string(first_addr, p, maxlen);
1520 else if(cntaddr == 2){
1521 a_little_addr_string(first_addr, p, maxlen);
1522 maxlen -= (l=strlen(p));
1523 p += l;
1524 if(maxlen > 7){
1525 strncpy(p, " and ", maxlen);
1526 maxlen -= 5;
1527 p += 5;
1528 a_little_addr_string(second_addr, p, maxlen);
1531 else if(cntaddr == 3){
1532 a_little_addr_string(first_addr, p, maxlen);
1533 maxlen -= (l=strlen(p));
1534 p += l;
1535 if(maxlen > 7){
1536 strncpy(p, ", ", maxlen);
1537 maxlen -= 2;
1538 p += 2;
1539 a_little_addr_string(second_addr, p, maxlen);
1540 maxlen -= (l=strlen(p));
1541 p += l;
1542 if(maxlen > 7){
1543 strncpy(p, ", and ", maxlen);
1544 maxlen -= 6;
1545 p += 6;
1546 a_little_addr_string(third_addr, p, maxlen);
1550 else if(cntaddr > 3){
1551 a_little_addr_string(first_addr, p, maxlen);
1552 maxlen -= (l=strlen(p));
1553 p += l;
1554 if(maxlen > 7){
1555 strncpy(p, ", ", maxlen);
1556 maxlen -= 2;
1557 p += 2;
1558 a_little_addr_string(second_addr, p, maxlen);
1559 maxlen -= (l=strlen(p));
1560 p += l;
1561 if(maxlen > 7){
1562 strncpy(p, ", ", maxlen);
1563 maxlen -= 2;
1564 p += 2;
1565 a_little_addr_string(third_addr, p, maxlen);
1566 maxlen -= (l=strlen(p));
1567 p += l;
1568 if(maxlen >= 12)
1569 strncpy(p, ", and others", maxlen);
1570 else if(maxlen >= 3)
1571 strncpy(p, "...", maxlen);
1575 else if(addr){
1576 char *a_string;
1578 a_string = addr_list_string(addr, NULL, 0);
1579 iutf8ncpy(buf, a_string, maxlen);
1581 fs_give((void **)&a_string);
1584 if(last_to)
1585 last_to->next = NULL;
1587 buf[orig_maxlen] = '\0';
1592 * Buf is at least size maxlen+1
1594 void
1595 get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1597 int cntnews = 0, orig_maxlen;
1598 char *news = NULL, *p, *q;
1600 switch(type){
1601 case iNews:
1602 case iNewsAndTo:
1603 case iToAndNews:
1604 case iNewsAndRecips:
1605 case iRecipsAndNews:
1606 news = env ? env->newsgroups : NULL;
1607 break;
1609 case iCurNews:
1610 if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream))
1611 news = ps_global->cur_folder;
1613 break;
1615 default:
1616 break;
1619 orig_maxlen = maxlen;
1621 if(news){
1622 q = news;
1623 while(isspace((unsigned char)*q))
1624 q++;
1626 if(*q)
1627 cntnews++;
1629 while((q = strindex(q, ',')) != NULL){
1630 q++;
1631 while(isspace((unsigned char)*q))
1632 q++;
1634 if(*q)
1635 cntnews++;
1636 else
1637 break;
1641 if(cntnews == 1){
1642 istrncpy(buf, news, maxlen);
1643 buf[maxlen] = '\0';
1644 removing_leading_and_trailing_white_space(buf);
1646 else if(cntnews == 2){
1647 p = buf;
1648 q = news;
1649 while(isspace((unsigned char)*q))
1650 q++;
1652 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1653 *p++ = *q++;
1654 maxlen--;
1657 if(maxlen > 7){
1658 strncpy(p, " and ", maxlen);
1659 p += 5;
1660 maxlen -= 5;
1663 while(isspace((unsigned char)*q) || *q == ',')
1664 q++;
1666 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1667 *p++ = *q++;
1668 maxlen--;
1671 *p = '\0';
1673 istrncpy(tmp_20k_buf, buf, 10000);
1674 strncpy(buf, tmp_20k_buf, orig_maxlen);
1676 else if(cntnews > 2){
1677 char b[100];
1679 p = buf;
1680 q = news;
1681 while(isspace((unsigned char)*q))
1682 q++;
1684 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1685 *p++ = *q++;
1686 maxlen--;
1689 *p = '\0';
1690 snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1);
1691 b[sizeof(b)-1] = '\0';
1692 if(maxlen >= strlen(b))
1693 strncpy(p, b, maxlen);
1694 else if(maxlen >= 3)
1695 strncpy(p, "...", maxlen);
1697 buf[orig_maxlen] = '\0';
1699 istrncpy(tmp_20k_buf, buf, 10000);
1700 tmp_20k_buf[10000-1] = '\0';
1701 strncpy(buf, tmp_20k_buf, orig_maxlen);
1704 buf[orig_maxlen] = '\0';
1707 void
1708 shorten_subject(char *origsubj)
1710 char *s, *t, *u;
1711 int endlist = 0;
1713 if(origsubj == NULL || *origsubj == '\0')
1714 return;
1716 for(t=s=origsubj; *s ; s++){
1717 switch(*s){
1718 /* this transforms "A [B [C] D" into "A D" should this be
1719 * "A [B D"?
1721 case '[' : if((u = strchr(s+1,']')) != NULL){
1722 s = u;
1723 endlist = 1;
1725 else
1726 *t++ = *s;
1727 break;
1728 case ' ' : if(endlist == 0) *t++ = *s; break;
1729 default : endlist = 0; *t++ = *s; break;
1732 *t = '\0';
1736 * Buf is at least size maxlen+1
1738 char *
1739 get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen)
1741 char *space = NULL;
1742 IndexColType addrtype;
1744 buf[0] = '\0';
1746 switch(type){
1747 case iRDate: case iSDate: case iSTime: case iSTime24:
1748 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
1749 case iSDateIso: case iSDateIsoS:
1750 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1751 case iSDateTime:
1752 case iSDateTimeIso: case iSDateTimeIsoS:
1753 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1754 case iSDateTime24:
1755 case iSDateTimeIso24: case iSDateTimeIsoS24:
1756 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1757 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
1758 case iDay: case iDayOrdinal: case iDay2Digit:
1759 case iMonAbb: case iMonLong: case iMon: case iMon2Digit:
1760 case iYear: case iYear2Digit:
1761 case iDate: case iLDate:
1762 case iTimezone: case iDayOfWeekAbb: case iDayOfWeek:
1763 case iPrefDate: case iPrefTime: case iPrefDateTime:
1764 if(env && env->date && env->date[0] && maxlen >= 20)
1765 date_str((char *) env->date, type, 1, buf, maxlen+1, 0);
1767 break;
1769 case iCurDate:
1770 case iCurDateIso:
1771 case iCurDateIsoS:
1772 case iCurTime24:
1773 case iCurTime12:
1774 case iCurDay:
1775 case iCurDay2Digit:
1776 case iCurDayOfWeek:
1777 case iCurDayOfWeekAbb:
1778 case iCurMon:
1779 case iCurMon2Digit:
1780 case iCurMonLong:
1781 case iCurMonAbb:
1782 case iCurYear:
1783 case iCurYear2Digit:
1784 case iCurPrefDate:
1785 case iCurPrefDateTime:
1786 case iCurPrefTime:
1787 case iLstMon:
1788 case iLstMon2Digit:
1789 case iLstMonLong:
1790 case iLstMonAbb:
1791 case iLstMonYear:
1792 case iLstMonYear2Digit:
1793 case iLstYear:
1794 case iLstYear2Digit:
1795 if(maxlen >= 20)
1796 date_str(NULL, type, 1, buf, maxlen+1, 0);
1798 break;
1800 case iFrom:
1801 case iTo:
1802 case iCc:
1803 case iSender:
1804 case iRecips:
1805 case iInit:
1806 get_addr_data(env, type, buf, maxlen);
1807 break;
1809 case iRoleNick:
1810 if(role && role->nick){
1811 strncpy(buf, role->nick, maxlen);
1812 buf[maxlen] = '\0';
1814 break;
1816 case iNewLine:
1817 if(maxlen >= strlen(NEWLINE)){
1818 strncpy(buf, NEWLINE, maxlen);
1819 buf[maxlen] = '\0';
1821 break;
1823 case iAddress:
1824 case iMailbox:
1825 if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
1826 strlen(env->from->mailbox) <= maxlen){
1827 strncpy(buf, env->from->mailbox, maxlen);
1828 buf[maxlen] = '\0';
1829 if(type == iAddress &&
1830 env->from->host &&
1831 env->from->host[0] &&
1832 env->from->host[0] != '.' &&
1833 strlen(buf) + strlen(env->from->host) + 1 <= maxlen){
1834 strncat(buf, "@", maxlen+1-1-strlen(buf));
1835 buf[maxlen] = '\0';
1836 strncat(buf, env->from->host, maxlen+1-1-strlen(buf));
1837 buf[maxlen] = '\0';
1841 break;
1843 case iNews:
1844 case iCurNews:
1845 get_news_data(env, type, buf, maxlen);
1846 break;
1848 case iToAndNews:
1849 case iNewsAndTo:
1850 case iRecipsAndNews:
1851 case iNewsAndRecips:
1852 if(type == iToAndNews || type == iNewsAndTo)
1853 addrtype = iTo;
1854 else
1855 addrtype = iRecips;
1857 if(env && env->newsgroups){
1858 space = (char *)fs_get((maxlen+1) * sizeof(char));
1859 get_news_data(env, type, space, maxlen);
1862 get_addr_data(env, addrtype, buf, maxlen);
1864 if(space && *space && *buf){
1865 if(strlen(space) + strlen(buf) + 5 > maxlen){
1866 if(strlen(space) > maxlen/2)
1867 get_news_data(env, type, space, maxlen - strlen(buf) - 5);
1868 else
1869 get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
1872 if(type == iToAndNews || type == iRecipsAndNews)
1873 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space);
1874 else
1875 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf);
1877 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1879 strncpy(buf, tmp_20k_buf, maxlen);
1880 buf[maxlen] = '\0';
1882 else if(space && *space){
1883 strncpy(buf, space, maxlen);
1884 buf[maxlen] = '\0';
1887 if(space)
1888 fs_give((void **)&space);
1890 break;
1892 case iSubject:
1893 case iShortSubject:
1894 if(env && env->subject){
1895 size_t n, len;
1896 unsigned char *p, *tmp = NULL;
1898 if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){
1899 len = n+1;
1900 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
1902 else{
1903 len = SIZEOF_20KBUF;
1904 p = (unsigned char *)tmp_20k_buf;
1907 istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen);
1909 buf[maxlen] = '\0';
1911 if(tmp)
1912 fs_give((void **)&tmp);
1914 if(type == iShortSubject)
1915 shorten_subject(buf);
1918 break;
1920 case iMsgID:
1921 if(env && env->message_id){
1922 strncpy(buf, env->message_id, maxlen);
1923 buf[maxlen] = '\0';
1926 break;
1928 default:
1929 break;
1932 buf[maxlen] = '\0';
1933 return(buf);
1938 * reply_delimiter - output formatted reply delimiter for given envelope
1939 * with supplied character writing function.
1941 void
1942 reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc)
1944 #define MAX_DELIM 2000
1945 char buf[MAX_DELIM+1];
1946 char *p;
1947 char *filtered = NULL;
1948 int contains_newline_token = 0;
1951 if(!env)
1952 return;
1954 strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
1955 buf[MAX_DELIM] = '\0';
1956 /* preserve exact default behavior from before */
1957 if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
1958 struct date d;
1959 int include_date;
1961 parse_date((char *) env->date, &d);
1962 include_date = !(d.day == -1 || d.month == -1 || d.year == -1);
1963 if(include_date){
1964 gf_puts("On ", pc); /* All delims have... */
1965 if(d.wkday != -1){ /* "On day, date month year" */
1966 gf_puts(day_abbrev(d.wkday), pc); /* in common */
1967 gf_puts(", ", pc);
1970 gf_puts(int2string(d.day), pc);
1971 (*pc)(' ');
1972 gf_puts(month_abbrev(d.month), pc);
1973 (*pc)(' ');
1974 gf_puts(int2string(d.year), pc);
1977 if(env->from
1978 && ((env->from->personal && env->from->personal[0])
1979 || (env->from->mailbox && env->from->mailbox[0]))){
1980 char buftmp[MAILTMPLEN];
1982 a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1);
1983 if(include_date)
1984 gf_puts(", ", pc);
1986 gf_puts(buftmp, pc);
1987 gf_puts(" wrote:", pc);
1989 else{
1990 if(include_date)
1991 gf_puts(", it was written", pc);
1992 else
1993 gf_puts("It was written", pc);
1997 else{
1999 * This is here for backwards compatibility. There didn't used
2000 * to be a _NEWLINE_ token. The user would enter text that should
2001 * all fit on one line and then that was followed by two newlines.
2002 * Also, truncation occurs if it is long.
2003 * Now, if _NEWLINE_ is not in the text, same thing still works
2004 * the same. However, if _NEWLINE_ is in there, then all bets are
2005 * off and the user is on his or her own. No automatic newlines
2006 * are added, only those that come from the tokens. No truncation
2007 * is done, the user is trusted to get it right. Newlines may be
2008 * embedded so that the leadin is multi-line.
2010 contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL);
2011 filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role,
2012 NULL, NULL);
2014 /* try to truncate if too long */
2015 if(!contains_newline_token && filtered && utf8_width(filtered) > 80){
2016 int ended_with_colon = 0;
2017 int ended_with_quote = 0;
2018 int ended_with_quote_colon = 0;
2019 int l;
2021 l = strlen(filtered);
2023 if(filtered[l-1] == ':'){
2024 ended_with_colon = ':';
2025 if(filtered[l-2] == QUOTE || filtered[l-2] == '\'')
2026 ended_with_quote_colon = filtered[l-2];
2028 else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'')
2029 ended_with_quote = filtered[l-1];
2031 /* try to find space to break at */
2032 for(p = &filtered[75]; p > &filtered[60] &&
2033 !isspace((unsigned char)*p); p--)
2036 if(!isspace((unsigned char)*p))
2037 p = &filtered[75];
2039 *p++ = '.';
2040 *p++ = '.';
2041 *p++ = '.';
2042 if(ended_with_quote_colon){
2043 *p++ = ended_with_quote_colon;
2044 *p++ = ':';
2046 else if(ended_with_colon)
2047 *p++ = ended_with_colon;
2048 else if(ended_with_quote)
2049 *p++ = ended_with_quote;
2051 *p = '\0';
2054 if(filtered && *filtered)
2055 gf_puts(filtered, pc);
2058 /* and end with two newlines unless no leadin at all */
2059 if(!contains_newline_token){
2060 if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
2061 gf_puts(NEWLINE, pc);
2062 gf_puts(NEWLINE, pc);
2066 if(filtered)
2067 fs_give((void **)&filtered);
2071 void
2072 free_redraft_pos(REDRAFT_POS_S **redraft_pos)
2074 if(redraft_pos && *redraft_pos){
2075 if((*redraft_pos)->hdrname)
2076 fs_give((void **)&(*redraft_pos)->hdrname);
2078 fs_give((void **)redraft_pos);
2083 /*----------------------------------------------------------------------
2084 Build the body for the message number/part being forwarded as ATTACHMENT
2086 Args:
2088 Result: PARTS suitably attached to body
2090 ----------------------------------------------------------------------*/
2092 forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext)
2094 char *tmp_text;
2095 unsigned long len;
2096 BODY *b;
2098 *partp = mail_newbody_part();
2099 b = &(*partp)->body;
2100 b->type = TYPEMESSAGE;
2101 b->id = generate_message_id();
2102 b->description = cpystr("Forwarded Message");
2103 b->nested.msg = mail_newmsg();
2104 b->disposition.type = cpystr("inline");
2106 /*---- Package each message in a storage object ----*/
2107 if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
2108 && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
2109 && *tmp_text){
2110 so_puts((STORE_S *) b->contents.text.data, tmp_text);
2112 b->size.bytes = strlen(tmp_text);
2113 so_puts((STORE_S *) b->contents.text.data, "\015\012");
2114 if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){
2115 so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
2116 b->size.bytes += len;
2117 return(1);
2121 return(0);
2126 * forward_delimiter - return delimiter for forwarded text
2128 void
2129 forward_delimiter(gf_io_t pc)
2131 gf_puts(NEWLINE, pc);
2132 /* TRANSLATORS: When a message is forwarded by the user this is the
2133 text that shows where the forwarded part of the message begins. */
2134 gf_puts(_("---------- Forwarded message ----------"), pc);
2135 gf_puts(NEWLINE, pc);
2139 /*----------------------------------------------------------------------
2140 Wrapper for header formatting tool
2142 Args: stream --
2143 msgno --
2144 env --
2145 pc --
2146 prefix --
2148 Result: header suitable for reply/forward text written using "pc"
2150 ----------------------------------------------------------------------*/
2151 void
2152 reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env,
2153 gf_io_t pc, char *prefix)
2155 int rv;
2156 HEADER_S h;
2157 char **list, **new_list = NULL;
2159 list = ps_global->VAR_VIEW_HEADERS;
2162 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2163 * the user doesn't inadvertently forward the BCC header.
2165 if(list && list[0]){
2166 int i, cnt = 0;
2167 char **p;
2169 while(list[cnt++])
2172 p = new_list = (char **) fs_get((cnt+1) * sizeof(char *));
2174 for(i=0; list[i]; i++)
2175 if(strucmp(list[i], "bcc"))
2176 *p++ = cpystr(list[i]);
2178 *p = NULL;
2180 if(new_list && new_list[0])
2181 list = new_list;
2185 HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC);
2186 if((rv = format_header(stream, msgno, part, env, &h,
2187 prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){
2188 if(rv == 1)
2189 gf_puts(" [Error fetching message header data]", pc);
2191 else
2192 gf_puts(NEWLINE, pc); /* write header delimiter */
2194 if(new_list)
2195 free_list_array(&new_list);
2199 /*----------------------------------------------------------------------
2200 Build the subject for the message number being forwarded
2202 Args: pine_state -- The usual pine structure
2203 msgno -- The message number to build subject for
2205 Result: malloc'd string containing new subject or NULL on error
2207 ----------------------------------------------------------------------*/
2208 char *
2209 forward_subject(ENVELOPE *env, int flags)
2211 size_t l;
2212 char *p, buftmp[MAILTMPLEN];
2214 if(!env)
2215 return(NULL);
2217 dprint((9, "checking subject: \"%s\"\n",
2218 env->subject ? env->subject : "NULL"));
2220 if(env->subject && env->subject[0]){ /* add (fwd)? */
2221 snprintf(buftmp, sizeof(buftmp), "%s", env->subject);
2222 buftmp[sizeof(buftmp)-1] = '\0';
2223 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2224 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
2225 SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp)
2226 strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF);
2228 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2230 removing_trailing_white_space(tmp_20k_buf);
2231 if((l = strlen(tmp_20k_buf)) < 1000 &&
2232 (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){
2233 snprintf(tmp_20k_buf+2000, SIZEOF_20KBUF-2000, "%s (fwd)", tmp_20k_buf);
2234 tmp_20k_buf[SIZEOF_20KBUF-2000-1] = '\0';
2235 strncpy(tmp_20k_buf, tmp_20k_buf+2000, SIZEOF_20KBUF);
2236 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2240 * HACK: composer can't handle embedded double quotes in attachment
2241 * comments so we substitute two single quotes.
2243 if(flags & FS_CONVERT_QUOTES)
2244 while((p = strchr(tmp_20k_buf, QUOTE)) != NULL)
2245 (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''");
2247 return(cpystr(tmp_20k_buf));
2251 return(cpystr("Forwarded mail...."));
2255 /*----------------------------------------------------------------------
2256 Build the body for the message number/part being forwarded
2258 Args:
2260 Result: BODY structure suitable for sending
2262 ----------------------------------------------------------------------*/
2263 BODY *
2264 forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
2265 long int msgno, char *sect_prefix, void *msgtext, int flags)
2267 BODY *body = NULL, *text_body, *tmp_body;
2268 PART *part;
2269 gf_io_t pc;
2270 char *tmp_text, *section, sect_buf[256];
2271 int forward_raw_body = 0;
2274 * Check to see if messages got expunged out from underneath us. This
2275 * could have happened during the prompt to the user asking whether to
2276 * include the message as an attachment. Either the message is gone or
2277 * it might be at a different sequence number. We'd better bail.
2279 if(ps_global->full_header == 2
2280 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
2281 forward_raw_body = 1;
2282 if(sp_expunge_count(stream))
2283 return(NULL);
2285 if(sect_prefix && forward_raw_body == 0)
2286 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
2287 else if(sect_prefix && forward_raw_body)
2288 section = sect_prefix;
2289 else if(!sect_prefix && forward_raw_body)
2290 section = NULL;
2291 else
2292 section = "1";
2294 sect_buf[sizeof(sect_buf)-1] = '\0';
2296 gf_set_so_writec(&pc, (STORE_S *) msgtext);
2297 if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) {
2298 char *charset = NULL;
2300 /*---- Message has a single text part -----*/
2301 body = mail_newbody();
2302 body->type = TYPETEXT;
2303 body->contents.text.data = msgtext;
2304 if(orig_body
2305 && (charset = parameter_val(orig_body->parameter, "charset")))
2306 set_parameter(&body->parameter, "charset", charset);
2308 if(charset)
2309 fs_give((void **) &charset);
2311 if(!(flags & FWD_ANON)){
2312 forward_delimiter(pc);
2313 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
2316 if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body,
2317 msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){
2318 mail_free_body(&body);
2319 return(NULL);
2322 else if(orig_body->type == TYPEMULTIPART) {
2323 if(orig_body->subtype
2324 && ((!strucmp(orig_body->subtype, "signed") && orig_body->nested.part)
2325 #ifdef SMIME
2326 || (!strucmp(orig_body->subtype, "mixed")
2327 && orig_body->nested.part
2328 && orig_body->nested.part->body.type == TYPEMULTIPART
2329 && orig_body->nested.part->body.subtype
2330 && (!strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2331 || !strucmp(orig_body->nested.part->body.subtype, "signed")))
2332 || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2333 #endif /* SMIME */
2335 /* only operate on the signed data (not the signature) */
2336 body = forward_body(stream, env, &orig_body->nested.part->body,
2337 msgno,
2338 orig_body->nested.part->body.type == TYPEMULTIPART
2339 ? section : sect_prefix, msgtext, flags);
2341 /*---- Message is multipart ----*/
2342 else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
2343 "alternative")
2344 && (body = forward_multi_alt(stream, env, orig_body, msgno,
2345 sect_prefix, msgtext,
2346 pc, flags)))){
2347 /*--- Copy the body and entire structure ---*/
2348 body = copy_body(NULL, orig_body);
2351 * whatever subtype it is, demote it
2352 * to plain old MIXED.
2354 if(body->subtype)
2355 fs_give((void **) &body->subtype);
2357 body->subtype = cpystr("Mixed");
2359 /*--- The text part of the message ---*/
2360 if(!body->nested.part){
2361 q_status_message(SM_ORDER | SM_DING, 3, 6,
2362 "Error referencing body part 1");
2363 mail_free_body(&body);
2365 else if(body->nested.part->body.type == TYPETEXT) {
2366 char *new_charset = NULL;
2368 /*--- The first part is text ----*/
2369 text_body = &body->nested.part->body;
2370 text_body->contents.text.data = msgtext;
2371 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
2372 /* this text is going to the composer, it should be Plain */
2373 fs_give((void **)&text_body->subtype);
2374 text_body->subtype = cpystr("PLAIN");
2376 if(!(flags & FWD_ANON)){
2377 forward_delimiter(pc);
2378 reply_forward_header(stream, msgno,
2379 sect_prefix, env, pc, "");
2382 if(!(get_body_part_text(stream, &orig_body->nested.part->body,
2383 msgno, section, 0L, pc,
2384 NULL, &new_charset, GBPT_NONE)
2385 && fetch_contents(stream, msgno, sect_prefix, body)))
2386 mail_free_body(&body);
2387 else if(new_charset)
2388 set_parameter(&text_body->parameter, "charset", new_charset);
2390 /* BUG: ? matter that we're not setting body.size.bytes */
2392 else if(body->nested.part->body.type == TYPEMULTIPART
2393 && body->nested.part->body.subtype
2394 && !strucmp(body->nested.part->body.subtype, "alternative")
2395 && (tmp_body = forward_multi_alt(stream, env,
2396 &body->nested.part->body,
2397 msgno, sect_prefix,
2398 msgtext, pc,
2399 flags | FWD_NESTED))){
2400 /* for the forward_multi_alt call above, we want to pass
2401 * sect_prefix instead of section so we can obtain the header.
2402 * Set the FWD_NESTED flag so we fetch the right body_part.
2404 int partnum;
2406 part = body->nested.part->next;
2407 body->nested.part->next = NULL;
2408 mail_free_body_part(&body->nested.part);
2409 body->nested.part = mail_newbody_part();
2410 body->nested.part->body = *tmp_body;
2411 body->nested.part->next = part;
2413 for(partnum = 2; part != NULL; part = part->next){
2414 snprintf(sect_buf, sizeof(sect_buf), "%s%s%d",
2415 sect_prefix ? sect_prefix : "",
2416 sect_prefix ? "." : "", partnum++);
2417 sect_buf[sizeof(sect_buf)-1] = '\0';
2419 if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
2420 mail_free_body(&body);
2421 break;
2425 else {
2426 if(fetch_contents(stream, msgno, sect_prefix, body)){
2427 /*--- Create a new blank text part ---*/
2428 part = mail_newbody_part();
2429 part->next = body->nested.part;
2430 body->nested.part = part;
2431 part->body.contents.text.data = msgtext;
2433 else
2434 mail_free_body(&body);
2438 else {
2439 /*---- A single part message, not of type text ----*/
2440 body = mail_newbody();
2441 body->type = TYPEMULTIPART;
2442 part = mail_newbody_part();
2443 body->nested.part = part;
2445 /*--- The first part, a blank text part to be edited ---*/
2446 part->body.type = TYPETEXT;
2447 part->body.contents.text.data = msgtext;
2449 /*--- The second part, what ever it is ---*/
2450 part->next = mail_newbody_part();
2451 part = part->next;
2452 part->body.id = generate_message_id();
2453 copy_body(&(part->body), orig_body);
2456 * the idea here is to fetch part into storage object
2458 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
2459 EDIT_ACCESS)) != NULL){
2460 if((tmp_text = pine_mail_fetch_body(stream, msgno, section,
2461 &part->body.size.bytes, NIL)) != NULL)
2462 so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
2463 part->body.size.bytes);
2464 else
2465 mail_free_body(&body);
2467 else
2468 mail_free_body(&body);
2471 gf_clear_so_writec((STORE_S *) msgtext);
2473 return(body);
2479 * bounce_msg_body - build body from specified message suitable
2480 * for sending as bounced message
2482 char *
2483 bounce_msg_body(MAILSTREAM *stream,
2484 long int rawno,
2485 char *part,
2486 char **to,
2487 char *subject,
2488 ENVELOPE **outgoingp,
2489 BODY **bodyp,
2490 int *seenp)
2492 char *h, *p, *errstr = NULL;
2493 int i;
2494 STORE_S *msgtext;
2495 gf_io_t pc;
2497 *outgoingp = mail_newenvelope();
2498 (*outgoingp)->message_id = generate_message_id();
2499 (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail....");
2502 * Fill in destination if we were given one. If so, note that we
2503 * call p_s_s() below such that it won't prompt...
2505 if(to && *to){
2506 static char *fakedomain = "@";
2507 char *tmp_a_string;
2509 /* rfc822_parse_adrlist feels free to destroy input so copy */
2510 tmp_a_string = cpystr(*to);
2511 rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string,
2512 (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
2513 ? fakedomain : ps_global->maildomain);
2514 fs_give((void **) &tmp_a_string);
2517 /* build remail'd header */
2518 if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){
2519 for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++)
2520 i++;
2522 /* allocate it */
2523 (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);
2526 * copy it, "X-"ing out transport headers bothersome to
2527 * software but potentially useful to the human recipient...
2529 p = (*outgoingp)->remail;
2530 bounce_mask_header(&p, h);
2532 if(*h == '\015' && *(h+1) == '\012'){
2533 *p++ = *h++; /* copy CR LF */
2534 *p++ = *h++;
2535 bounce_mask_header(&p, h);
2537 while((*p++ = *h++) != '\0');
2539 /* BUG: else complain? */
2541 /* NOT bound for the composer, so no need for PicoText */
2542 if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){
2543 mail_free_envelope(outgoingp);
2544 return(_("Error allocating message text"));
2547 /* mark object for special handling */
2548 so_attr(msgtext, "rawbody", "1");
2551 * Build a fake body description. It's ignored by pine_rfc822_header,
2552 * but we need to set it to something that makes set_mime_types
2553 * not sniff it and pine_rfc822_output_body not re-encode it.
2554 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2555 * problems unless something tries to access body_encodings[] using
2556 * it without proper precautions. We don't want to use ENCOTHER
2557 * cause that tells set_mime_types to sniff it, and we don't want to
2558 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2559 * it. When there's time, it'd be nice to clean this interaction
2560 * up...
2562 *bodyp = mail_newbody();
2563 (*bodyp)->type = TYPETEXT;
2564 (*bodyp)->encoding = ENCMAX + 1;
2565 (*bodyp)->subtype = cpystr("Plain");
2566 (*bodyp)->contents.text.data = (void *) msgtext;
2567 gf_set_so_writec(&pc, msgtext);
2569 if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){
2570 MESSAGECACHE *mc;
2572 if((mc = mail_elt(stream, rawno)) != NULL)
2573 *seenp = mc->seen;
2576 /* pass NULL body to force mail_fetchtext */
2577 if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE))
2578 errstr = _("Error fetching message contents. Can't Bounce message");
2580 gf_clear_so_writec(msgtext);
2582 return(errstr);
2587 /*----------------------------------------------------------------------
2588 Mask off any header entries we don't want xport software to see
2590 Args: d -- destination string pointer pointer
2591 s -- source string pointer pointer
2593 Postfix uses Delivered-To to detect loops.
2594 Received line counting is also used to detect loops in places.
2596 ----*/
2597 void
2598 bounce_mask_header(char **d, char *s)
2600 if(((*s == 'R' || *s == 'r')
2601 && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8)
2602 || !struncmp(s+1, "eturn-Path", 10)))
2603 || !struncmp(s, "Delivered-To:", 13)){
2604 *(*d)++ = 'X'; /* write mask */
2605 *(*d)++ = '-';
2610 /*----------------------------------------------------------------------
2611 Fetch and format text for forwarding
2613 Args: stream -- Mail stream to fetch text from
2614 body -- Body structure of message being forwarded
2615 msg_no -- Message number of text for forward
2616 part_no -- Part number of text to forward
2617 partial -- If this is > 0 a partial fetch will be done and it will
2618 be done using FT_PEEK so the message will remain unseen.
2619 pc -- Function to write to
2620 prefix -- Prefix for each line
2621 ret_charset -- If we translate to another charset return that
2622 new charset here
2624 Returns: true if OK, false if problem occured while filtering
2626 If the text is richtext, it will be converted to plain text, since there's
2627 no rich text editing capabilities in Pine (yet).
2629 It's up to calling routines to plug in signature appropriately
2631 As with all internal text, NVT end-of-line conventions are observed.
2632 DOESN'T sanity check the prefix given!!!
2633 ----*/
2635 get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body,
2636 long int msg_no, char *part_no, long partial, gf_io_t pc,
2637 char *prefix, char **ret_charset, unsigned flags)
2639 int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0;
2640 FILTLIST_S filters[12];
2641 long len;
2642 char *err, *charset, *prefix_p = NULL;
2643 int filtcnt = 0;
2644 char *free_this = NULL;
2645 DELQ_S dq;
2647 memset(filters, 0, sizeof(filters));
2648 if(ret_charset)
2649 *ret_charset = NULL;
2651 if(!pc_is_picotext(pc))
2652 we_cancel = busy_cue(NULL, NULL, 1);
2654 /* if null body, we must be talking to a non-IMAP2bis server.
2655 * No MIME parsing provided, so we just grab the message text...
2657 if(body == NULL){
2658 char *text, *decode_error;
2659 gf_io_t gc;
2660 SourceType src = CharStar;
2661 int rv = 0;
2663 (void) pine_mail_fetchstructure(stream, msg_no, NULL);
2665 if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){
2666 gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0);
2668 gf_filter_init(); /* no filters needed */
2669 if(prefix)
2670 gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
2671 if((decode_error = gf_pipe(gc, pc)) != NULL){
2672 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s",
2673 NEWLINE, NEWLINE,
2674 decode_error, NEWLINE);
2675 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2676 gf_puts(tmp_20k_buf, pc);
2677 rv++;
2680 else{
2681 gf_puts(NEWLINE, pc);
2682 gf_puts(_(" [ERROR fetching text of message]"), pc);
2683 gf_puts(NEWLINE, pc);
2684 gf_puts(NEWLINE, pc);
2685 rv++;
2688 if(we_cancel)
2689 cancel_busy_cue(-1);
2691 return(rv == 0);
2694 charset = parameter_val(body->parameter, "charset");
2696 if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
2697 if(ret_charset)
2698 *ret_charset = "UTF-8";
2702 * just use detach, but add an auxiliary filter to insert prefix,
2703 * and, perhaps, digest richtext
2705 if(ps_global->full_header != 2
2706 && !ps_global->postpone_no_flow
2707 && (!body->subtype || !strucmp(body->subtype, "plain"))){
2708 char *parmval;
2710 flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
2711 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
2712 && (!prefix || (strucmp(prefix,"> ") == 0)
2713 || strucmp(prefix, ">") == 0));
2714 if((parmval = parameter_val(body->parameter,
2715 "format")) != NULL){
2716 if(!strucmp(parmval, "flowed")){
2717 wrapflags |= GFW_FLOWED;
2719 fs_give((void **) &parmval);
2720 if((parmval = parameter_val(body->parameter, "delsp")) != NULL){
2721 if(!strucmp(parmval, "yes")){
2722 filters[filtcnt++].filter = gf_preflow;
2723 wrapflags |= GFW_DELSP;
2726 fs_give((void **) &parmval);
2730 * if there's no prefix we're forwarding text
2731 * otherwise it's reply text. unless the user's
2732 * tied our hands, alter the prefix to continue flowed
2733 * formatting...
2735 if(flow_res && ps_global->reply.use_flowed)
2736 wrapflags |= GFW_FLOW_RESULT;
2738 filters[filtcnt].filter = gf_wrap;
2740 * The 80 will cause longer lines than what is likely
2741 * set by composer_fillcol, so we'll have longer
2742 * quoted and forwarded lines than the lines we type.
2743 * We're fine with that since the alternative is the
2744 * possible wrapping of lines we shouldn't have, which
2745 * is mitigated by this higher 80 limit.
2747 * If we were to go back to using composer_fillcol,
2748 * the correct value is composer_fillcol + 1, pine
2749 * is off-by-one from pico.
2751 filters[filtcnt++].data = gf_wrap_filter_opt(
2752 MAX(MIN(ps_global->ttyo->screen_cols
2753 - (prefix ? strlen(prefix) : 0),
2754 80 - (prefix ? strlen(prefix) : 0)),
2755 30), /* doesn't have to be 30 */
2756 990, /* 998 is the SMTP limit */
2757 NULL, 0, wrapflags);
2762 * if not flowed, remove trailing whitespace to reduce
2763 * confusion, since we're sending out as flowed if we
2764 * can. At some future point, we might try
2765 * plugging in a user-option-controlled heuristic
2766 * flowing filter
2768 * We also want to fold "> " quotes so we get the
2769 * attributions correct.
2771 if(flow_res && ps_global->reply.use_flowed && prefix && !strucmp(prefix, "> "))
2772 *(prefix_p = prefix + 1) = '\0';
2773 ps_global->reply.use_flowed = 1; /* reset for next call */
2774 if(!(wrapflags & GFW_FLOWED)
2775 && flow_res){
2776 filters[filtcnt].filter = gf_line_test;
2777 filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL);
2779 filters[filtcnt].filter = gf_line_test;
2780 filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL);
2783 else if(body->subtype){
2784 int plain_opt = 1;
2786 if(strucmp(body->subtype,"richtext") == 0){
2787 filters[filtcnt].filter = gf_rich2plain;
2788 filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
2790 else if(strucmp(body->subtype,"enriched") == 0){
2791 filters[filtcnt].filter = gf_enriched2plain;
2792 filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
2794 else if(strucmp(body->subtype,"html") == 0){
2795 if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){
2796 filters[filtcnt].filter = gf_html2plain;
2797 filters[filtcnt++].data = gf_html2plain_opt(NULL,
2798 ps_global->ttyo->screen_cols,
2799 non_messageview_margin(),
2800 NULL, NULL, GFHP_STRIPPED);
2805 if(prefix){
2806 if(ps_global->reply.strip_signature){
2807 dashdata = 0;
2808 filters[filtcnt].filter = gf_line_test;
2809 filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata);
2812 filters[filtcnt].filter = gf_prefix;
2813 filters[filtcnt++].data = gf_prefix_opt(prefix);
2815 if(wrapflags & GFW_FLOWED || flow_res){
2816 filters[filtcnt].filter = gf_line_test;
2817 filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL);
2821 if(flags & GBPT_DELQUOTES){
2822 memset(&dq, 0, sizeof(dq));
2823 dq.lines = Q_DEL_ALL;
2824 dq.is_flowed = 0;
2825 dq.indent_length = 0;
2826 dq.saved_line = &free_this;
2827 dq.handlesp = NULL;
2828 dq.do_color = 0;
2829 dq.delete_all = 1;
2831 filters[filtcnt].filter = gf_line_test;
2832 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
2835 err = detach(stream, msg_no, part_no, partial, &len, pc,
2836 filters[0].filter ? filters : NULL,
2837 ((flags & GBPT_PEEK) ? FT_PEEK : 0)
2838 | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0));
2840 if(free_this)
2841 fs_give((void **) &free_this);
2843 if(prefix_p)
2844 *prefix_p = ' ';
2846 if (err != (char *) NULL)
2847 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2848 q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
2849 err, (void *) msg_no);
2851 if(we_cancel)
2852 cancel_busy_cue(-1);
2854 return((int) len);
2859 quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local)
2861 char *p;
2863 if(*line == '>'){
2864 for(p = line; *p; p++){
2865 if(isspace((unsigned char) *p)){
2866 if(*(p+1) == '>')
2867 ins = gf_line_test_new_ins(ins, p, "", -1);
2869 else if(*p != '>')
2870 break;
2874 return(0);
2879 twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2881 char *p, *ws = NULL;
2883 for(p = line; *p; p++){
2884 /* don't strip trailing space on signature line */
2885 if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))
2886 break;
2888 if(isspace((unsigned char) *p)){
2889 if(!ws)
2890 ws = p;
2892 else
2893 ws = NULL;
2896 if(ws)
2897 ins = gf_line_test_new_ins(ins, ws, "", -(p - ws));
2899 return(0);
2903 post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local)
2905 char *p;
2907 for(p = line; *p; p++)
2908 if(*p != '>'){
2909 if(p != line && *p != ' ')
2910 ins = gf_line_test_new_ins(ins, p, " ", 1);
2912 break;
2915 return(0);
2920 sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2922 if(*((int *)local)
2923 || (*line == '-' && *(line+1) == '-'
2924 && *(line+2) == ' ' && !*(line+3))){
2925 *((int *) local) = 1;
2926 return(2); /* skip this line! */
2929 return(0);
2933 /*----------------------------------------------------------------------
2934 return the c-client reference name for the given end_body part
2935 ----*/
2936 char *
2937 body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body)
2939 BODY *body;
2941 (void) pine_mail_fetchstructure(stream, msgno, &body);
2942 return(partno(body, end_body));
2946 /*----------------------------------------------------------------------
2947 return the c-client reference name for the given end_body part
2948 ----*/
2949 char *
2950 partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body)
2952 #define PARTTMPLEN 64
2953 PART *part;
2954 int num = 0;
2955 char tmp[PARTTMPLEN], *p = NULL;
2957 if(body && body->type == TYPEMULTIPART) {
2958 part = body->nested.part; /* first body part */
2960 do { /* for each part */
2961 num++; /* PARTTMPLEN = sizeof(tmp) */
2962 if(&part->body == end_body || (p = partno(&part->body, end_body))){
2963 snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "",
2964 PARTTMPLEN-10, (p) ? p : "");
2965 tmp[sizeof(tmp)-1] = '\0';
2966 if(p)
2967 fs_give((void **)&p);
2969 return(cpystr(tmp));
2971 } while ((part = part->next) != NULL); /* until done */
2973 return(NULL);
2975 else if(body && body->type == TYPEMESSAGE && body->subtype
2976 && !strucmp(body->subtype, "rfc822")){
2977 return(partno(body->nested.msg->body, end_body));
2980 return((body == end_body) ? cpystr("1") : NULL);
2984 /*----------------------------------------------------------------------
2985 Fill in the contents of each body part
2987 Args: stream -- Stream the message is on
2988 msgno -- Message number the body structure is for
2989 section -- body section associated with body pointer
2990 body -- Body pointer to fill in
2992 Result: 1 if all went OK, 0 if there was a problem
2994 This function copies the contents from an original message/body to
2995 a new message/body. It recurses down all multipart levels.
2997 If one or more part (but not all) can't be fetched, a status message
2998 will be queued.
2999 ----*/
3001 fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body)
3003 char *tp;
3004 int got_one = 0;
3006 if(!body->id)
3007 body->id = generate_message_id();
3009 if(body->type == TYPEMULTIPART){
3010 char subsection[256], *subp;
3011 int n, last_one = 10; /* remember worst case */
3012 PART *part = body->nested.part;
3014 if(!(part = body->nested.part))
3015 return(0);
3017 subp = subsection;
3018 if(section && *section){
3019 for(n = 0;
3020 n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++)
3023 *subp++ = '.';
3026 n = 1;
3027 do {
3028 snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++);
3029 subsection[sizeof(subsection)-1] = '\0';
3030 got_one = fetch_contents(stream, msgno, subsection, &part->body);
3031 last_one = MIN(last_one, got_one);
3033 while((part = part->next) != NULL);
3035 return(last_one);
3038 if(body->contents.text.data)
3039 return(1); /* already taken care of... */
3041 if(body->type == TYPEMESSAGE){
3042 if(body->subtype && strucmp(body->subtype,"external-body")){
3044 * the idea here is to fetch everything into storage objects
3046 body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
3047 EDIT_ACCESS);
3048 if(body->contents.text.data
3049 && (tp = pine_mail_fetch_body(stream, msgno, section,
3050 &body->size.bytes, NIL))){
3051 so_truncate((STORE_S *)body->contents.text.data,
3052 body->size.bytes + 2048);
3053 so_nputs((STORE_S *)body->contents.text.data, tp,
3054 body->size.bytes);
3055 got_one = 1;
3057 else
3058 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3059 _("Error fetching part %s"), section);
3060 } else {
3061 got_one = 1;
3063 } else {
3065 * the idea here is to fetch everything into storage objects
3066 * so, grab one, then fetch the body part
3068 body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
3069 if(body->contents.text.data
3070 && (tp=pine_mail_fetch_body(stream, msgno, section,
3071 &body->size.bytes, NIL))){
3072 so_truncate((STORE_S *)body->contents.text.data,
3073 body->size.bytes + 2048);
3074 so_nputs((STORE_S *)body->contents.text.data, tp,
3075 body->size.bytes);
3076 got_one = 1;
3078 else
3079 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3080 _("Error fetching part %s"), section);
3083 return(got_one);
3087 /*----------------------------------------------------------------------
3088 Copy the body structure
3090 Args: new_body -- Pointer to already allocated body, or NULL, if none
3091 old_body -- The Body to copy
3094 This is traverses the body structure recursively copying all elements.
3095 The new_body parameter can be NULL in which case a new body is
3096 allocated. Alternatively it can point to an already allocated body
3097 structure. This is used when copying body parts since a PART includes a
3098 BODY. The contents fields are *not* filled in.
3099 ----*/
3101 BODY *
3102 copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body)
3104 if(old_body == NULL)
3105 return(NULL);
3107 if(new_body == NULL)
3108 new_body = mail_newbody();
3110 new_body->type = old_body->type;
3111 new_body->encoding = old_body->encoding;
3113 if(old_body->subtype)
3114 new_body->subtype = cpystr(old_body->subtype);
3116 new_body->parameter = copy_parameters(old_body->parameter);
3118 if(old_body->id)
3119 new_body->id = cpystr(old_body->id);
3121 if(old_body->description)
3122 new_body->description = cpystr(old_body->description);
3124 if(old_body->disposition.type)
3125 new_body->disposition.type = cpystr(old_body->disposition.type);
3127 new_body->disposition.parameter
3128 = copy_parameters(old_body->disposition.parameter);
3130 new_body->size = old_body->size;
3132 if(new_body->type == TYPEMESSAGE
3133 && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
3134 new_body->nested.msg = mail_newmsg();
3135 new_body->nested.msg->body
3136 = copy_body(NULL, old_body->nested.msg->body);
3138 else if(new_body->type == TYPEMULTIPART) {
3139 PART **new_partp, *old_part;
3141 new_partp = &new_body->nested.part;
3142 for(old_part = old_body->nested.part;
3143 old_part != NULL;
3144 old_part = old_part->next){
3145 *new_partp = mail_newbody_part();
3146 copy_body(&(*new_partp)->body, &old_part->body);
3147 new_partp = &(*new_partp)->next;
3151 return(new_body);
3155 /*----------------------------------------------------------------------
3156 Copy the MIME parameter list
3158 Allocates storage for new part, and returns pointer to new paramter
3159 list. If old_p is NULL, NULL is returned.
3160 ----*/
3161 PARAMETER *
3162 copy_parameters(PARAMETER *old_p)
3164 PARAMETER *new_p, *p1, *p2;
3166 if(old_p == NULL)
3167 return((PARAMETER *)NULL);
3169 new_p = p2 = NULL;
3170 for(p1 = old_p; p1 != NULL; p1 = p1->next){
3171 set_parameter(&p2, p1->attribute, p1->value);
3172 if(new_p == NULL)
3173 new_p = p2;
3176 return(new_p);
3180 /*----------------------------------------------------------------------
3181 Make a complete copy of an envelope and all it's fields
3183 Args: e -- the envelope to copy
3185 Result: returns the new envelope, or NULL, if the given envelope was NULL
3187 ----*/
3189 ENVELOPE *
3190 copy_envelope(register ENVELOPE *e)
3192 register ENVELOPE *e2;
3194 if(!e)
3195 return(NULL);
3197 e2 = mail_newenvelope();
3198 e2->remail = e->remail ? cpystr(e->remail) : NULL;
3199 e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
3200 e2->date = e->date ? (unsigned char *)cpystr((char *) e->date)
3201 : NULL;
3202 e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL;
3203 e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL;
3204 e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL;
3205 e2->subject = e->subject ? cpystr(e->subject) : NULL;
3206 e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL;
3207 e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL;
3208 e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL;
3209 e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL;
3210 e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL;
3211 e2->message_id = e->message_id ? cpystr(e->message_id) : NULL;
3212 e2->references = e->references ? cpystr(e->references) : NULL;
3213 e2->followup_to = e->followup_to ? cpystr(e->references) : NULL;
3214 return(e2);
3218 /*----------------------------------------------------------------------
3219 Generate the "In-reply-to" text from message header
3221 Args: message -- Envelope of original message
3223 Result: returns an alloc'd string or NULL if there is a problem
3224 ----*/
3225 char *
3226 reply_in_reply_to(ENVELOPE *env)
3228 return((env && env->message_id) ? cpystr(env->message_id) : NULL);
3232 /*----------------------------------------------------------------------
3233 Generate a unique message id string.
3235 Args: ps -- The usual pine structure
3237 Result: Alloc'd unique string is returned
3239 Uniqueness is gaurenteed by using the host name, process id, date to the
3240 second and a single unique character
3241 *----------------------------------------------------------------------*/
3242 char *
3243 generate_message_id(void)
3245 static short osec = 0, cnt = 0;
3246 char idbuf[128];
3247 char *id;
3248 time_t now;
3249 struct tm *now_x;
3250 char *hostpart = NULL;
3251 char *alpine_name = NULL;
3252 char *alpine_version = NULL;
3253 char *system_os = NULL;
3255 now = time((time_t *)0);
3256 now_x = localtime(&now);
3258 if(now_x->tm_sec == osec)
3259 cnt++;
3260 else{
3261 cnt = 0;
3262 osec = now_x->tm_sec;
3265 if(F_ON(F_ROT13_MESSAGE_ID, ps_global)){
3266 hostpart = rot13(ps_global->hostname);
3267 alpine_name = rot13("alpine");
3268 alpine_version = rot5n(ALPINE_VERSION);
3269 system_os = rot13(SYSTYPE);
3270 } else {
3271 hostpart = cpystr(ps_global->hostname);
3272 alpine_name = cpystr("alpine");
3273 alpine_version = cpystr(ALPINE_VERSION);
3274 system_os = cpystr(SYSTYPE);
3277 if(!hostpart)
3278 hostpart = cpystr("huh");
3280 snprintf(idbuf, sizeof(idbuf), "<%.6s.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
3281 alpine_name, system_os, alpine_version, (now_x->tm_year) % 100, now_x->tm_mon + 1,
3282 now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec,
3283 cnt, getpid(), hostpart);
3284 idbuf[sizeof(idbuf)-1] = '\0';
3286 id = cpystr(idbuf);
3288 if(hostpart) fs_give((void **) &hostpart);
3289 if(alpine_name) fs_give((void **) & alpine_name);
3290 if(alpine_version) fs_give((void **)&alpine_version);
3291 if(system_os) fs_give((void **)&system_os);
3293 return(id);
3297 char *
3298 generate_user_agent(void)
3300 char buf[128];
3301 char rev[128];
3303 if(F_ON(F_QUELL_USERAGENT, ps_global))
3304 return(NULL);
3306 snprintf(buf, sizeof(buf),
3307 "%sAlpine %s (%s %s)",
3308 (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "",
3309 ALPINE_VERSION, SYSTYPE,
3310 get_alpine_revision_string(rev, sizeof(rev)));
3312 return(cpystr(buf));
3316 char *
3317 rot13(char *src)
3319 char byte, cap, *p, *ret = NULL;
3321 if(src && *src){
3322 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3323 p = ret;
3324 while((byte = *src++) != '\0'){
3325 cap = byte & 32;
3326 byte &= ~cap;
3327 *p++ = ((byte >= 'A') && (byte <= 'Z')
3328 ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap;
3331 *p = '\0';
3334 return(ret);
3337 char *
3338 rot5n(char *src)
3340 char byte, *p, *ret = NULL;
3342 if(src && *src){
3343 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3344 p = ret;
3345 while((byte = *src++) != '\0')
3346 *p++ = ((byte >= '0') && (byte <= '9')
3347 ? ((byte - '0' + 5) % 10 + '0') : byte);
3348 *p = '\0';
3351 return(ret);
3355 /*----------------------------------------------------------------------
3356 Return the first true address pointer (modulo group syntax allowance)
3358 Args: addr -- Address list
3360 Result: First real address pointer, or NULL
3361 ----------------------------------------------------------------------*/
3362 ADDRESS *
3363 first_addr(struct mail_address *addr)
3365 while(addr && !addr->host)
3366 addr = addr->next;
3368 return(addr);
3372 /*----------------------------------------------------------------------
3373 lit -- this is the source
3374 prenewlines -- prefix the file contents with this many newlines
3375 postnewlines -- postfix the file contents with this many newlines
3376 is_sig -- this is a signature (not a template)
3377 decode_constants -- change C-style constants into their values
3378 ----*/
3379 char *
3380 get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants)
3382 char *sig = NULL;
3385 * Should make this smart enough not to do the copying and double
3386 * allocation of space.
3388 if(lit){
3389 char *tmplit = NULL, *p, *q, *d, save;
3390 size_t len;
3392 if(decode_constants){
3393 tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char));
3394 tmplit[0] = '\0';
3395 cstring_to_string(lit, tmplit);
3397 else
3398 tmplit = cpystr(lit);
3400 len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE);
3401 sig = (char *) fs_get((len+1) * sizeof(char));
3402 memset(sig, 0, len+1);
3403 d = sig;
3404 while(prenewlines--)
3405 sstrncpy(&d, NEWLINE, len-(d-sig));
3407 if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
3408 !sigdashes_are_present(tmplit)){
3409 sstrncpy(&d, SIGDASHES, len-(d-sig));
3410 sstrncpy(&d, NEWLINE, len-(d-sig));
3413 sig[len] = '\0';
3415 p = tmplit;
3416 while(*p){
3417 /* get a line */
3418 q = strpbrk(p, "\n\r");
3419 if(q){
3420 save = *q;
3421 *q = '\0';
3425 * Strip trailing space if we are doing a signature and
3426 * this line is not sigdashes.
3428 if(is_sig && strcmp(p, SIGDASHES))
3429 removing_trailing_white_space(p);
3431 while((d-sig) <= len && (*d = *p++) != '\0')
3432 d++;
3434 if(q){
3435 if((d-sig) <= len)
3436 *d++ = save;
3438 p = q+1;
3440 else
3441 break;
3444 while(postnewlines--)
3445 sstrncpy(&d, NEWLINE, len-(d-sig));
3447 sig[len] = '\0';
3449 if((d-sig) <= len)
3450 *d = '\0';
3452 if(tmplit)
3453 fs_give((void **) &tmplit);
3456 return(sig);
3461 sigdashes_are_present(char *sig)
3463 char *p;
3465 p = srchstr(sig, SIGDASHES);
3466 while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
3467 (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
3468 p = srchstr(p+1, SIGDASHES);
3470 return(p ? 1 : 0);
3474 /*----------------------------------------------------------------------
3475 Acquire the pinerc defined signature file pathname
3477 ----*/
3478 char *
3479 signature_path(char *sname, char *sbuf, size_t len)
3481 *sbuf = '\0';
3482 if(sname && *sname){
3483 size_t spl = strlen(sname);
3484 if(IS_REMOTE(sname)){
3485 if(spl < len - 1)
3486 strncpy(sbuf, sname, len-1);
3488 else if(is_absolute_path(sname)){
3489 strncpy(sbuf, sname, len-1);
3490 sbuf[len-1] = '\0';
3491 fnexpand(sbuf, len);
3493 else if(ps_global->VAR_OPER_DIR){
3494 if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
3495 build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len);
3497 else{
3498 char *lc = last_cmpnt(ps_global->pinerc);
3500 sbuf[0] = '\0';
3501 if(lc != NULL){
3502 strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc));
3503 sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0';
3506 strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0));
3507 sbuf[len-1] = '\0';
3511 return(*sbuf ? sbuf : NULL);
3515 char *
3516 simple_read_remote_file(char *name, char *subtype)
3518 int try_cache;
3519 REMDATA_S *rd;
3520 char *file = NULL;
3523 dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?"));
3526 * We could parse the name here to find what type it is. So far we
3527 * only have type RemImap.
3529 rd = rd_create_remote(RemImap, name, subtype,
3530 NULL, _("Error: "), _("Can't fetch remote configuration."));
3531 if(!rd)
3532 goto bail_out;
3534 try_cache = rd_read_metadata(rd);
3536 if(rd->access == MaybeRorW){
3537 if(rd->read_status == 'R')
3538 rd->access = ReadOnly;
3539 else
3540 rd->access = ReadWrite;
3543 if(rd->access != NoExists){
3545 rd_check_remvalid(rd, 1L);
3548 * If the cached info says it is readonly but
3549 * it looks like it's been fixed now, change it to readwrite.
3551 if(rd->read_status == 'R'){
3553 * We go to this trouble since readonly sigfiles
3554 * are likely a mistake. They are usually supposed to be
3555 * readwrite so we open it and check if it's been fixed.
3557 rd_check_readonly_access(rd);
3558 if(rd->read_status == 'W'){
3559 rd->access = ReadWrite;
3560 rd->flags |= REM_OUTOFDATE;
3562 else
3563 rd->access = ReadOnly;
3566 if(rd->flags & REM_OUTOFDATE){
3567 if(rd_update_local(rd) != 0){
3569 dprint((1,
3570 "simple_read_remote_file: rd_update_local failed\n"));
3572 * Don't give up altogether. We still may be
3573 * able to use a cached copy.
3576 else{
3577 dprint((7,
3578 "%s: copied remote to local (%ld)\n",
3579 rd->rn ? rd->rn : "?", (long)rd->last_use));
3583 if(rd->access == ReadWrite)
3584 rd->flags |= DO_REMTRIM;
3587 /* If we couldn't get to remote folder, try using the cached copy */
3588 if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){
3589 if(try_cache){
3590 rd->access = ReadOnly;
3591 rd->flags |= USE_OLD_CACHE;
3592 q_status_message(SM_ORDER, 3, 4,
3593 "Can't contact remote server, using cached copy");
3594 dprint((2,
3595 "Can't open remote file %s, using local cached copy %s readonly\n",
3596 rd->rn ? rd->rn : "?",
3597 rd->lf ? rd->lf : "?"));
3599 else{
3600 rd->flags &= ~DO_REMTRIM;
3601 goto bail_out;
3605 file = read_file(rd->lf, READ_FROM_LOCALE);
3607 bail_out:
3608 if(rd)
3609 rd_close_remdata(&rd);
3611 return(file);
3614 /* special handling for messages that contain a mixed part in the
3615 * multipart alternative section.
3617 BODY *
3618 forward_multi_alt_mixed(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3619 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3621 #define FWDTMPLEN 256
3622 BODY *body = NULL, *text_body = NULL;
3623 PART *part = NULL;
3624 char prefix_buf[FWDTMPLEN];
3625 char *new_charset = NULL;
3626 int partnum;
3627 char *section, sect_buf[256];
3628 int forward_raw_body = 0;
3630 if(orig_body
3631 && orig_body->type == TYPEMULTIPART
3632 && orig_body->subtype
3633 && !strucmp(orig_body->subtype, "alternative"))
3634 for(part = orig_body->nested.part, partnum = 1;
3635 part;
3636 part = part->next, partnum++)
3637 if(part->body.type == TYPEMULTIPART
3638 && part->body.subtype
3639 && !strucmp(part->body.subtype, "MIXED"))
3640 break;
3642 if(part == NULL) return NULL;
3644 snprintf(prefix_buf, sizeof(prefix_buf), "%.*s%s%s%d",
3645 FWDTMPLEN/2, sect_prefix ? sect_prefix : "",
3646 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3647 partnum);
3648 prefix_buf[sizeof(prefix_buf)-1] = '\0';
3650 if(ps_global->full_header == 2
3651 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
3652 forward_raw_body = 1;
3653 if(sp_expunge_count(stream))
3654 return(NULL);
3656 if(sect_prefix && forward_raw_body == 0)
3657 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
3658 else if(sect_prefix && forward_raw_body)
3659 section = sect_prefix;
3660 else if(!sect_prefix && forward_raw_body)
3661 section = NULL;
3662 else
3663 section = "1";
3664 sect_buf[sizeof(sect_buf)-1] = '\0';
3666 body = copy_body(NULL, &part->body);
3668 /*--- The text part of the message ---*/
3669 if(!body->nested.part){
3670 q_status_message(SM_ORDER | SM_DING, 3, 6,
3671 "Error referencing body part 1");
3672 mail_free_body(&body);
3674 else if(body->nested.part->body.type == TYPETEXT) {
3675 char *new_charset = NULL;
3677 /*--- The first part is text ----*/
3678 text_body = &body->nested.part->body;
3679 text_body->contents.text.data = msgtext;
3680 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
3681 /* this text is going to the composer, it should be Plain */
3682 fs_give((void **)&text_body->subtype);
3683 text_body->subtype = cpystr("PLAIN");
3685 if(!(flags & FWD_ANON)){
3686 forward_delimiter(pc);
3687 reply_forward_header(stream, msgno,
3688 sect_prefix, env, pc, "");
3691 if(!(get_body_part_text(stream, &part->body,
3692 msgno, section, 0L, pc,
3693 NULL, &new_charset, GBPT_NONE)
3694 && fetch_contents(stream, msgno, prefix_buf, body)))
3695 mail_free_body(&body);
3696 else if(new_charset)
3697 set_parameter(&text_body->parameter, "charset", new_charset);
3699 return(body);
3703 /*----------------------------------------------------------------------
3704 Build the body for the multipart/alternative part
3706 Args:
3708 Result:
3710 ----------------------------------------------------------------------*/
3711 BODY *
3712 forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3713 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3715 #define FWDTMPLEN 256
3716 BODY *body = NULL;
3717 PART *part = NULL, *bestpart = NULL;
3718 char tmp_buf[FWDTMPLEN];
3719 char *new_charset = NULL;
3720 int partnum, bestpartnum;
3722 /* try multipart mixed first */
3723 if((body = forward_multi_alt_mixed(stream, env, orig_body,
3724 msgno, sect_prefix, msgtext, pc, flags)) != NULL)
3725 return body;
3727 if(ps_global->force_prefer_plain
3728 || (!ps_global->force_no_prefer_plain
3729 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
3730 for(part = orig_body->nested.part, partnum = 1;
3731 part;
3732 part = part->next, partnum++)
3733 if((!part->body.type || part->body.type == TYPETEXT)
3734 && (!part->body.subtype
3735 || !strucmp(part->body.subtype, "plain")))
3736 break;
3740 * Else choose last alternative among plain or html parts.
3741 * Perhaps we should really be using mime_show() to make this
3742 * potentially more general than just plain or html.
3744 if(!part){
3745 for(part = orig_body->nested.part, partnum = 1;
3746 part;
3747 part = part->next, partnum++){
3748 if((!part->body.type || part->body.type == TYPETEXT)
3749 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
3751 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
3752 bestpart = part;
3753 bestpartnum = partnum;
3757 part = bestpart;
3758 partnum = bestpartnum;
3762 * IF something's interesting insert it
3763 * AND forget the rest of the multipart
3765 if(part){
3766 body = mail_newbody();
3767 body->type = TYPETEXT;
3768 body->contents.text.data = msgtext;
3770 /* record character set, flowing, etc */
3771 body->parameter = copy_parameters(part->body.parameter);
3772 body->size.bytes = part->body.size.bytes;
3774 if(!(flags & FWD_ANON)){
3775 forward_delimiter(pc);
3776 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
3778 /* FWDTMPLEN = sizeof(tmp_buf) */
3779 snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d",
3780 FWDTMPLEN/2, sect_prefix ? sect_prefix : "",
3781 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3782 partnum);
3783 tmp_buf[sizeof(tmp_buf)-1] = '\0';
3784 get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc,
3785 NULL, &new_charset, GBPT_NONE);
3788 * get_body_part_text translated the data to a new charset.
3789 * We need to record that fact in body.
3791 if(new_charset)
3792 set_parameter(&body->parameter, "charset", new_charset);
3794 else
3795 q_status_message(SM_ORDER | SM_DING, 3, 3,
3796 "No suitable part found. Forwarding as attachment");
3798 return(body);
3802 void
3803 reply_append_addr(struct mail_address **dest, struct mail_address *src)
3805 for( ; *dest; dest = &(*dest)->next)
3808 *dest = src;