Add support for tab-completion when selecting by rule
[alpine.git] / pith / reply.c
blob41ae9fe6b778978fadde1bc8c78c3db9e92bac9f
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/reply.h"
17 #include "../pith/send.h"
18 #include "../pith/init.h"
19 #include "../pith/state.h"
20 #include "../pith/conf.h"
21 #include "../pith/remote.h"
22 #include "../pith/status.h"
23 #include "../pith/mailview.h"
24 #include "../pith/filter.h"
25 #include "../pith/newmail.h"
26 #include "../pith/bldaddr.h"
27 #include "../pith/mailindx.h"
28 #include "../pith/mimedesc.h"
29 #include "../pith/detach.h"
30 #include "../pith/help.h"
31 #include "../pith/pipe.h"
32 #include "../pith/addrstring.h"
33 #include "../pith/news.h"
34 #include "../pith/util.h"
35 #include "../pith/pattern.h"
36 #include "../pith/detoken.h"
37 #include "../pith/stream.h"
38 #include "../pith/busy.h"
39 #include "../pith/readfile.h"
40 #include "../pith/text.h"
41 #include "../pith/list.h"
42 #include "../pith/ablookup.h"
43 #include "../pith/mailcmd.h"
44 #include "../pith/margin.h"
45 #include "../pith/smime.h"
49 * Internal prototypes
51 void bounce_mask_header(char **, char *);
54 int (*pith_opt_replyto_prompt)(void);
55 int (*pith_opt_reply_to_all_prompt)(int *);
59 * standard type of storage object used for body parts...
61 #define PART_SO_TYPE CharStar
64 char *(*pith_opt_user_agent_prefix)(void);
66 /* compare two subjects and return if they are the same.
67 We compare stripped subjects, that is, those that do
68 not have re/fwd. Before we compare the subjects, we
69 decode them in case they were encoded.
70 Return value: 0 - not the same subject, 1 - same subject.
73 int
74 same_subject(char *s, char *t)
76 int rv = 0;
77 int i, j, len;
78 char *s1, *s2; /* holds decoded subjects from s and t */
79 char *u, *v;
81 if (s == NULL || t == NULL)
82 return s == t ? 1 : 0;
84 i = strlen(s); j = strlen(t);
85 len = i < j ? j : i;
86 u = fs_get(6*len+1);
87 v = fs_get(6*len+1);
88 s1 = (char *) rfc1522_decode_to_utf8((unsigned char *) u, 6*len + 1, s);
89 s2 = (char *) rfc1522_decode_to_utf8((unsigned char *) v, 6*len + 1, t);
90 mail_strip_subject(s1, &u);
91 mail_strip_subject(s2, &v);
93 rv = !strucmp(u, v);
95 fs_give((void **) &u);
96 fs_give((void **) &v);
97 return rv;
101 * reply_harvest -
103 * Returns: 1 if addresses successfully copied
104 * 0 on user cancel or error
106 * Input flags:
107 * RSF_FORCE_REPLY_TO
108 * RSF_QUERY_REPLY_ALL
109 * RSF_FORCE_REPLY_ALL
111 * Output flags:
112 * RSF_FORCE_REPLY_ALL
116 reply_harvest(struct pine *ps, long int msgno, char *section, ENVELOPE *env,
117 struct mail_address **saved_from, struct mail_address **saved_to,
118 struct mail_address **saved_cc, struct mail_address **saved_resent,
119 int *flags)
121 ADDRESS *ap, *ap2, *rep_address;
122 int ret = 0, sniff_resent = 0;
123 char *rep_field;
126 * If Reply-To is same as From just treat it like it was From.
127 * Otherwise, always use the reply-to if we're replying to more
128 * than one msg or say ok to using it, even if it's us.
129 * If there's no reply-to or it's the same as the from, assume
130 * that the user doesn't want to reply to himself, unless there's
131 * nobody else.
133 if(env->reply_to && !addr_lists_same(env->reply_to, env->from)
134 && (F_ON(F_AUTO_REPLY_TO, ps)
135 || ((*flags) & RSF_FORCE_REPLY_TO)
136 || (pith_opt_replyto_prompt && (*pith_opt_replyto_prompt)() == 'y'))){
137 rep_field = "reply-to";
138 rep_address = env->reply_to;
140 else{
141 rep_field = "From";
142 rep_address = env->from;
145 ap = reply_cp_addr(ps, msgno, section, rep_field, *saved_from,
146 (ADDRESS *) NULL, rep_address, RCA_NOT_US);
148 if(ret == 'x') {
149 cmd_cancelled("Reply");
150 return(0);
153 reply_append_addr(saved_from, ap);
155 /*--------- check for other recipients ---------*/
156 if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){
158 if((ap = reply_cp_addr(ps, msgno, section, "To", *saved_to,
159 *saved_from, env->to, RCA_NOT_US)) != NULL)
160 reply_append_addr(saved_to, ap);
162 if((ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc,
163 *saved_from, env->cc, RCA_NOT_US)) != NULL)
164 reply_append_addr(saved_cc, ap);
167 * In these cases, we either need to look at the resent headers
168 * to include in the reply-to-all, or to decide whether or not
169 * we need to ask the reply-to-all question.
171 if(((*flags) & RSF_FORCE_REPLY_ALL)
172 || (((*flags) & RSF_QUERY_REPLY_ALL)
173 && ((!(*saved_from) && !(*saved_cc))
174 || (*saved_from && !(*saved_to) && !(*saved_cc))))){
176 sniff_resent++;
177 if((ap2 = reply_resent(ps, msgno, section)) != NULL){
179 * look for bogus addr entries and replace
181 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
182 *saved_from, ap2, RCA_NOT_US)) != NULL)
184 reply_append_addr(saved_resent, ap);
186 mail_free_address(&ap2);
191 * It makes sense to ask reply-to-all now.
193 if(((*flags) & RSF_QUERY_REPLY_ALL)
194 && ((*saved_from && (*saved_to || *saved_cc || *saved_resent))
195 || (*saved_cc || *saved_resent))){
196 *flags &= ~RSF_QUERY_REPLY_ALL;
197 if(pith_opt_reply_to_all_prompt
198 && (*pith_opt_reply_to_all_prompt)(flags) < 0){
199 cmd_cancelled("Reply");
200 return(0);
205 * If we just answered yes to the reply-to-all question and
206 * we still haven't collected the resent headers, do so now.
208 if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent
209 && (ap2 = reply_resent(ps, msgno, section))){
211 * look for bogus addr entries and replace
213 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
214 *saved_from, ap2, RCA_NOT_US)) != NULL)
215 reply_append_addr(saved_resent, ap);
217 mail_free_address(&ap2);
221 return(1);
225 /*----------------------------------------------------------------------
226 Return a pointer to a copy of the given address list
227 filtering out those already in the "mask" lists and ourself.
229 Args: mask1 -- Don't copy if in this list
230 mask2 -- or if in this list
231 source -- List to be copied
232 us_too -- Don't filter out ourself.
233 flags -- RCA_NOT_US copy all addrs except for our own
234 RCA_ALL copy all addrs, including our own
235 RCA_ONLY_US copy only addrs that are our own
237 ---*/
238 ADDRESS *
239 reply_cp_addr(struct pine *ps, long int msgno, char *section, char *field,
240 struct mail_address *mask1, struct mail_address *mask2,
241 struct mail_address *source, int flags)
243 ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail;
245 /* can only choose one of these flags values */
246 assert(!((flags & RCA_ALL && flags & RCA_ONLY_US)
247 || (flags & RCA_ALL && flags & RCA_NOT_US)
248 || (flags & RCA_ONLY_US && flags & RCA_NOT_US)));
250 for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next)
251 if(tmp1->host && tmp1->host[0] == '.'){
252 char *h, *fields[2];
254 fields[0] = field;
255 fields[1] = NULL;
256 if((h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL,
257 msgno, section, fields)) != NULL){
258 char *p, fname[32];
259 int q;
261 q = strlen(h);
263 strncpy(fname, field, sizeof(fname)-2);
264 fname[sizeof(fname)-2] = '\0';
265 strncat(fname, ":", sizeof(fname)-strlen(fname)-1);
266 fname[sizeof(fname)-1] = '\0';
268 for(p = h; (p = strstr(p, fname)) != NULL; )
269 rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */
271 sqznewlines(h); /* blat out CR's & LF's */
272 for(p = h; (p = strchr(p, TAB)) != NULL; )
273 *p++ = ' '; /* turn TABs to whitespace */
275 if(*h){
276 long l;
277 size_t ll;
279 ret = (ADDRESS *) fs_get(sizeof(ADDRESS));
280 memset(ret, 0, sizeof(ADDRESS));
282 /* get rid of leading white space */
283 for(p = h; *p == SPACE; p++)
286 if(p != h){
287 memmove(h, p, l = strlen(p));
288 h[l] = '\0';
291 /* base64 armor plate the gunk to protect against
292 * c-client quoting in output.
294 p = (char *) rfc822_binary(h, strlen(h),
295 (unsigned long *) &l);
296 sqznewlines(p);
297 fs_give((void **) &h);
299 * Seems like the 4 ought to be a 2, but I'll leave it
300 * to be safe, in case something else adds 2 chars later.
302 ll = strlen(p) + 4;
303 ret->mailbox = (char *) fs_get(ll * sizeof(char));
304 snprintf(ret->mailbox, ll, "&%s", p);
305 ret->mailbox[ll-1] = '\0';
306 fs_give((void **) &p);
307 ret->host = cpystr(RAWFIELD);
311 return(ret);
314 ret_tail = &ret;
315 for(source = first_addr(source); source; source = source->next){
316 for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next)
317 if(address_is_same(source, tmp1)) /* it is in mask1, skip it */
318 break;
320 for(tmp2 = first_addr(mask2); !tmp1 && tmp2; tmp2 = tmp2->next)
321 if(address_is_same(source, tmp2)) /* it is in mask2, skip it */
322 break;
325 * If there's no match in masks and this address satisfies the
326 * flags requirement, copy it.
328 if(!tmp1 && !tmp2 /* no mask match */
329 && ((flags & RCA_ALL) /* including everybody */
330 || (flags & RCA_ONLY_US && address_is_us(source, ps))
331 || (flags & RCA_NOT_US && !address_is_us(source, ps)))){
332 tmp1 = source->next;
333 source->next = NULL; /* only copy one addr! */
334 *ret_tail = rfc822_cpy_adr(source);
335 ret_tail = &(*ret_tail)->next;
336 source->next = tmp1; /* restore rest of list */
340 return(ret);
344 ACTION_S *
345 set_role_from_msg(struct pine *ps, long int rflags, long int msgno, char *section)
347 ACTION_S *role = NULL;
348 PAT_S *pat = NULL;
349 SEARCHSET *ss = NULL;
350 PAT_STATE pstate;
352 if(!nonempty_patterns(rflags, &pstate))
353 return(role);
355 if(msgno > 0L){
356 ss = mail_newsearchset();
357 ss->first = ss->last = (unsigned long)msgno;
360 /* Go through the possible roles one at a time until we get a match. */
361 pat = first_pattern(&pstate);
363 /* calculate this message's score if needed */
364 if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES &&
365 get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF)
366 (void)calculate_some_scores(ps->mail_stream, ss, 0);
368 while(!role && pat){
369 if(match_pattern(pat->patgrp, ps->mail_stream, ss, section,
370 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
371 if(!pat->action || pat->action->bogus)
372 break;
374 role = pat->action;
376 else
377 pat = next_pattern(&pstate);
380 if(ss)
381 mail_free_searchset(&ss);
383 return(role);
388 * reply_seed - fill in reply header
391 void
392 reply_seed(struct pine *ps, ENVELOPE *outgoing, ENVELOPE *env,
393 struct mail_address *saved_from, struct mail_address *saved_to,
394 struct mail_address *saved_cc, struct mail_address *saved_resent,
395 char **fcc, int replytoall, char **errmsg)
397 ADDRESS **to_tail, **cc_tail;
399 to_tail = &outgoing->to;
400 cc_tail = &outgoing->cc;
402 if(saved_from){
403 /* Put Reply-To or From in To. */
404 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
405 (ADDRESS *) NULL, saved_from, RCA_ALL);
406 if(replytoall){
407 if(ps->reply.preserve_fields){
408 while(*to_tail)
409 to_tail = &(*to_tail)->next;
411 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
412 (ADDRESS *) NULL, saved_to, RCA_ALL);
414 while(*to_tail)
415 to_tail = &(*to_tail)->next;
417 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
418 outgoing->to, saved_resent, RCA_ALL);
420 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
421 outgoing->to, saved_cc, RCA_ALL);
423 else{ /* and the rest in cc */
424 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
425 outgoing->to, saved_to, RCA_ALL);
426 while(*cc_tail) /* stay on last address */
427 cc_tail = &(*cc_tail)->next;
429 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
430 outgoing->to, saved_cc, RCA_ALL);
431 while(*cc_tail)
432 cc_tail = &(*cc_tail)->next;
434 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
435 outgoing->to, saved_resent, RCA_ALL);
439 else if(saved_to){
440 /* No From (maybe from us), put To in To. */
441 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
442 (ADDRESS *)NULL, saved_to, RCA_ALL);
443 /* and the rest in cc */
444 if(replytoall){
445 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
446 outgoing->to, saved_cc, RCA_ALL);
447 while(*cc_tail)
448 cc_tail = &(*cc_tail)->next;
450 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
451 outgoing->to, saved_resent, RCA_ALL);
454 else{
455 /* No From or To, put everything else in To if replytoall, */
456 if(replytoall){
457 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
458 (ADDRESS *) NULL, saved_cc, RCA_ALL);
459 while(*to_tail)
460 to_tail = &(*to_tail)->next;
462 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
463 (ADDRESS *) NULL, saved_resent, RCA_ALL);
465 /* else, reply to original From which must be us */
466 else{
468 * Put self in To if in original From.
470 if(!outgoing->newsgroups)
471 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
472 (ADDRESS *) NULL, env->from, RCA_ALL);
476 /* add any missing personal data */
477 reply_fish_personal(outgoing, env);
479 /* get fcc */
480 if(fcc && outgoing->to && outgoing->to->host[0] != '.'){
481 *fcc = get_fcc_based_on_to(outgoing->to);
483 else if(fcc && outgoing->newsgroups){
484 char *newsgroups_returned = NULL;
485 int rv;
487 rv = news_grouper(outgoing->newsgroups, &newsgroups_returned, errmsg, fcc, NULL);
488 if(rv != -1 &&
489 strcmp(outgoing->newsgroups, newsgroups_returned)){
490 fs_give((void **)&outgoing->newsgroups);
491 outgoing->newsgroups = newsgroups_returned;
493 else
494 fs_give((void **) &newsgroups_returned);
499 /*----------------------------------------------------------------------
500 Test the given address lists for equivalence
502 Args: x -- First address list for comparison
503 y -- Second address for comparison
505 ---*/
507 addr_lists_same(struct mail_address *x, struct mail_address *y)
509 for(x = first_addr(x), y = first_addr(y);
510 x && y;
511 x = first_addr(x->next), y = first_addr(y->next)){
512 if(!address_is_same(x, y))
513 return(0);
516 return(!x && !y); /* true if ran off both lists */
520 /*----------------------------------------------------------------------
521 Test the given address against those in the given envelope's to, cc
523 Args: addr -- address for comparison
524 env -- envelope to compare against
526 ---*/
528 addr_in_env(struct mail_address *addr, ENVELOPE *env)
530 ADDRESS *ap;
532 for(ap = env ? env->to : NULL; ap; ap = ap->next)
533 if(address_is_same(addr, ap))
534 return(1);
536 for(ap = env ? env->cc : NULL; ap; ap = ap->next)
537 if(address_is_same(addr, ap))
538 return(1);
540 return(0); /* not found! */
544 /*----------------------------------------------------------------------
545 Add missing personal info dest from src envelope
547 Args: dest -- envelope to add personal info to
548 src -- envelope to get personal info from
550 NOTE: This is just kind of a courtesy function. It's really not adding
551 anything needed to get the mail thru, but it is nice for the user
552 under some odd circumstances.
553 ---*/
554 void
555 reply_fish_personal(ENVELOPE *dest, ENVELOPE *src)
557 ADDRESS *da, *sa;
559 for(da = dest ? dest->to : NULL; da; da = da->next){
560 if(da->personal && !da->personal[0])
561 fs_give((void **)&da->personal);
563 for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next)
564 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
565 da->personal = cpystr(sa->personal);
567 for(sa = src ? src->cc : 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);
572 for(da = dest ? dest->cc : NULL; da; da = da->next){
573 if(da->personal && !da->personal[0])
574 fs_give((void **)&da->personal);
576 for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next)
577 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
578 da->personal = cpystr(sa->personal);
580 for(sa = src ? src->cc : 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);
587 /*----------------------------------------------------------------------
588 Given a header field and envelope, build "References: " header data
590 Args:
592 Returns:
593 ---*/
594 char *
595 reply_build_refs(ENVELOPE *env)
597 int len, id_len, first_ref_len = 0, foldslop;
598 char *p, *refs = NULL, *h = env->references;
599 char *first_ref = NULL, *tail_refs = NULL;
602 if(!(env->message_id && (id_len = strlen(env->message_id))))
603 return(NULL);
605 if(h){
607 * The length we have to work with doesn't seem to appear in any
608 * standards. Steve Jones says that in comp.news discussions he
609 * has seen 1024 as the longest length of a header value.
610 * In the inn news source we find MAXHEADERSIZE = 1024. It appears
611 * that is the maximum length of the header value, including
612 * newlines for folded lines (that is, the newlines are counted).
613 * We'll be conservative and figure every reference will take up a
614 * line of its own when we fold. We'll also count 2 for CRLF instead
615 * of just one for LF just to be safe. hubert 2001-jan
616 * J.B. Moreno <planb@newsreaders.com> says "The server limit is
617 * more commonly encountered at 999/1000 bytes [...]". So we'll
618 * back off to 999 instead of 1024.
620 #define MAXHEADERSIZE (999)
622 /* count the total number of potential folds, max of 2 bytes each */
623 for(foldslop = 2, p = h; (p = strstr(p+1, "> <")); )
624 foldslop += 2;
626 if((len=strlen(h)) + 1+id_len + foldslop >= MAXHEADERSIZE
627 && (p = strstr(h, "> <"))){
629 * If the references line is so long that we are going to have
630 * to delete some of the references, delete the 2nd, 3rd, ...
631 * We don't want to delete the first message in the thread.
633 p[1] = '\0';
634 first_ref = cpystr(h);
635 first_ref_len = strlen(first_ref)+1; /* len includes space */
636 p[1] = ' ';
637 tail_refs = p+2;
638 /* get rid of 2nd, 3rd, ... until it fits */
639 while((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
640 foldslop >= MAXHEADERSIZE
641 && (p = strstr(tail_refs, "> <"))){
642 tail_refs = p + 2;
643 foldslop -= 2;
647 * If the individual references are seriously long, somebody
648 * is messing with us and we don't care if it works right.
650 if((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
651 foldslop >= MAXHEADERSIZE){
652 first_ref_len = len = 0;
653 tail_refs = NULL;
654 if(first_ref)
655 fs_give((void **)&first_ref);
658 else
659 tail_refs = h;
661 refs = (char *)fs_get((first_ref_len + 1+id_len + len + 1) *
662 sizeof(char));
663 snprintf(refs, first_ref_len + 1+id_len + len + 1, "%s%s%s%s%s",
664 first_ref ? first_ref : "",
665 first_ref ? " " : "",
666 tail_refs ? tail_refs : "",
667 tail_refs ? " " : "",
668 env->message_id);
669 refs[first_ref_len + 1+id_len + len] = '\0';
672 if(!refs && id_len)
673 refs = cpystr(env->message_id);
675 if(first_ref)
676 fs_give((void **)&first_ref);
678 return(refs);
683 /*----------------------------------------------------------------------
684 Snoop for any Resent-* headers, and return an ADDRESS list
686 Args: stream --
687 msgno --
689 Returns: either NULL if no Resent-* or parsed ADDRESS struct list
690 ---*/
691 ADDRESS *
692 reply_resent(struct pine *pine_state, long int msgno, char *section)
694 #define RESENTFROM 0
695 #define RESENTTO 1
696 #define RESENTCC 2
697 ADDRESS *rlist = NULL, **a, **b;
698 char *hdrs, *values[RESENTCC+1];
699 int i;
700 static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL};
701 static char *fakedomain = "@";
703 if((hdrs = pine_fetchheader_lines(pine_state->mail_stream,
704 msgno, section, fields)) != NULL){
705 memset(values, 0, (RESENTCC+1) * sizeof(char *));
706 simple_header_parse(hdrs, fields, values);
707 for(i = RESENTFROM; i <= RESENTCC; i++)
708 rfc822_parse_adrlist(&rlist, values[i],
709 (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state))
710 ? fakedomain : pine_state->maildomain);
712 /* pare dup's ... */
713 for(a = &rlist; *a; a = &(*a)->next) /* compare every address */
714 for(b = &(*a)->next; *b; ) /* to the others */
715 if(address_is_same(*a, *b)){
716 ADDRESS *t = *b;
718 if(!(*a)->personal){ /* preserve personal name */
719 (*a)->personal = (*b)->personal;
720 (*b)->personal = NULL;
723 *b = t->next;
724 t->next = NULL;
725 mail_free_address(&t);
727 else
728 b = &(*b)->next;
731 if(hdrs)
732 fs_give((void **) &hdrs);
734 return(rlist);
738 /*----------------------------------------------------------------------
739 Format and return subject suitable for the reply command
741 Args: subject -- subject to build reply subject for
742 buf -- buffer to use for writing. If non supplied, alloc one.
743 buflen -- length of buf if supplied, else ignored
745 Returns: with either "Re:" prepended or not, if already there.
746 returned string is allocated.
747 ---*/
748 char *
749 reply_subject(char *subject, char *buf, size_t buflen)
751 size_t l = (subject && *subject) ? 4*strlen(subject) : 10;
752 char *tmp = fs_get(l + 1), *decoded, *p;
754 if(!buf){
755 buflen = l + 5;
756 buf = fs_get(buflen);
759 /* decode any 8bit into tmp buffer */
760 decoded = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp, l+1, subject);
762 buf[0] = '\0';
763 if(decoded /* already "re:" ? */
764 && (decoded[0] == 'R' || decoded[0] == 'r')
765 && (decoded[1] == 'E' || decoded[1] == 'e')){
767 if(decoded[2] == ':')
768 snprintf(buf, buflen, "%.*s", (int)(buflen-1), subject);
769 else if((decoded[2] == '[') && (p = strchr(decoded, ']'))){
770 p++;
771 while(*p && isspace((unsigned char)*p)) p++;
772 if(p[0] == ':')
773 snprintf(buf, buflen, "%.*s", (int)(buflen-1), subject);
777 if(!buf[0])
778 snprintf(buf, buflen, "Re: %.*s", (int)(buflen-1),
779 (subject && *subject) ? subject : "your mail");
781 buf[buflen-1] = '\0';
783 fs_give((void **) &tmp);
784 return(buf);
788 /*----------------------------------------------------------------------
789 return initials for the given personal name
791 Args: name -- Personal name to extract initials from
793 Returns: pointer to name overwritten with initials
794 ---*/
795 char *
796 reply_quote_initials(char *name)
798 char *s = name,
799 *w = name;
800 int i, j;
801 CBUF_S cbuf;
802 UCS ucs;
804 cbuf.cbuf[i = 0] = '\0';
805 cbuf.cbufp = cbuf.cbuf;
806 cbuf.cbufend = cbuf.cbuf;
808 /* while there are still characters to look at */
809 while(s && *s){
810 /* skip to next initial */
811 while(*s && (unsigned char) *s == ' ')
812 s++;
814 if(!utf8_to_ucs4_oneatatime((unsigned char) *s++ & 0xff, &cbuf, &ucs, NULL)){
815 i++;
816 continue;
819 /* skip over non-alpha stuff */
820 if(i == 0 && !isalnum((unsigned char) cbuf.cbuf[0]))
821 goto reset_cbuf;
823 /* copy cbuf */
824 for(j = 0; j <= i; j++) *w++ = cbuf.cbuf[j];
826 /* skip to end of this piece of name */
827 while(*s && (unsigned char) *s != ' ')
828 s++;
830 reset_cbuf:
831 cbuf.cbuf[i = 0] = '\0';
832 cbuf.cbufp = cbuf.cbuf;
833 cbuf.cbufend = cbuf.cbuf;
836 if(w)
837 *w = '\0';
839 return(name);
843 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
844 * tokens being substituted for (only in the size of buf below).
846 #define MAX_SUBSTITUTION 6
847 #define MAX_PREFIX 63
848 static char *from_token = "_FROM_";
849 static char *nick_token = "_NICK_";
850 static char *init_token = "_INIT_";
852 /*----------------------------------------------------------------------
853 return a quoting string, "> " by default, for replied text
855 Args: env -- envelope of message being replied to
857 Returns: malloc'd array containing quoting string, freed by caller
858 ---*/
859 char *
860 reply_quote_str(ENVELOPE *env)
862 char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1];
864 strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1);
865 buf[sizeof(buf)-1] = '\0';
867 /* set up the prefix to quote included text */
868 if((p = strstr(buf, from_token)) != NULL){
869 repl = (env && env->from && env->from->mailbox) ? env->from->mailbox
870 : "";
871 strncpy(pbf, repl, sizeof(pbf)-1);
872 pbf[sizeof(pbf)-1] = '\0';;
873 rplstr(p, sizeof(buf)-(p-buf), strlen(from_token), pbf);
876 if((p = strstr(buf, nick_token)) != NULL){
877 repl = (env &&
878 env->from &&
879 env->from &&
880 get_nickname_from_addr(env->from, tmp_20k_buf, 1000))
881 ? tmp_20k_buf : "";
882 strncpy(pbf, repl, sizeof(pbf)-1);
883 pbf[sizeof(pbf)-1] = '\0';;
884 rplstr(p, sizeof(buf)-(p-buf), strlen(nick_token), pbf);
887 if((p = strstr(buf, init_token)) != NULL){
888 char *q = NULL;
889 char buftmp[MAILTMPLEN];
891 snprintf(buftmp, sizeof(buftmp), "%.200s",
892 (env && env->from && env->from->personal) ? env->from->personal : "");
893 buftmp[sizeof(buftmp)-1] = '\0';
895 repl = (env && env->from && env->from->personal)
896 ? reply_quote_initials(q = cpystr((char *)rfc1522_decode_to_utf8(
897 (unsigned char *)tmp_20k_buf,
898 SIZEOF_20KBUF, buftmp)))
899 : "";
901 istrncpy(pbf, repl, sizeof(pbf)-1);
902 pbf[sizeof(pbf)-1] = '\0';;
903 rplstr(p, sizeof(buf)-(p-buf), strlen(init_token), pbf);
904 if(q)
905 fs_give((void **)&q);
908 prefix = removing_quotes(cpystr(buf));
910 return(prefix);
914 reply_quote_str_contains_tokens(void)
916 return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] &&
917 (strstr(ps_global->VAR_REPLY_STRING, from_token) ||
918 strstr(ps_global->VAR_REPLY_STRING, nick_token) ||
919 strstr(ps_global->VAR_REPLY_STRING, init_token)));
923 /*----------------------------------------------------------------------
924 Build the body for the message number/part being replied to
926 Args:
928 Result: BODY structure suitable for sending
930 ----------------------------------------------------------------------*/
931 BODY *
932 reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
933 long int msgno, char *sect_prefix, void *msgtext, char *prefix,
934 int plustext, ACTION_S *role, int toplevel, REDRAFT_POS_S **redraft_pos)
936 #define SECTBUFLEN 256
937 char *p, *sig = NULL, *section, sect_buf[SECTBUFLEN];
938 BODY *body = NULL, *tmp_body = NULL;
939 PART *part;
940 gf_io_t pc;
941 int impl, template_len = 0, leave_cursor_at_top = 0, reply_raw_body = 0;
943 if(sect_prefix) /* SECTBUFLEN = sizeof(sect_buf) */
944 snprintf(section = sect_buf, sizeof(sect_buf), "%.*s.1", SECTBUFLEN-3, sect_prefix);
945 else
946 section = "1";
948 sect_buf[sizeof(sect_buf)-1] = '\0';
950 if(ps_global->full_header == 2
951 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
952 reply_raw_body = 1;
954 gf_set_so_writec(&pc, (STORE_S *) msgtext);
956 if(toplevel){
957 char *filtered;
959 impl = 0;
960 filtered = detoken(role, env, 0,
961 ps_global->reply.signature_bottom,
962 0, redraft_pos, &impl);
963 if(filtered){
964 if(*filtered){
965 so_puts((STORE_S *)msgtext, filtered);
966 if(impl == 1)
967 template_len = strlen(filtered);
968 else if(impl == 2)
969 leave_cursor_at_top++;
972 fs_give((void **)&filtered);
974 else
975 impl = 1;
977 else
978 impl = 1;
980 if(toplevel &&
981 (sig = reply_signature(role, env, redraft_pos, &impl)) &&
982 !ps_global->reply.signature_bottom){
985 * If CURSORPOS was set explicitly in sig_file, and there was a
986 * template file before that, we need to adjust the offset by the
987 * length of the template file. However, if the template had
988 * a set CURSORPOS in it then impl was 2 before getting to the
989 * signature, so offset wouldn't have been reset by the signature
990 * CURSORPOS and offset would already be correct. That case will
991 * be ok here because template_len will be 0 and adding it does
992 * nothing. If template
993 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
994 * by the CURSORPOS in the sig. In that case we have to adjust the
995 * offset. That's what the next line does. It adjusts it if
996 * template_len is nonzero and if CURSORPOS was set in sig_file.
998 if(impl == 2)
999 (*redraft_pos)->offset += template_len;
1001 if(*sig)
1002 so_puts((STORE_S *)msgtext, sig);
1005 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
1006 * is set, we won't have used it yet and want it to be non-NULL.
1008 fs_give((void **)&sig);
1012 * Only put cursor in sig if there is a cursorpos there but not
1013 * one in the template, and sig-at-bottom.
1015 if(!(sig && impl == 2 && !leave_cursor_at_top))
1016 leave_cursor_at_top++;
1018 if(plustext){
1019 if(!orig_body
1020 || orig_body->type == TYPETEXT
1021 || reply_raw_body
1022 || !ps_global->reply.keep_attach){
1023 char *charset = NULL;
1025 /*------ Simple text-only message ----*/
1026 body = mail_newbody();
1027 body->type = TYPETEXT;
1028 body->contents.text.data = msgtext;
1029 reply_delimiter(env, role, pc);
1030 if(ps_global->reply.include_header)
1031 reply_forward_header(stream, msgno, sect_prefix,
1032 env, pc, prefix);
1034 if(!orig_body || reply_raw_body || reply_body_text(orig_body, &tmp_body)){
1035 BODY *bodyp = NULL;
1037 bodyp = reply_raw_body ? NULL : tmp_body;
1040 * We set the charset in the outgoing message to the same
1041 * as the one in the message we're replying to unless it
1042 * is the unknown charset. We do that in order to attempt
1043 * to preserve the same charset in the reply if possible.
1044 * It may be safer to just set it to whatever we want instead
1045 * but then the receiver may not be able to read it.
1047 if(bodyp
1048 && (charset = parameter_val(bodyp->parameter, "charset"))
1049 && strucmp(charset, UNKNOWN_CHARSET))
1050 set_parameter(&body->parameter, "charset", charset);
1052 if(charset)
1053 fs_give((void **) &charset);
1055 get_body_part_text(stream, bodyp, msgno,
1056 bodyp ? (p = body_partno(stream, msgno, bodyp))
1057 : sect_prefix,
1058 0L, pc, prefix, NULL, GBPT_NONE);
1059 if(bodyp && p)
1060 fs_give((void **) &p);
1062 else{
1063 gf_puts(NEWLINE, pc);
1064 gf_puts(" [NON-Text Body part not included]", pc);
1065 gf_puts(NEWLINE, pc);
1068 else if(orig_body->type == TYPEMULTIPART){
1069 /*------ Message is Multipart ------*/
1070 if(orig_body->subtype
1071 && (!strucmp(orig_body->subtype, "signed")
1072 #ifdef SMIME
1073 || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
1074 #endif /* SMIME */
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, "mixed")
1085 && orig_body->nested.part
1086 && orig_body->nested.part->body.type == TYPEMULTIPART
1087 && orig_body->nested.part->body.subtype
1088 && (!strucmp(orig_body->nested.part->body.subtype, "signed")
1089 #ifdef SMIME
1090 || !strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
1091 #endif /* SMIME */
1093 /* we can call reply_body as in the call above with section
1094 * equal to "1", but that adds the multipart text to the
1095 * list of attachments. We do not want that, so we redo this
1096 * manually.
1098 body = copy_body(NULL, orig_body);
1101 * whatever subtype it is, demote it
1102 * to plain old MIXED.
1104 if(body->subtype)
1105 fs_give((void **) &body->subtype);
1107 body->subtype = cpystr("Mixed");
1109 if(reply_body_text(&orig_body->nested.part->body.nested.part->body,
1110 &tmp_body)){
1111 int partnum;
1113 reply_delimiter(env, role, pc);
1114 if(ps_global->reply.include_header)
1115 reply_forward_header(stream, msgno, sect_prefix,
1116 env, pc, prefix);
1117 /* SECTBUFLEN = sizeof(sect_buf) */
1118 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s",
1119 SECTBUFLEN/2-2,
1120 sect_prefix ? sect_prefix : "",
1121 sect_prefix ? "." : "",
1122 SECTBUFLEN/2-2,
1123 p = partno(orig_body, tmp_body));
1124 sect_buf[sizeof(sect_buf)-1] = '\0';
1125 fs_give((void **) &p);
1126 get_body_part_text(stream, tmp_body, msgno,
1127 sect_buf, 0L, pc, prefix,
1128 NULL, GBPT_NONE);
1130 part = body->nested.part->next;
1131 body->nested.part->next = NULL;
1132 mail_free_body_part(&body->nested.part);
1133 body->nested.part = mail_newbody_part();
1134 body->nested.part->body.type = TYPETEXT;
1135 body->nested.part->body.subtype = cpystr("Plain");
1136 body->nested.part->body.contents.text.data = msgtext;
1137 body->nested.part->next = part;
1138 /* SECTBUFLEN = sizeof(sect_buf) */
1139 for(partnum = 2; part != NULL; part = part->next){
1140 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d",
1141 SECTBUFLEN/2,
1142 sect_prefix ? sect_prefix : "",
1143 sect_prefix ? "." : "", partnum++);
1144 sect_buf[sizeof(sect_buf)-1] = '\0';
1146 if(!fetch_contents(stream, msgno,
1147 sect_buf, &part->body)){
1148 break;
1152 else {
1153 /*--- Fetch the original pieces ---*/
1154 if(!fetch_contents(stream, msgno, sect_prefix, body))
1155 q_status_message(SM_ORDER | SM_DING, 3, 4,
1156 _("Error including all message parts"));
1158 /*--- No text part, create a blank one ---*/
1159 part = mail_newbody_part();
1160 part->next = body->nested.part;
1161 body->nested.part = part;
1162 part->body.contents.text.data = msgtext;
1165 else if(orig_body->subtype
1166 && !strucmp(orig_body->subtype, "alternative")
1167 && orig_body->nested.part
1168 && orig_body->nested.part->next
1169 && orig_body->nested.part->next->body.type == TYPEMULTIPART
1170 && orig_body->nested.part->next->body.subtype
1171 && !strucmp(orig_body->nested.part->next->body.subtype, "MIXED"))
1172 body = reply_body(stream, env,
1173 &orig_body->nested.part->next->body,
1174 msgno, "2", msgtext, prefix,
1175 plustext, role, 0, redraft_pos);
1176 else if(orig_body->subtype
1177 && !strucmp(orig_body->subtype, "alternative")){
1178 /* Set up the simple text reply */
1179 body = mail_newbody();
1180 body->type = TYPETEXT;
1181 body->contents.text.data = msgtext;
1183 if(reply_body_text(orig_body, &tmp_body)){
1184 reply_delimiter(env, role, pc);
1185 if(ps_global->reply.include_header)
1186 reply_forward_header(stream, msgno, sect_prefix,
1187 env, pc, prefix);
1189 get_body_part_text(stream, tmp_body, msgno,
1190 p = body_partno(stream,msgno,tmp_body),
1191 0L, pc, prefix, NULL, GBPT_NONE);
1192 if(p)
1193 fs_give((void **) &p);
1195 else
1196 q_status_message(SM_ORDER | SM_DING, 3, 3,
1197 "No suitable multipart text found for inclusion!");
1199 else{
1200 body = copy_body(NULL, orig_body);
1203 * whatever subtype it is, demote it
1204 * to plain old MIXED.
1206 if(body->subtype)
1207 fs_give((void **) &body->subtype);
1209 body->subtype = cpystr("Mixed");
1211 if(body->nested.part &&
1212 body->nested.part->body.type == TYPETEXT) {
1213 char *new_charset = NULL;
1215 /*---- First part of the message is text -----*/
1216 body->nested.part->body.contents.text.data = msgtext;
1217 if(body->nested.part->body.subtype &&
1218 strucmp(body->nested.part->body.subtype, "Plain")){
1219 fs_give((void **)&body->nested.part->body.subtype);
1220 body->nested.part->body.subtype = cpystr("Plain");
1222 reply_delimiter(env, role, pc);
1223 if(ps_global->reply.include_header)
1224 reply_forward_header(stream, msgno, sect_prefix,
1225 env, pc, prefix);
1227 if(!(get_body_part_text(stream,
1228 &orig_body->nested.part->body,
1229 msgno, section, 0L, pc, prefix,
1230 &new_charset, GBPT_NONE)
1231 && fetch_contents(stream, msgno, sect_prefix, body)))
1232 q_status_message(SM_ORDER | SM_DING, 3, 4,
1233 _("Error including all message parts"));
1234 else if(new_charset)
1235 set_parameter(&body->nested.part->body.parameter, "charset", new_charset);
1237 else if(orig_body->nested.part->body.type == TYPEMULTIPART
1238 && orig_body->nested.part->body.subtype
1239 && !strucmp(orig_body->nested.part->body.subtype,
1240 "alternative")
1241 && reply_body_text(&orig_body->nested.part->body,
1242 &tmp_body)){
1243 int partnum;
1245 reply_delimiter(env, role, pc);
1246 if(ps_global->reply.include_header)
1247 reply_forward_header(stream, msgno, sect_prefix,
1248 env, pc, prefix);
1249 /* SECTBUFLEN = sizeof(sect_buf) */
1250 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s",
1251 SECTBUFLEN/2-2,
1252 sect_prefix ? sect_prefix : "",
1253 sect_prefix ? "." : "",
1254 SECTBUFLEN/2-2,
1255 p = partno(orig_body, tmp_body));
1256 sect_buf[sizeof(sect_buf)-1] = '\0';
1257 fs_give((void **) &p);
1258 get_body_part_text(stream, tmp_body, msgno,
1259 sect_buf, 0L, pc, prefix,
1260 NULL, GBPT_NONE);
1262 part = body->nested.part->next;
1263 body->nested.part->next = NULL;
1264 mail_free_body_part(&body->nested.part);
1265 body->nested.part = mail_newbody_part();
1266 body->nested.part->body.type = TYPETEXT;
1267 body->nested.part->body.subtype = cpystr("Plain");
1268 body->nested.part->body.contents.text.data = msgtext;
1269 body->nested.part->next = part;
1270 /* SECTBUFLEN = sizeof(sect_buf) */
1271 for(partnum = 2; part != NULL; part = part->next){
1272 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d",
1273 SECTBUFLEN/2,
1274 sect_prefix ? sect_prefix : "",
1275 sect_prefix ? "." : "", partnum++);
1276 sect_buf[sizeof(sect_buf)-1] = '\0';
1278 if(!fetch_contents(stream, msgno,
1279 sect_buf, &part->body)){
1280 break;
1284 else {
1285 /*--- Fetch the original pieces ---*/
1286 if(!fetch_contents(stream, msgno, sect_prefix, body))
1287 q_status_message(SM_ORDER | SM_DING, 3, 4,
1288 _("Error including all message parts"));
1290 /*--- No text part, create a blank one ---*/
1291 part = mail_newbody_part();
1292 part->next = body->nested.part;
1293 body->nested.part = part;
1294 part->body.contents.text.data = msgtext;
1298 else{
1299 /*---- Single non-text message of some sort ----*/
1300 body = mail_newbody();
1301 body->type = TYPEMULTIPART;
1302 part = mail_newbody_part();
1303 body->nested.part = part;
1305 /*--- The first part, a blank text part to be edited ---*/
1306 part->body.type = TYPETEXT;
1307 part->body.contents.text.data = msgtext;
1309 /*--- The second part, what ever it is ---*/
1310 part->next = mail_newbody_part();
1311 part = part->next;
1312 part->body.id = generate_message_id(NULL);
1313 copy_body(&(part->body), orig_body);
1316 * the idea here is to fetch part into storage object
1318 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE,
1319 NULL,EDIT_ACCESS)) != NULL){
1320 if((p = pine_mail_fetch_body(stream, msgno, section,
1321 &part->body.size.bytes, NIL)) != NULL){
1322 so_nputs((STORE_S *)part->body.contents.text.data,
1323 p, part->body.size.bytes);
1325 else
1326 mail_free_body(&body);
1328 else
1329 mail_free_body(&body);
1332 else{
1333 /*--------- No text included --------*/
1334 body = mail_newbody();
1335 body->type = TYPETEXT;
1336 body->contents.text.data = msgtext;
1339 if(!leave_cursor_at_top){
1340 long cnt = 0L;
1341 unsigned char c;
1343 /* rewind and count chars to start of sig file */
1344 so_seek((STORE_S *)msgtext, 0L, 0);
1345 while(so_readc(&c, (STORE_S *)msgtext))
1346 cnt++;
1348 if(!*redraft_pos){
1349 *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
1350 memset((void *)*redraft_pos, 0,sizeof(**redraft_pos));
1351 (*redraft_pos)->hdrname = cpystr(":");
1355 * If explicit cursor positioning in sig file,
1356 * add offset to start of sig file plus offset into sig file.
1357 * Else, just offset to start of sig file.
1359 (*redraft_pos)->offset += cnt;
1362 if(sig){
1363 if(*sig)
1364 so_puts((STORE_S *)msgtext, sig);
1366 fs_give((void **)&sig);
1369 gf_clear_so_writec((STORE_S *) msgtext);
1371 return(body);
1376 * reply_part - first replyable multipart of a multipart.
1379 reply_body_text(struct mail_bodystruct *body, struct mail_bodystruct **new_body)
1381 if(body){
1382 switch(body->type){
1383 case TYPETEXT :
1384 *new_body = body;
1385 return(1);
1387 case TYPEMULTIPART :
1388 if(body->subtype && !strucmp(body->subtype, "alternative")){
1389 PART *part;
1390 int got_one = 0;
1392 if((part = body->nested.part) != NULL
1393 && part->body.type == TYPEMULTIPART)
1394 return reply_body_text(&body->nested.part->body, new_body);
1396 if(ps_global->force_prefer_plain
1397 || (!ps_global->force_no_prefer_plain
1398 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
1399 for(part = body->nested.part; part; part = part->next)
1400 if((!part->body.type || part->body.type == TYPETEXT)
1401 && (!part->body.subtype
1402 || !strucmp(part->body.subtype, "plain"))){
1403 *new_body = &part->body;
1404 return(1);
1409 * Else choose last alternative among plain or html parts.
1410 * Perhaps we should really be using mime_show() to make this
1411 * potentially more general than just plain or html.
1413 for(part = body->nested.part; part; part = part->next){
1414 if((!part->body.type || part->body.type == TYPETEXT)
1415 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
1417 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
1418 got_one++;
1419 *new_body = &part->body;
1423 if(got_one)
1424 return(1);
1426 else if(body->nested.part)
1427 /* NOTE: we're only interested in "first" part of mixed */
1428 return(reply_body_text(&body->nested.part->body, new_body));
1430 break;
1432 default:
1433 break;
1437 return(0);
1441 char *
1442 reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl)
1444 char *sig;
1445 size_t l;
1447 sig = detoken(role, env,
1448 2, ps_global->reply.signature_bottom ? 0 : 1, 1,
1449 redraft_pos, impl);
1451 if(!ps_global->reply.signature_bottom && (!sig || !*sig)){
1452 if(sig)
1453 fs_give((void **)&sig);
1455 l = 2 * strlen(NEWLINE);
1456 sig = (char *)fs_get((l+1) * sizeof(char));
1457 strncpy(sig, NEWLINE, l);
1458 sig[l] = '\0';
1459 strncat(sig, NEWLINE, l+1-1-strlen(sig));
1460 sig[l] = '\0';
1461 return(sig);
1464 return(sig);
1469 * Buf is at least size maxlen+1
1471 void
1472 get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1474 ADDRESS *addr = NULL;
1475 ADDRESS *last_to = NULL;
1476 ADDRESS *first_addr = NULL, *second_addr = NULL;
1477 ADDRESS *third_addr = NULL, *fourth_addr = NULL;
1478 int cntaddr, l;
1479 size_t orig_maxlen;
1480 char *p;
1482 buf[0] = '\0';
1484 switch(type){
1485 case iFrom:
1486 addr = env ? env->from : NULL;
1487 break;
1489 case iTo:
1490 addr = env ? env->to : NULL;
1491 break;
1493 case iCc:
1494 addr = env ? env->cc : NULL;
1495 break;
1497 case iSender:
1498 addr = env ? env->sender : NULL;
1499 break;
1502 * Recips is To and Cc together. We hook the two adrlists together
1503 * temporarily.
1505 case iRecips:
1506 addr = env ? env->to : NULL;
1507 /* Find end of To list */
1508 for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
1511 /* Make the end of To list point to cc list */
1512 if(last_to)
1513 last_to->next = (env ? env->cc : NULL);
1515 break;
1518 * Initials.
1520 case iInit:
1521 if(env && env->from && env->from->personal){
1522 char *name, *initials = NULL;
1524 name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1525 SIZEOF_20KBUF, env->from->personal);
1526 if(name == env->from->personal){
1527 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
1528 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
1529 name = tmp_20k_buf;
1532 if(name && *name){
1533 initials = reply_quote_initials(name);
1534 iutf8ncpy(buf, initials, maxlen);
1535 buf[maxlen] = '\0';
1539 return;
1541 default:
1542 break;
1545 orig_maxlen = maxlen;
1547 first_addr = addr;
1548 /* skip over rest of c-client group addr */
1549 if(first_addr && first_addr->mailbox && !first_addr->host){
1550 for(second_addr = first_addr->next;
1551 second_addr && second_addr->host;
1552 second_addr = second_addr->next)
1555 if(second_addr && !second_addr->host)
1556 second_addr = second_addr->next;
1558 else if(!(first_addr && first_addr->host && first_addr->host[0] == '.'))
1559 second_addr = first_addr ? first_addr->next : NULL;
1561 if(second_addr && second_addr->mailbox && !second_addr->host){
1562 for(third_addr = second_addr->next;
1563 third_addr && third_addr->host;
1564 third_addr = third_addr->next)
1567 if(third_addr && !third_addr->host)
1568 third_addr = third_addr->next;
1570 else if(!(second_addr && second_addr->host && second_addr->host[0] == '.'))
1571 third_addr = second_addr ? second_addr->next : NULL;
1573 if(third_addr && third_addr->mailbox && !third_addr->host){
1574 for(fourth_addr = third_addr->next;
1575 fourth_addr && fourth_addr->host;
1576 fourth_addr = fourth_addr->next)
1579 if(fourth_addr && !fourth_addr->host)
1580 fourth_addr = fourth_addr->next;
1582 else if(!(third_addr && third_addr->host && third_addr->host[0] == '.'))
1583 fourth_addr = third_addr ? third_addr->next : NULL;
1585 /* Just attempting to make a nice display */
1586 if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
1587 (first_addr->mailbox && first_addr->mailbox[0]))){
1588 if(second_addr){
1589 if((second_addr->personal && second_addr->personal[0]) ||
1590 (second_addr->mailbox && second_addr->mailbox[0])){
1591 if(third_addr){
1592 if((third_addr->personal && third_addr->personal[0]) ||
1593 (third_addr->mailbox && third_addr->mailbox[0])){
1594 if(fourth_addr)
1595 cntaddr = 4;
1596 else
1597 cntaddr = 3;
1599 else
1600 cntaddr = -1;
1602 else
1603 cntaddr = 2;
1605 else
1606 cntaddr = -1;
1608 else
1609 cntaddr = 1;
1611 else
1612 cntaddr = -1;
1614 p = buf;
1615 if(cntaddr == 1)
1616 a_little_addr_string(first_addr, p, maxlen);
1617 else if(cntaddr == 2){
1618 a_little_addr_string(first_addr, p, maxlen);
1619 maxlen -= (l=strlen(p));
1620 p += l;
1621 if(maxlen > 7){
1622 strncpy(p, " and ", maxlen);
1623 maxlen -= 5;
1624 p += 5;
1625 a_little_addr_string(second_addr, p, maxlen);
1628 else if(cntaddr == 3){
1629 a_little_addr_string(first_addr, p, maxlen);
1630 maxlen -= (l=strlen(p));
1631 p += l;
1632 if(maxlen > 7){
1633 strncpy(p, ", ", maxlen);
1634 maxlen -= 2;
1635 p += 2;
1636 a_little_addr_string(second_addr, p, maxlen);
1637 maxlen -= (l=strlen(p));
1638 p += l;
1639 if(maxlen > 7){
1640 strncpy(p, ", and ", maxlen);
1641 maxlen -= 6;
1642 p += 6;
1643 a_little_addr_string(third_addr, p, maxlen);
1647 else if(cntaddr > 3){
1648 a_little_addr_string(first_addr, p, maxlen);
1649 maxlen -= (l=strlen(p));
1650 p += l;
1651 if(maxlen > 7){
1652 strncpy(p, ", ", maxlen);
1653 maxlen -= 2;
1654 p += 2;
1655 a_little_addr_string(second_addr, p, maxlen);
1656 maxlen -= (l=strlen(p));
1657 p += l;
1658 if(maxlen > 7){
1659 strncpy(p, ", ", maxlen);
1660 maxlen -= 2;
1661 p += 2;
1662 a_little_addr_string(third_addr, p, maxlen);
1663 maxlen -= (l=strlen(p));
1664 p += l;
1665 if(maxlen >= 12)
1666 strncpy(p, ", and others", maxlen);
1667 else if(maxlen >= 3)
1668 strncpy(p, "...", maxlen);
1672 else if(addr){
1673 char *a_string;
1675 a_string = addr_list_string(addr, NULL, 0);
1676 iutf8ncpy(buf, a_string, maxlen);
1678 fs_give((void **)&a_string);
1681 if(last_to)
1682 last_to->next = NULL;
1684 buf[orig_maxlen] = '\0';
1689 * Buf is at least size maxlen+1
1691 void
1692 get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1694 int cntnews = 0, orig_maxlen;
1695 char *news = NULL, *p, *q;
1697 switch(type){
1698 case iNews:
1699 case iNewsAndTo:
1700 case iToAndNews:
1701 case iNewsAndRecips:
1702 case iRecipsAndNews:
1703 news = env ? env->newsgroups : NULL;
1704 break;
1706 case iCurNews:
1707 if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream))
1708 news = ps_global->cur_folder;
1710 break;
1712 default:
1713 break;
1716 orig_maxlen = maxlen;
1718 if(news){
1719 q = news;
1720 while(isspace((unsigned char)*q))
1721 q++;
1723 if(*q)
1724 cntnews++;
1726 while((q = strindex(q, ',')) != NULL){
1727 q++;
1728 while(isspace((unsigned char)*q))
1729 q++;
1731 if(*q)
1732 cntnews++;
1733 else
1734 break;
1738 if(cntnews == 1){
1739 istrncpy(buf, news, maxlen);
1740 buf[maxlen] = '\0';
1741 removing_leading_and_trailing_white_space(buf);
1743 else if(cntnews == 2){
1744 p = buf;
1745 q = news;
1746 while(isspace((unsigned char)*q))
1747 q++;
1749 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1750 *p++ = *q++;
1751 maxlen--;
1754 if(maxlen > 7){
1755 strncpy(p, " and ", maxlen);
1756 p += 5;
1757 maxlen -= 5;
1760 while(isspace((unsigned char)*q) || *q == ',')
1761 q++;
1763 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1764 *p++ = *q++;
1765 maxlen--;
1768 *p = '\0';
1770 istrncpy(tmp_20k_buf, buf, 10000);
1771 strncpy(buf, tmp_20k_buf, orig_maxlen);
1773 else if(cntnews > 2){
1774 char b[100];
1776 p = buf;
1777 q = news;
1778 while(isspace((unsigned char)*q))
1779 q++;
1781 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1782 *p++ = *q++;
1783 maxlen--;
1786 *p = '\0';
1787 snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1);
1788 b[sizeof(b)-1] = '\0';
1789 if(maxlen >= strlen(b))
1790 strncpy(p, b, maxlen);
1791 else if(maxlen >= 3)
1792 strncpy(p, "...", maxlen);
1794 buf[orig_maxlen] = '\0';
1796 istrncpy(tmp_20k_buf, buf, 10000);
1797 tmp_20k_buf[10000-1] = '\0';
1798 strncpy(buf, tmp_20k_buf, orig_maxlen);
1801 buf[orig_maxlen] = '\0';
1804 void
1805 shorten_subject(char *origsubj)
1807 char *s, *t, *u;
1808 int endlist = 0;
1810 if(origsubj == NULL || *origsubj == '\0')
1811 return;
1813 for(t=s=origsubj; *s ; s++){
1814 switch(*s){
1815 /* this transforms "A [B [C] D" into "A D" should this be
1816 * "A [B D"?
1818 case '[' : if((u = strchr(s+1,']')) != NULL){
1819 s = u;
1820 endlist = 1;
1822 else
1823 *t++ = *s;
1824 break;
1825 case ' ' : if(endlist == 0) *t++ = *s; break;
1826 default : endlist = 0; *t++ = *s; break;
1829 *t = '\0';
1833 * Buf is at least size maxlen+1
1835 char *
1836 get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen)
1838 char *space = NULL;
1839 IndexColType addrtype;
1841 buf[0] = '\0';
1843 switch(type){
1844 case iRDate: case iSDate: case iSTime: case iSTime24:
1845 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
1846 case iSDateIso: case iSDateIsoS:
1847 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1848 case iSDateTime:
1849 case iSDateTimeIso: case iSDateTimeIsoS:
1850 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1851 case iSDateTime24:
1852 case iSDateTimeIso24: case iSDateTimeIsoS24:
1853 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1854 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
1855 case iDay: case iDayOrdinal: case iDay2Digit:
1856 case iMonAbb: case iMonLong: case iMon: case iMon2Digit:
1857 case iYear: case iYear2Digit:
1858 case iDate: case iLDate:
1859 case iTimezone: case iDayOfWeekAbb: case iDayOfWeek:
1860 case iPrefDate: case iPrefTime: case iPrefDateTime:
1861 if(env && env->date && env->date[0] && maxlen >= 20)
1862 date_str((char *) env->date, type, 1, buf, maxlen+1, 0);
1864 break;
1866 case iCurDate:
1867 case iCurDateIso:
1868 case iCurDateIsoS:
1869 case iCurTime24:
1870 case iCurTime12:
1871 case iCurDay:
1872 case iCurDay2Digit:
1873 case iCurDayOfWeek:
1874 case iCurDayOfWeekAbb:
1875 case iCurMon:
1876 case iCurMon2Digit:
1877 case iCurMonLong:
1878 case iCurMonAbb:
1879 case iCurYear:
1880 case iCurYear2Digit:
1881 case iCurPrefDate:
1882 case iCurPrefDateTime:
1883 case iCurPrefTime:
1884 case iLstMon:
1885 case iLstMon2Digit:
1886 case iLstMonLong:
1887 case iLstMonAbb:
1888 case iLstMonYear:
1889 case iLstMonYear2Digit:
1890 case iLstYear:
1891 case iLstYear2Digit:
1892 if(maxlen >= 20)
1893 date_str(NULL, type, 1, buf, maxlen+1, 0);
1895 break;
1897 case iFrom:
1898 case iTo:
1899 case iCc:
1900 case iSender:
1901 case iRecips:
1902 case iInit:
1903 get_addr_data(env, type, buf, maxlen);
1904 break;
1906 case iRoleNick:
1907 if(role && role->nick){
1908 strncpy(buf, role->nick, maxlen);
1909 buf[maxlen] = '\0';
1911 break;
1913 case iNewLine:
1914 if(maxlen >= strlen(NEWLINE)){
1915 strncpy(buf, NEWLINE, maxlen);
1916 buf[maxlen] = '\0';
1918 break;
1920 case iAddress:
1921 case iMailbox:
1922 if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
1923 strlen(env->from->mailbox) <= maxlen){
1924 strncpy(buf, env->from->mailbox, maxlen);
1925 buf[maxlen] = '\0';
1926 if(type == iAddress &&
1927 env->from->host &&
1928 env->from->host[0] &&
1929 env->from->host[0] != '.' &&
1930 strlen(buf) + strlen(env->from->host) + 1 <= maxlen){
1931 strncat(buf, "@", maxlen+1-1-strlen(buf));
1932 buf[maxlen] = '\0';
1933 strncat(buf, env->from->host, maxlen+1-1-strlen(buf));
1934 buf[maxlen] = '\0';
1938 break;
1940 case iNews:
1941 case iCurNews:
1942 get_news_data(env, type, buf, maxlen);
1943 break;
1945 case iToAndNews:
1946 case iNewsAndTo:
1947 case iRecipsAndNews:
1948 case iNewsAndRecips:
1949 if(type == iToAndNews || type == iNewsAndTo)
1950 addrtype = iTo;
1951 else
1952 addrtype = iRecips;
1954 if(env && env->newsgroups){
1955 space = (char *)fs_get((maxlen+1) * sizeof(char));
1956 get_news_data(env, type, space, maxlen);
1959 get_addr_data(env, addrtype, buf, maxlen);
1961 if(space && *space && *buf){
1962 if(strlen(space) + strlen(buf) + 5 > maxlen){
1963 if(strlen(space) > maxlen/2)
1964 get_news_data(env, type, space, maxlen - strlen(buf) - 5);
1965 else
1966 get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
1969 if(type == iToAndNews || type == iRecipsAndNews)
1970 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space);
1971 else
1972 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf);
1974 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1976 strncpy(buf, tmp_20k_buf, maxlen);
1977 buf[maxlen] = '\0';
1979 else if(space && *space){
1980 strncpy(buf, space, maxlen);
1981 buf[maxlen] = '\0';
1984 if(space)
1985 fs_give((void **)&space);
1987 break;
1989 case iSubject:
1990 case iShortSubject:
1991 if(env && env->subject){
1992 size_t n, len;
1993 unsigned char *p, *tmp = NULL;
1995 if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){
1996 len = n+1;
1997 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
1999 else{
2000 len = SIZEOF_20KBUF;
2001 p = (unsigned char *)tmp_20k_buf;
2004 istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen);
2006 buf[maxlen] = '\0';
2008 if(tmp)
2009 fs_give((void **)&tmp);
2011 if(type == iShortSubject)
2012 shorten_subject(buf);
2015 break;
2017 case iMsgID:
2018 if(env && env->message_id){
2019 strncpy(buf, env->message_id, maxlen);
2020 buf[maxlen] = '\0';
2023 break;
2025 default:
2026 break;
2029 buf[maxlen] = '\0';
2030 return(buf);
2035 * reply_delimiter - output formatted reply delimiter for given envelope
2036 * with supplied character writing function.
2038 void
2039 reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc)
2041 #define MAX_DELIM 2000
2042 char buf[MAX_DELIM+1];
2043 char *p;
2044 char *filtered = NULL;
2045 int contains_newline_token = 0;
2048 if(!env)
2049 return;
2051 strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
2052 buf[MAX_DELIM] = '\0';
2053 /* preserve exact default behavior from before */
2054 if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
2055 struct date d;
2056 int include_date;
2058 parse_date((char *) env->date, &d);
2059 include_date = !(d.day == -1 || d.month == -1 || d.year == -1);
2060 if(include_date){
2061 gf_puts("On ", pc); /* All delims have... */
2062 if(d.wkday != -1){ /* "On day, date month year" */
2063 gf_puts(day_abbrev(d.wkday), pc); /* in common */
2064 gf_puts(", ", pc);
2067 gf_puts(int2string(d.day), pc);
2068 (*pc)(' ');
2069 gf_puts(month_abbrev(d.month), pc);
2070 (*pc)(' ');
2071 gf_puts(int2string(d.year), pc);
2074 if(env->from
2075 && ((env->from->personal && env->from->personal[0])
2076 || (env->from->mailbox && env->from->mailbox[0]))){
2077 char buftmp[MAILTMPLEN];
2079 a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1);
2080 if(include_date)
2081 gf_puts(", ", pc);
2083 gf_puts(buftmp, pc);
2084 gf_puts(" wrote:", pc);
2086 else{
2087 if(include_date)
2088 gf_puts(", it was written", pc);
2089 else
2090 gf_puts("It was written", pc);
2094 else{
2096 * This is here for backwards compatibility. There didn't used
2097 * to be a _NEWLINE_ token. The user would enter text that should
2098 * all fit on one line and then that was followed by two newlines.
2099 * Also, truncation occurs if it is long.
2100 * Now, if _NEWLINE_ is not in the text, same thing still works
2101 * the same. However, if _NEWLINE_ is in there, then all bets are
2102 * off and the user is on his or her own. No automatic newlines
2103 * are added, only those that come from the tokens. No truncation
2104 * is done, the user is trusted to get it right. Newlines may be
2105 * embedded so that the leadin is multi-line.
2107 contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL);
2108 filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role,
2109 NULL, NULL);
2111 /* try to truncate if too long */
2112 if(!contains_newline_token && filtered && utf8_width(filtered) > 80){
2113 int ended_with_colon = 0;
2114 int ended_with_quote = 0;
2115 int ended_with_quote_colon = 0;
2116 int l;
2118 l = strlen(filtered);
2120 if(filtered[l-1] == ':'){
2121 ended_with_colon = ':';
2122 if(filtered[l-2] == QUOTE || filtered[l-2] == '\'')
2123 ended_with_quote_colon = filtered[l-2];
2125 else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'')
2126 ended_with_quote = filtered[l-1];
2128 /* try to find space to break at */
2129 for(p = &filtered[75]; p > &filtered[60] &&
2130 !isspace((unsigned char)*p); p--)
2133 if(!isspace((unsigned char)*p))
2134 p = &filtered[75];
2136 *p++ = '.';
2137 *p++ = '.';
2138 *p++ = '.';
2139 if(ended_with_quote_colon){
2140 *p++ = ended_with_quote_colon;
2141 *p++ = ':';
2143 else if(ended_with_colon)
2144 *p++ = ended_with_colon;
2145 else if(ended_with_quote)
2146 *p++ = ended_with_quote;
2148 *p = '\0';
2151 if(filtered && *filtered)
2152 gf_puts(filtered, pc);
2155 /* and end with two newlines unless no leadin at all */
2156 if(!contains_newline_token){
2157 if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
2158 gf_puts(NEWLINE, pc);
2159 gf_puts(NEWLINE, pc);
2163 if(filtered)
2164 fs_give((void **)&filtered);
2168 void
2169 free_redraft_pos(REDRAFT_POS_S **redraft_pos)
2171 if(redraft_pos && *redraft_pos){
2172 if((*redraft_pos)->hdrname)
2173 fs_give((void **)&(*redraft_pos)->hdrname);
2175 fs_give((void **)redraft_pos);
2180 /*----------------------------------------------------------------------
2181 Build the body for the message number/part being forwarded as ATTACHMENT
2183 Args:
2185 Result: PARTS suitably attached to body
2187 ----------------------------------------------------------------------*/
2189 forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext)
2191 char *tmp_text;
2192 unsigned long len;
2193 BODY *b;
2195 *partp = mail_newbody_part();
2196 b = &(*partp)->body;
2197 b->type = TYPEMESSAGE;
2198 b->id = generate_message_id(NULL);
2199 b->description = cpystr("Forwarded Message");
2200 b->nested.msg = mail_newmsg();
2201 b->disposition.type = cpystr("inline");
2203 /*---- Package each message in a storage object ----*/
2204 if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
2205 && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
2206 && *tmp_text){
2207 so_puts((STORE_S *) b->contents.text.data, tmp_text);
2209 b->size.bytes = strlen(tmp_text);
2210 so_puts((STORE_S *) b->contents.text.data, "\015\012");
2211 if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){
2212 so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
2213 b->size.bytes += len;
2214 return(1);
2218 return(0);
2223 * forward_delimiter - return delimiter for forwarded text
2225 void
2226 forward_delimiter(gf_io_t pc)
2228 gf_puts(NEWLINE, pc);
2229 /* TRANSLATORS: When a message is forwarded by the user this is the
2230 text that shows where the forwarded part of the message begins. */
2231 gf_puts(_("---------- Forwarded message ----------"), pc);
2232 gf_puts(NEWLINE, pc);
2236 /*----------------------------------------------------------------------
2237 Wrapper for header formatting tool
2239 Args: stream --
2240 msgno --
2241 env --
2242 pc --
2243 prefix --
2245 Result: header suitable for reply/forward text written using "pc"
2247 ----------------------------------------------------------------------*/
2248 void
2249 reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env,
2250 gf_io_t pc, char *prefix)
2252 int rv;
2253 HEADER_S h;
2254 char **list, **new_list = NULL;
2256 list = ps_global->VAR_VIEW_HEADERS;
2259 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2260 * the user doesn't inadvertently forward the BCC header.
2262 if(list && list[0]){
2263 int i, cnt = 0;
2264 char **p;
2266 while(list[cnt++])
2269 p = new_list = (char **) fs_get((cnt+1) * sizeof(char *));
2271 for(i=0; list[i]; i++)
2272 if(strucmp(list[i], "bcc"))
2273 *p++ = cpystr(list[i]);
2275 *p = NULL;
2277 if(new_list && new_list[0])
2278 list = new_list;
2282 HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC);
2283 if((rv = format_header(stream, msgno, part, env, &h,
2284 prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){
2285 if(rv == 1)
2286 gf_puts(" [Error fetching message header data]", pc);
2288 else
2289 gf_puts(NEWLINE, pc); /* write header delimiter */
2291 if(new_list)
2292 free_list_array(&new_list);
2296 /*----------------------------------------------------------------------
2297 Build the subject for the message number being forwarded
2299 Args: pine_state -- The usual pine structure
2300 msgno -- The message number to build subject for
2302 Result: malloc'd string containing new subject or NULL on error
2304 ----------------------------------------------------------------------*/
2305 char *
2306 forward_subject(ENVELOPE *env, int flags)
2308 size_t l;
2309 char *p, buftmp[MAILTMPLEN];
2311 if(!env)
2312 return(NULL);
2314 dprint((9, "checking subject: \"%s\"\n",
2315 env->subject ? env->subject : "NULL"));
2317 if(env->subject && env->subject[0]){ /* add (fwd)? */
2318 snprintf(buftmp, sizeof(buftmp), "%s", env->subject);
2319 buftmp[sizeof(buftmp)-1] = '\0';
2320 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2321 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
2322 SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp)
2323 strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF);
2325 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2327 removing_trailing_white_space(tmp_20k_buf);
2328 l = strlen(tmp_20k_buf);
2329 if(l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)")){
2330 char *s = cpystr(tmp_20k_buf);
2331 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%.1000s (fwd)", s);
2332 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2333 fs_give((void **) &s);
2337 * HACK: composer can't handle embedded double quotes in attachment
2338 * comments so we substitute two single quotes.
2340 if(flags & FS_CONVERT_QUOTES)
2341 while((p = strchr(tmp_20k_buf, QUOTE)) != NULL)
2342 (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''");
2344 return(cpystr(tmp_20k_buf));
2348 return(cpystr("Forwarded mail...."));
2352 /*----------------------------------------------------------------------
2353 Build the body for the message number/part being forwarded
2355 Args:
2357 Result: BODY structure suitable for sending
2359 ----------------------------------------------------------------------*/
2360 BODY *
2361 forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
2362 long int msgno, char *sect_prefix, void *msgtext, int flags)
2364 BODY *body = NULL, *text_body, *tmp_body;
2365 PART *part;
2366 gf_io_t pc;
2367 char *tmp_text, *section, sect_buf[256];
2368 int forward_raw_body = 0;
2371 * Check to see if messages got expunged out from underneath us. This
2372 * could have happened during the prompt to the user asking whether to
2373 * include the message as an attachment. Either the message is gone or
2374 * it might be at a different sequence number. We'd better bail.
2376 if(ps_global->full_header == 2
2377 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
2378 forward_raw_body = 1;
2379 if(sp_expunge_count(stream))
2380 return(NULL);
2382 if(sect_prefix && forward_raw_body == 0)
2383 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
2384 else if(sect_prefix && forward_raw_body)
2385 section = sect_prefix;
2386 else if(!sect_prefix && forward_raw_body)
2387 section = NULL;
2388 else
2389 section = "1";
2391 sect_buf[sizeof(sect_buf)-1] = '\0';
2393 gf_set_so_writec(&pc, (STORE_S *) msgtext);
2394 if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) {
2395 char *charset = NULL;
2397 /*---- Message has a single text part -----*/
2398 body = mail_newbody();
2399 body->type = TYPETEXT;
2400 body->contents.text.data = msgtext;
2401 if(orig_body
2402 && (charset = parameter_val(orig_body->parameter, "charset")))
2403 set_parameter(&body->parameter, "charset", charset);
2405 if(charset)
2406 fs_give((void **) &charset);
2408 if(!(flags & FWD_ANON)){
2409 forward_delimiter(pc);
2410 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
2413 if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body,
2414 msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){
2415 mail_free_body(&body);
2416 return(NULL);
2419 else if(orig_body->type == TYPEMULTIPART) {
2420 if(orig_body->subtype
2421 && ((!strucmp(orig_body->subtype, "signed") && orig_body->nested.part)
2422 #ifdef SMIME
2423 || (!strucmp(orig_body->subtype, "mixed")
2424 && orig_body->nested.part
2425 && orig_body->nested.part->body.type == TYPEMULTIPART
2426 && orig_body->nested.part->body.subtype
2427 && (!strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2428 || !strucmp(orig_body->nested.part->body.subtype, "signed")))
2429 || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2430 #endif /* SMIME */
2432 /* only operate on the signed data (not the signature) */
2433 body = forward_body(stream, env, &orig_body->nested.part->body,
2434 msgno,
2435 orig_body->nested.part->body.type == TYPEMULTIPART
2436 ? section : sect_prefix, msgtext, flags);
2438 /*---- Message is multipart ----*/
2439 else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
2440 "alternative")
2441 && (body = forward_multi_alt(stream, env, orig_body, msgno,
2442 sect_prefix, msgtext,
2443 pc, flags)))){
2444 /*--- Copy the body and entire structure ---*/
2445 body = copy_body(NULL, orig_body);
2448 * whatever subtype it is, demote it
2449 * to plain old MIXED.
2451 if(body->subtype)
2452 fs_give((void **) &body->subtype);
2454 body->subtype = cpystr("Mixed");
2456 /*--- The text part of the message ---*/
2457 if(!body->nested.part){
2458 q_status_message(SM_ORDER | SM_DING, 3, 6,
2459 "Error referencing body part 1");
2460 mail_free_body(&body);
2462 else if(body->nested.part->body.type == TYPETEXT) {
2463 char *new_charset = NULL;
2465 /*--- The first part is text ----*/
2466 text_body = &body->nested.part->body;
2467 text_body->contents.text.data = msgtext;
2468 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
2469 /* this text is going to the composer, it should be Plain */
2470 fs_give((void **)&text_body->subtype);
2471 text_body->subtype = cpystr("PLAIN");
2473 if(!(flags & FWD_ANON)){
2474 forward_delimiter(pc);
2475 reply_forward_header(stream, msgno,
2476 sect_prefix, env, pc, "");
2479 if(!(get_body_part_text(stream, &orig_body->nested.part->body,
2480 msgno, section, 0L, pc,
2481 NULL, &new_charset, GBPT_NONE)
2482 && fetch_contents(stream, msgno, sect_prefix, body)))
2483 mail_free_body(&body);
2484 else if(new_charset)
2485 set_parameter(&text_body->parameter, "charset", new_charset);
2487 /* BUG: ? matter that we're not setting body.size.bytes */
2489 else if(body->nested.part->body.type == TYPEMULTIPART
2490 && body->nested.part->body.subtype
2491 && !strucmp(body->nested.part->body.subtype, "alternative")
2492 && (tmp_body = forward_multi_alt(stream, env,
2493 &body->nested.part->body,
2494 msgno, sect_prefix,
2495 msgtext, pc,
2496 flags | FWD_NESTED))){
2497 /* for the forward_multi_alt call above, we want to pass
2498 * sect_prefix instead of section so we can obtain the header.
2499 * Set the FWD_NESTED flag so we fetch the right body_part.
2501 int partnum;
2503 part = body->nested.part->next;
2504 body->nested.part->next = NULL;
2505 mail_free_body_part(&body->nested.part);
2506 body->nested.part = mail_newbody_part();
2507 body->nested.part->body = *tmp_body;
2508 body->nested.part->next = part;
2510 for(partnum = 2; part != NULL; part = part->next){
2511 snprintf(sect_buf, sizeof(sect_buf), "%s%s%d",
2512 sect_prefix ? sect_prefix : "",
2513 sect_prefix ? "." : "", partnum++);
2514 sect_buf[sizeof(sect_buf)-1] = '\0';
2516 if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
2517 mail_free_body(&body);
2518 break;
2522 else {
2523 if(fetch_contents(stream, msgno, sect_prefix, body)){
2524 /*--- Create a new blank text part ---*/
2525 part = mail_newbody_part();
2526 part->next = body->nested.part;
2527 body->nested.part = part;
2528 part->body.contents.text.data = msgtext;
2530 else
2531 mail_free_body(&body);
2535 else {
2536 /*---- A single part message, not of type text ----*/
2537 body = mail_newbody();
2538 body->type = TYPEMULTIPART;
2539 part = mail_newbody_part();
2540 body->nested.part = part;
2542 /*--- The first part, a blank text part to be edited ---*/
2543 part->body.type = TYPETEXT;
2544 part->body.contents.text.data = msgtext;
2546 /*--- The second part, what ever it is ---*/
2547 part->next = mail_newbody_part();
2548 part = part->next;
2549 part->body.id = generate_message_id(NULL);
2550 copy_body(&(part->body), orig_body);
2553 * the idea here is to fetch part into storage object
2555 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
2556 EDIT_ACCESS)) != NULL){
2557 if((tmp_text = pine_mail_fetch_body(stream, msgno, section,
2558 &part->body.size.bytes, NIL)) != NULL)
2559 so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
2560 part->body.size.bytes);
2561 else
2562 mail_free_body(&body);
2564 else
2565 mail_free_body(&body);
2568 gf_clear_so_writec((STORE_S *) msgtext);
2570 return(body);
2576 * bounce_msg_body - build body from specified message suitable
2577 * for sending as bounced message
2579 char *
2580 bounce_msg_body(MAILSTREAM *stream,
2581 long int rawno,
2582 char *part,
2583 char **to,
2584 char *subject,
2585 ENVELOPE **outgoingp,
2586 BODY **bodyp,
2587 int *seenp)
2589 char *h, *p, *errstr = NULL;
2590 int i;
2591 STORE_S *msgtext;
2592 gf_io_t pc;
2594 *outgoingp = mail_newenvelope();
2595 (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail....");
2598 * Fill in destination if we were given one. If so, note that we
2599 * call p_s_s() below such that it won't prompt...
2601 if(to && *to){
2602 static char *fakedomain = "@";
2603 char *tmp_a_string;
2605 /* rfc822_parse_adrlist feels free to destroy input so copy */
2606 tmp_a_string = cpystr(*to);
2607 rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string,
2608 (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
2609 ? fakedomain : ps_global->maildomain);
2610 fs_give((void **) &tmp_a_string);
2613 /* build remail'd header */
2614 if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){
2615 for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++)
2616 i++;
2618 /* allocate it */
2619 (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);
2622 * copy it, "X-"ing out transport headers bothersome to
2623 * software but potentially useful to the human recipient...
2625 p = (*outgoingp)->remail;
2626 bounce_mask_header(&p, h);
2628 if(*h == '\015' && *(h+1) == '\012'){
2629 *p++ = *h++; /* copy CR LF */
2630 *p++ = *h++;
2631 bounce_mask_header(&p, h);
2633 while((*p++ = *h++) != '\0');
2635 /* BUG: else complain? */
2637 /* NOT bound for the composer, so no need for PicoText */
2638 if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){
2639 mail_free_envelope(outgoingp);
2640 return(_("Error allocating message text"));
2643 /* mark object for special handling */
2644 so_attr(msgtext, "rawbody", "1");
2647 * Build a fake body description. It's ignored by pine_rfc822_header,
2648 * but we need to set it to something that makes set_mime_types
2649 * not sniff it and pine_rfc822_output_body not re-encode it.
2650 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2651 * problems unless something tries to access body_encodings[] using
2652 * it without proper precautions. We don't want to use ENCOTHER
2653 * cause that tells set_mime_types to sniff it, and we don't want to
2654 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2655 * it. When there's time, it'd be nice to clean this interaction
2656 * up...
2658 *bodyp = mail_newbody();
2659 (*bodyp)->type = TYPETEXT;
2660 (*bodyp)->encoding = ENCMAX + 1;
2661 (*bodyp)->subtype = cpystr("Plain");
2662 (*bodyp)->contents.text.data = (void *) msgtext;
2663 gf_set_so_writec(&pc, msgtext);
2665 if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){
2666 MESSAGECACHE *mc;
2668 if((mc = mail_elt(stream, rawno)) != NULL)
2669 *seenp = mc->seen;
2672 /* pass NULL body to force mail_fetchtext */
2673 if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE))
2674 errstr = _("Error fetching message contents. Can't Bounce message");
2676 gf_clear_so_writec(msgtext);
2678 return(errstr);
2683 /*----------------------------------------------------------------------
2684 Mask off any header entries we don't want xport software to see
2686 Args: d -- destination string pointer pointer
2687 s -- source string pointer pointer
2689 Postfix uses Delivered-To to detect loops.
2690 Received line counting is also used to detect loops in places.
2692 ----*/
2693 void
2694 bounce_mask_header(char **d, char *s)
2696 if(((*s == 'R' || *s == 'r')
2697 && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8)
2698 || !struncmp(s+1, "eturn-Path", 10)))
2699 || !struncmp(s, "Delivered-To:", 13)){
2700 *(*d)++ = 'X'; /* write mask */
2701 *(*d)++ = '-';
2706 /*----------------------------------------------------------------------
2707 Fetch and format text for forwarding
2709 Args: stream -- Mail stream to fetch text from
2710 body -- Body structure of message being forwarded
2711 msg_no -- Message number of text for forward
2712 part_no -- Part number of text to forward
2713 partial -- If this is > 0 a partial fetch will be done and it will
2714 be done using FT_PEEK so the message will remain unseen.
2715 pc -- Function to write to
2716 prefix -- Prefix for each line
2717 ret_charset -- If we translate to another charset return that
2718 new charset here
2720 Returns: true if OK, false if problem occurred while filtering
2722 If the text is richtext, it will be converted to plain text, since there's
2723 no rich text editing capabilities in Pine (yet).
2725 It's up to calling routines to plug in signature appropriately
2727 As with all internal text, NVT end-of-line conventions are observed.
2728 DOESN'T sanity check the prefix given!!!
2729 ----*/
2731 get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body,
2732 long int msg_no, char *part_no, long partial, gf_io_t pc,
2733 char *prefix, char **ret_charset, unsigned flags)
2735 int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0;
2736 FILTLIST_S filters[12];
2737 long len;
2738 char *err, *charset, *prefix_p = NULL;
2739 int filtcnt = 0;
2740 char *free_this = NULL;
2741 DELQ_S dq;
2743 memset(filters, 0, sizeof(filters));
2744 if(ret_charset)
2745 *ret_charset = NULL;
2747 if(!pc_is_picotext(pc))
2748 we_cancel = busy_cue(NULL, NULL, 1);
2750 /* if null body, we must be talking to a non-IMAP2bis server.
2751 * No MIME parsing provided, so we just grab the message text...
2753 if(body == NULL){
2754 char *text, *decode_error;
2755 gf_io_t gc;
2756 SourceType src = CharStar;
2757 int rv = 0;
2759 (void) pine_mail_fetchstructure(stream, msg_no, NULL);
2761 if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){
2762 gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0);
2764 gf_filter_init(); /* no filters needed */
2765 if(prefix)
2766 gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
2767 if((decode_error = gf_pipe(gc, pc)) != NULL){
2768 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s",
2769 NEWLINE, NEWLINE,
2770 decode_error, NEWLINE);
2771 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2772 gf_puts(tmp_20k_buf, pc);
2773 rv++;
2776 else{
2777 gf_puts(NEWLINE, pc);
2778 gf_puts(_(" [ERROR fetching text of message]"), pc);
2779 gf_puts(NEWLINE, pc);
2780 gf_puts(NEWLINE, pc);
2781 rv++;
2784 if(we_cancel)
2785 cancel_busy_cue(-1);
2787 return(rv == 0);
2790 charset = parameter_val(body->parameter, "charset");
2792 if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
2793 if(ret_charset)
2794 *ret_charset = "UTF-8";
2797 if(charset)
2798 fs_give((void **) &charset);
2801 * just use detach, but add an auxiliary filter to insert prefix,
2802 * and, perhaps, digest richtext
2804 if(ps_global->full_header != 2
2805 && !ps_global->postpone_no_flow
2806 && (!body->subtype || !strucmp(body->subtype, "plain"))){
2807 char *parmval;
2809 flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
2810 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
2811 && (!prefix || (strucmp(prefix,"> ") == 0)
2812 || strucmp(prefix, ">") == 0));
2813 if((parmval = parameter_val(body->parameter,
2814 "format")) != NULL){
2815 if(!strucmp(parmval, "flowed")){
2816 wrapflags |= GFW_FLOWED;
2818 fs_give((void **) &parmval);
2819 if((parmval = parameter_val(body->parameter, "delsp")) != NULL){
2820 if(!strucmp(parmval, "yes")){
2821 filters[filtcnt++].filter = gf_preflow;
2822 wrapflags |= GFW_DELSP;
2825 fs_give((void **) &parmval);
2829 * if there's no prefix we're forwarding text
2830 * otherwise it's reply text. unless the user's
2831 * tied our hands, alter the prefix to continue flowed
2832 * formatting...
2834 if(flow_res && ps_global->reply.use_flowed)
2835 wrapflags |= GFW_FLOW_RESULT;
2837 filters[filtcnt].filter = gf_wrap;
2839 * The 80 will cause longer lines than what is likely
2840 * set by composer_fillcol, so we'll have longer
2841 * quoted and forwarded lines than the lines we type.
2842 * We're fine with that since the alternative is the
2843 * possible wrapping of lines we shouldn't have, which
2844 * is mitigated by this higher 80 limit.
2846 * If we were to go back to using composer_fillcol,
2847 * the correct value is composer_fillcol + 1, pine
2848 * is off-by-one from pico.
2850 filters[filtcnt++].data = gf_wrap_filter_opt(
2851 MAX(MIN(ps_global->ttyo->screen_cols
2852 - (prefix ? strlen(prefix) : 0),
2853 80 - (prefix ? strlen(prefix) : 0)),
2854 30), /* doesn't have to be 30 */
2855 990, /* 998 is the SMTP limit */
2856 NULL, 0, wrapflags);
2861 * if not flowed, remove trailing whitespace to reduce
2862 * confusion, since we're sending out as flowed if we
2863 * can. At some future point, we might try
2864 * plugging in a user-option-controlled heuristic
2865 * flowing filter
2867 * We also want to fold "> " quotes so we get the
2868 * attributions correct.
2870 if(flow_res && ps_global->reply.use_flowed && prefix && !strucmp(prefix, "> "))
2871 *(prefix_p = prefix + 1) = '\0';
2872 ps_global->reply.use_flowed = 1; /* reset for next call */
2873 if(!(wrapflags & GFW_FLOWED)
2874 && flow_res){
2875 filters[filtcnt].filter = gf_line_test;
2876 filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL);
2878 filters[filtcnt].filter = gf_line_test;
2879 filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL);
2882 else if(body->subtype){
2883 int plain_opt = 1;
2885 if(strucmp(body->subtype,"richtext") == 0){
2886 filters[filtcnt].filter = gf_rich2plain;
2887 filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
2889 else if(strucmp(body->subtype,"enriched") == 0){
2890 filters[filtcnt].filter = gf_enriched2plain;
2891 filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
2893 else if(strucmp(body->subtype,"html") == 0){
2894 if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){
2895 filters[filtcnt].filter = gf_html2plain;
2896 filters[filtcnt++].data = gf_html2plain_opt(NULL,
2897 ps_global->ttyo->screen_cols,
2898 non_messageview_margin(),
2899 NULL, NULL, GFHP_STRIPPED);
2904 if(prefix){
2905 if(ps_global->reply.strip_signature){
2906 dashdata = 0;
2907 filters[filtcnt].filter = gf_line_test;
2908 filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata);
2911 filters[filtcnt].filter = gf_prefix;
2912 filters[filtcnt++].data = gf_prefix_opt(prefix);
2914 if(wrapflags & GFW_FLOWED || flow_res){
2915 filters[filtcnt].filter = gf_line_test;
2916 filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL);
2920 if(flags & GBPT_DELQUOTES){
2921 memset(&dq, 0, sizeof(dq));
2922 dq.lines = Q_DEL_ALL;
2923 dq.is_flowed = 0;
2924 dq.indent_length = 0;
2925 dq.saved_line = &free_this;
2926 dq.handlesp = NULL;
2927 dq.do_color = 0;
2928 dq.delete_all = 1;
2930 filters[filtcnt].filter = gf_line_test;
2931 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
2934 err = detach(stream, msg_no, part_no, partial, &len, pc,
2935 filters[0].filter ? filters : NULL,
2936 ((flags & GBPT_PEEK) ? FT_PEEK : 0)
2937 | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0));
2939 if(free_this)
2940 fs_give((void **) &free_this);
2942 if(prefix_p)
2943 *prefix_p = ' ';
2945 if (err != (char *) NULL)
2946 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2947 q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
2948 err, (void *) msg_no);
2950 if(we_cancel)
2951 cancel_busy_cue(-1);
2953 return((int) len);
2958 quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local)
2960 char *p;
2962 if(*line == '>'){
2963 for(p = line; *p; p++){
2964 if(isspace((unsigned char) *p)){
2965 if(*(p+1) == '>')
2966 ins = gf_line_test_new_ins(ins, p, "", -1);
2968 else if(*p != '>')
2969 break;
2973 return(0);
2978 twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2980 char *p, *ws = NULL;
2982 for(p = line; *p; p++){
2983 /* don't strip trailing space on signature line */
2984 if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))
2985 break;
2987 if(isspace((unsigned char) *p)){
2988 if(!ws)
2989 ws = p;
2991 else
2992 ws = NULL;
2995 if(ws)
2996 ins = gf_line_test_new_ins(ins, ws, "", -(p - ws));
2998 return(0);
3002 post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local)
3004 char *p;
3006 for(p = line; *p; p++)
3007 if(*p != '>'){
3008 if(p != line && *p != ' ')
3009 ins = gf_line_test_new_ins(ins, p, " ", 1);
3011 break;
3014 return(0);
3019 sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
3021 if(*((int *)local)
3022 || (*line == '-' && *(line+1) == '-'
3023 && *(line+2) == ' ' && !*(line+3))){
3024 *((int *) local) = 1;
3025 return(2); /* skip this line! */
3028 return(0);
3032 /*----------------------------------------------------------------------
3033 return the c-client reference name for the given end_body part
3034 ----*/
3035 char *
3036 body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body)
3038 BODY *body;
3040 (void) pine_mail_fetchstructure(stream, msgno, &body);
3041 return(partno(body, end_body));
3045 /*----------------------------------------------------------------------
3046 return the c-client reference name for the given end_body part
3047 ----*/
3048 char *
3049 partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body)
3051 #define PARTTMPLEN 64
3052 PART *part;
3053 int num = 0;
3054 char tmp[PARTTMPLEN], *p = NULL;
3056 if(body && body->type == TYPEMULTIPART) {
3057 part = body->nested.part; /* first body part */
3059 do { /* for each part */
3060 num++; /* PARTTMPLEN = sizeof(tmp) */
3061 if(&part->body == end_body || (p = partno(&part->body, end_body))){
3062 snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "",
3063 PARTTMPLEN-12, (p) ? p : "");
3064 tmp[sizeof(tmp)-1] = '\0';
3065 if(p)
3066 fs_give((void **)&p);
3068 return(cpystr(tmp));
3070 } while ((part = part->next) != NULL); /* until done */
3072 return(NULL);
3074 else if(body && body->type == TYPEMESSAGE && body->subtype
3075 && !strucmp(body->subtype, "rfc822")){
3076 return(partno(body->nested.msg->body, end_body));
3079 return((body == end_body) ? cpystr("1") : NULL);
3083 /*----------------------------------------------------------------------
3084 Fill in the contents of each body part
3086 Args: stream -- Stream the message is on
3087 msgno -- Message number the body structure is for
3088 section -- body section associated with body pointer
3089 body -- Body pointer to fill in
3091 Result: 1 if all went OK, 0 if there was a problem
3093 This function copies the contents from an original message/body to
3094 a new message/body. It recurses down all multipart levels.
3096 If one or more part (but not all) can't be fetched, a status message
3097 will be queued.
3098 ----*/
3100 fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body)
3102 char *tp;
3103 int got_one = 0;
3105 if(!body->id)
3106 body->id = generate_message_id(NULL);
3108 if(body->type == TYPEMULTIPART){
3109 char subsection[256], *subp;
3110 int n, last_one = 10; /* remember worst case */
3111 PART *part = body->nested.part;
3113 if(!(part = body->nested.part))
3114 return(0);
3116 subp = subsection;
3117 if(section && *section){
3118 for(n = 0;
3119 n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++)
3122 *subp++ = '.';
3125 n = 1;
3126 do {
3127 snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++);
3128 subsection[sizeof(subsection)-1] = '\0';
3129 got_one = fetch_contents(stream, msgno, subsection, &part->body);
3130 last_one = MIN(last_one, got_one);
3132 while((part = part->next) != NULL);
3134 return(last_one);
3137 if(body->contents.text.data)
3138 return(1); /* already taken care of... */
3140 if(body->type == TYPEMESSAGE){
3141 if(body->subtype && strucmp(body->subtype,"external-body")){
3143 * the idea here is to fetch everything into storage objects
3145 body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
3146 EDIT_ACCESS);
3147 if(body->contents.text.data
3148 && (tp = pine_mail_fetch_body(stream, msgno, section,
3149 &body->size.bytes, NIL))){
3150 so_truncate((STORE_S *)body->contents.text.data,
3151 body->size.bytes + 2048);
3152 so_nputs((STORE_S *)body->contents.text.data, tp,
3153 body->size.bytes);
3154 got_one = 1;
3156 else
3157 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3158 _("Error fetching part %s"), section);
3159 } else {
3160 got_one = 1;
3162 } else {
3164 * the idea here is to fetch everything into storage objects
3165 * so, grab one, then fetch the body part
3167 body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
3168 if(body->contents.text.data
3169 && (tp=pine_mail_fetch_body(stream, msgno, section,
3170 &body->size.bytes, NIL))){
3171 so_truncate((STORE_S *)body->contents.text.data,
3172 body->size.bytes + 2048);
3173 so_nputs((STORE_S *)body->contents.text.data, tp,
3174 body->size.bytes);
3175 got_one = 1;
3177 else
3178 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3179 _("Error fetching part %s"), section);
3182 return(got_one);
3186 /*----------------------------------------------------------------------
3187 Copy the body structure
3189 Args: new_body -- Pointer to already allocated body, or NULL, if none
3190 old_body -- The Body to copy
3193 This is traverses the body structure recursively copying all elements.
3194 The new_body parameter can be NULL in which case a new body is
3195 allocated. Alternatively it can point to an already allocated body
3196 structure. This is used when copying body parts since a PART includes a
3197 BODY. The contents fields are *not* filled in.
3198 ----*/
3200 BODY *
3201 copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body)
3203 if(old_body == NULL)
3204 return(NULL);
3206 if(new_body == NULL)
3207 new_body = mail_newbody();
3209 new_body->type = old_body->type;
3210 new_body->encoding = old_body->encoding;
3212 if(old_body->subtype)
3213 new_body->subtype = cpystr(old_body->subtype);
3215 new_body->parameter = copy_parameters(old_body->parameter);
3217 if(old_body->id)
3218 new_body->id = cpystr(old_body->id);
3220 if(old_body->description)
3221 new_body->description = cpystr(old_body->description);
3223 if(old_body->disposition.type)
3224 new_body->disposition.type = cpystr(old_body->disposition.type);
3226 new_body->disposition.parameter
3227 = copy_parameters(old_body->disposition.parameter);
3229 new_body->size = old_body->size;
3231 if(new_body->type == TYPEMESSAGE
3232 && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
3233 new_body->nested.msg = mail_newmsg();
3234 new_body->nested.msg->body
3235 = copy_body(NULL, old_body->nested.msg->body);
3237 else if(new_body->type == TYPEMULTIPART) {
3238 PART **new_partp, *old_part;
3240 new_partp = &new_body->nested.part;
3241 for(old_part = old_body->nested.part;
3242 old_part != NULL;
3243 old_part = old_part->next){
3244 *new_partp = mail_newbody_part();
3245 copy_body(&(*new_partp)->body, &old_part->body);
3246 new_partp = &(*new_partp)->next;
3250 return(new_body);
3254 /*----------------------------------------------------------------------
3255 Copy the MIME parameter list
3257 Allocates storage for new part, and returns pointer to new parameter
3258 list. If old_p is NULL, NULL is returned.
3259 ----*/
3260 PARAMETER *
3261 copy_parameters(PARAMETER *old_p)
3263 PARAMETER *new_p, *p1, *p2;
3265 if(old_p == NULL)
3266 return((PARAMETER *)NULL);
3268 new_p = p2 = NULL;
3269 for(p1 = old_p; p1 != NULL; p1 = p1->next){
3270 set_parameter(&p2, p1->attribute, p1->value);
3271 if(new_p == NULL)
3272 new_p = p2;
3275 return(new_p);
3279 /*----------------------------------------------------------------------
3280 Make a complete copy of an envelope and all it's fields
3282 Args: e -- the envelope to copy
3284 Result: returns the new envelope, or NULL, if the given envelope was NULL
3286 ----*/
3288 ENVELOPE *
3289 copy_envelope(register ENVELOPE *e)
3291 register ENVELOPE *e2;
3293 if(!e)
3294 return(NULL);
3296 e2 = mail_newenvelope();
3297 e2->remail = e->remail ? cpystr(e->remail) : NULL;
3298 e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
3299 e2->date = e->date ? (unsigned char *)cpystr((char *) e->date)
3300 : NULL;
3301 e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL;
3302 e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL;
3303 e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL;
3304 e2->subject = e->subject ? cpystr(e->subject) : NULL;
3305 e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL;
3306 e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL;
3307 e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL;
3308 e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL;
3309 e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL;
3310 e2->message_id = e->message_id ? cpystr(e->message_id) : NULL;
3311 e2->references = e->references ? cpystr(e->references) : NULL;
3312 e2->followup_to = e->followup_to ? cpystr(e->references) : NULL;
3313 return(e2);
3317 /*----------------------------------------------------------------------
3318 Generate the "In-reply-to" text from message header
3320 Args: message -- Envelope of original message
3322 Result: returns an alloc'd string or NULL if there is a problem
3323 ----*/
3324 char *
3325 reply_in_reply_to(ENVELOPE *env)
3327 return((env && env->message_id) ? cpystr(env->message_id) : NULL);
3331 /*----------------------------------------------------------------------
3332 Generate a unique message id string.
3334 Args: ps -- The usual pine structure
3336 Result: Alloc'd unique string is returned
3338 Uniqueness is guaranteed by using the host name, process id, date to the
3339 second and a single unique character
3340 *----------------------------------------------------------------------*/
3341 char *
3342 generate_message_id(ACTION_S *role)
3344 char *id, *leftpart, *hostpart, *prehostpart;
3346 if(role && role->from)
3347 prehostpart = cpystr(role->from->host ? role->from->host : "huh");
3348 else if(ps_global->maildomain) /* as in generate_from() */
3349 prehostpart = cpystr(ps_global->maildomain);
3350 else
3351 prehostpart = cpystr(ps_global->hostname);
3353 if(F_ON(F_ROT13_MESSAGE_ID, ps_global)){
3354 hostpart = rot13(prehostpart);
3355 leftpart = rot13(oauth2_generate_state());
3356 } else {
3357 hostpart = prehostpart;
3358 leftpart = oauth2_generate_state();
3361 id = fs_get(strlen(leftpart) + strlen(hostpart) + 4);
3362 sprintf(id, "<%s@%s>", leftpart, hostpart);
3364 fs_give((void **) &hostpart);
3365 fs_give((void **) &leftpart);
3366 prehostpart = NULL;
3368 return(id);
3372 char *
3373 generate_user_agent(void)
3375 char buf[128];
3376 char rev[128];
3378 if(F_ON(F_QUELL_USERAGENT, ps_global))
3379 return(NULL);
3381 snprintf(buf, sizeof(buf),
3382 "%sAlpine %s (%s %s)",
3383 (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "",
3384 ALPINE_VERSION, SYSTYPE,
3385 get_alpine_revision_string(rev, sizeof(rev)));
3387 return(cpystr(buf));
3391 char *
3392 rot13(char *src)
3394 char byte, cap, *p, *ret = NULL;
3396 if(src && *src){
3397 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3398 p = ret;
3399 while((byte = *src++) != '\0'){
3400 cap = byte & 32;
3401 byte &= ~cap;
3402 *p++ = ((byte >= 'A') && (byte <= 'Z')
3403 ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap;
3406 *p = '\0';
3409 return(ret);
3412 char *
3413 rot5n(char *src)
3415 char byte, *p, *ret = NULL;
3417 if(src && *src){
3418 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3419 p = ret;
3420 while((byte = *src++) != '\0')
3421 *p++ = ((byte >= '0') && (byte <= '9')
3422 ? ((byte - '0' + 5) % 10 + '0') : byte);
3423 *p = '\0';
3426 return(ret);
3430 /*----------------------------------------------------------------------
3431 Return the first true address pointer (modulo group syntax allowance)
3433 Args: addr -- Address list
3435 Result: First real address pointer, or NULL
3436 ----------------------------------------------------------------------*/
3437 ADDRESS *
3438 first_addr(struct mail_address *addr)
3440 while(addr && !addr->host)
3441 addr = addr->next;
3443 return(addr);
3447 /*----------------------------------------------------------------------
3448 lit -- this is the source
3449 prenewlines -- prefix the file contents with this many newlines
3450 postnewlines -- postfix the file contents with this many newlines
3451 is_sig -- this is a signature (not a template)
3452 decode_constants -- change C-style constants into their values
3453 ----*/
3454 char *
3455 get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants)
3457 char *sig = NULL;
3460 * Should make this smart enough not to do the copying and double
3461 * allocation of space.
3463 if(lit){
3464 char *tmplit = NULL, *p, *q, *d, save;
3465 size_t len;
3467 if(decode_constants){
3468 tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char));
3469 tmplit[0] = '\0';
3470 cstring_to_string(lit, tmplit);
3472 else
3473 tmplit = cpystr(lit);
3475 len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE);
3476 sig = (char *) fs_get((len+1) * sizeof(char));
3477 memset(sig, 0, len+1);
3478 d = sig;
3479 while(prenewlines--)
3480 sstrncpy(&d, NEWLINE, len-(d-sig));
3482 if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
3483 !sigdashes_are_present(tmplit)){
3484 sstrncpy(&d, SIGDASHES, len-(d-sig));
3485 sstrncpy(&d, NEWLINE, len-(d-sig));
3488 sig[len] = '\0';
3490 p = tmplit;
3491 while(*p){
3492 /* get a line */
3493 q = strpbrk(p, "\n\r");
3494 if(q){
3495 save = *q;
3496 *q = '\0';
3500 * Strip trailing space if we are doing a signature and
3501 * this line is not sigdashes.
3503 if(is_sig && strcmp(p, SIGDASHES))
3504 removing_trailing_white_space(p);
3506 while((d-sig) <= len && (*d = *p++) != '\0')
3507 d++;
3509 if(q){
3510 if((d-sig) <= len)
3511 *d++ = save;
3513 p = q+1;
3515 else
3516 break;
3519 while(postnewlines--)
3520 sstrncpy(&d, NEWLINE, len-(d-sig));
3522 sig[len] = '\0';
3524 if((d-sig) <= len)
3525 *d = '\0';
3527 if(tmplit)
3528 fs_give((void **) &tmplit);
3531 return(sig);
3536 sigdashes_are_present(char *sig)
3538 char *p;
3540 p = srchstr(sig, SIGDASHES);
3541 while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
3542 (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
3543 p = srchstr(p+1, SIGDASHES);
3545 return(p ? 1 : 0);
3549 /*----------------------------------------------------------------------
3550 Acquire the pinerc defined signature file pathname
3552 ----*/
3553 char *
3554 signature_path(char *sname, char *sbuf, size_t len)
3556 *sbuf = '\0';
3557 if(sname && *sname){
3558 size_t spl = strlen(sname);
3559 if(IS_REMOTE(sname)){
3560 if(spl < len - 1)
3561 strncpy(sbuf, sname, len-1);
3563 else if(is_absolute_path(sname)){
3564 strncpy(sbuf, sname, len-1);
3565 sbuf[len-1] = '\0';
3566 fnexpand(sbuf, len);
3568 else if(ps_global->VAR_OPER_DIR){
3569 if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
3570 build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len);
3572 else{
3573 char *lc = last_cmpnt(ps_global->pinerc);
3575 sbuf[0] = '\0';
3576 if(lc != NULL){
3577 strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc));
3578 sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0';
3581 strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0));
3582 sbuf[len-1] = '\0';
3586 return(*sbuf ? sbuf : NULL);
3590 char *
3591 simple_read_remote_file(char *name, char *subtype)
3593 int try_cache;
3594 REMDATA_S *rd;
3595 char *file = NULL;
3598 dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?"));
3601 * We could parse the name here to find what type it is. So far we
3602 * only have type RemImap.
3604 rd = rd_create_remote(RemImap, name, subtype,
3605 NULL, _("Error: "), _("Can't fetch remote configuration."));
3606 if(!rd)
3607 goto bail_out;
3609 try_cache = rd_read_metadata(rd);
3611 if(rd->access == MaybeRorW){
3612 if(rd->read_status == 'R')
3613 rd->access = ReadOnly;
3614 else
3615 rd->access = ReadWrite;
3618 if(rd->access != NoExists){
3620 rd_check_remvalid(rd, 1L);
3623 * If the cached info says it is readonly but
3624 * it looks like it's been fixed now, change it to readwrite.
3626 if(rd->read_status == 'R'){
3628 * We go to this trouble since readonly sigfiles
3629 * are likely a mistake. They are usually supposed to be
3630 * readwrite so we open it and check if it's been fixed.
3632 rd_check_readonly_access(rd);
3633 if(rd->read_status == 'W'){
3634 rd->access = ReadWrite;
3635 rd->flags |= REM_OUTOFDATE;
3637 else
3638 rd->access = ReadOnly;
3641 if(rd->flags & REM_OUTOFDATE){
3642 if(rd_update_local(rd) != 0){
3644 dprint((1,
3645 "simple_read_remote_file: rd_update_local failed\n"));
3647 * Don't give up altogether. We still may be
3648 * able to use a cached copy.
3651 else{
3652 dprint((7,
3653 "%s: copied remote to local (%ld)\n",
3654 rd->rn ? rd->rn : "?", (long)rd->last_use));
3658 if(rd->access == ReadWrite)
3659 rd->flags |= DO_REMTRIM;
3662 /* If we couldn't get to remote folder, try using the cached copy */
3663 if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){
3664 if(try_cache){
3665 rd->access = ReadOnly;
3666 rd->flags |= USE_OLD_CACHE;
3667 q_status_message(SM_ORDER, 3, 4,
3668 "Can't contact remote server, using cached copy");
3669 dprint((2,
3670 "Can't open remote file %s, using local cached copy %s readonly\n",
3671 rd->rn ? rd->rn : "?",
3672 rd->lf ? rd->lf : "?"));
3674 else{
3675 rd->flags &= ~DO_REMTRIM;
3676 goto bail_out;
3680 file = read_file(rd->lf, READ_FROM_LOCALE);
3682 bail_out:
3683 if(rd)
3684 rd_close_remdata(&rd);
3686 return(file);
3689 /* special handling for messages that contain a mixed part in the
3690 * multipart alternative section.
3692 BODY *
3693 forward_multi_alt_mixed(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3694 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3696 #define FWDTMPLEN 256
3697 BODY *body = NULL, *text_body = NULL;
3698 PART *part = NULL;
3699 char prefix_buf[FWDTMPLEN];
3700 int partnum;
3701 char *section, sect_buf[256];
3702 int forward_raw_body = 0;
3704 if(orig_body
3705 && orig_body->type == TYPEMULTIPART
3706 && orig_body->subtype
3707 && !strucmp(orig_body->subtype, "alternative"))
3708 for(part = orig_body->nested.part, partnum = 1;
3709 part;
3710 part = part->next, partnum++)
3711 if(part->body.type == TYPEMULTIPART
3712 && part->body.subtype
3713 && !strucmp(part->body.subtype, "MIXED"))
3714 break;
3716 if(part == NULL) return NULL;
3718 snprintf(prefix_buf, sizeof(prefix_buf), "%.*s%s%s%d",
3719 FWDTMPLEN/2, sect_prefix ? sect_prefix : "",
3720 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3721 partnum);
3722 prefix_buf[sizeof(prefix_buf)-1] = '\0';
3724 if(ps_global->full_header == 2
3725 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
3726 forward_raw_body = 1;
3727 if(sp_expunge_count(stream))
3728 return(NULL);
3730 if(sect_prefix && forward_raw_body == 0)
3731 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
3732 else if(sect_prefix && forward_raw_body)
3733 section = sect_prefix;
3734 else if(!sect_prefix && forward_raw_body)
3735 section = NULL;
3736 else
3737 section = "1";
3738 sect_buf[sizeof(sect_buf)-1] = '\0';
3740 body = copy_body(NULL, &part->body);
3742 /*--- The text part of the message ---*/
3743 if(!body->nested.part){
3744 q_status_message(SM_ORDER | SM_DING, 3, 6,
3745 "Error referencing body part 1");
3746 mail_free_body(&body);
3748 else if(body->nested.part->body.type == TYPETEXT) {
3749 char *new_charset = NULL;
3751 /*--- The first part is text ----*/
3752 text_body = &body->nested.part->body;
3753 text_body->contents.text.data = msgtext;
3754 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
3755 /* this text is going to the composer, it should be Plain */
3756 fs_give((void **)&text_body->subtype);
3757 text_body->subtype = cpystr("PLAIN");
3759 if(!(flags & FWD_ANON)){
3760 forward_delimiter(pc);
3761 reply_forward_header(stream, msgno,
3762 sect_prefix, env, pc, "");
3765 if(!(get_body_part_text(stream, &part->body,
3766 msgno, section, 0L, pc,
3767 NULL, &new_charset, GBPT_NONE)
3768 && fetch_contents(stream, msgno, prefix_buf, body)))
3769 mail_free_body(&body);
3770 else if(new_charset)
3771 set_parameter(&text_body->parameter, "charset", new_charset);
3773 return(body);
3777 /*----------------------------------------------------------------------
3778 Build the body for the multipart/alternative part
3780 Args:
3782 Result:
3784 ----------------------------------------------------------------------*/
3785 BODY *
3786 forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3787 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3789 #define FWDTMPLEN 256
3790 BODY *body = NULL;
3791 PART *part = NULL, *bestpart = NULL;
3792 char tmp_buf[FWDTMPLEN];
3793 char *new_charset = NULL;
3794 int partnum, bestpartnum;
3796 /* try multipart mixed first */
3797 if((body = forward_multi_alt_mixed(stream, env, orig_body,
3798 msgno, sect_prefix, msgtext, pc, flags)) != NULL)
3799 return body;
3801 if(ps_global->force_prefer_plain
3802 || (!ps_global->force_no_prefer_plain
3803 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
3804 for(part = orig_body->nested.part, partnum = 1;
3805 part;
3806 part = part->next, partnum++)
3807 if((!part->body.type || part->body.type == TYPETEXT)
3808 && (!part->body.subtype
3809 || !strucmp(part->body.subtype, "plain")))
3810 break;
3814 * Else choose last alternative among plain or html parts.
3815 * Perhaps we should really be using mime_show() to make this
3816 * potentially more general than just plain or html.
3818 if(!part){
3819 for(part = orig_body->nested.part, partnum = 1;
3820 part;
3821 part = part->next, partnum++){
3822 if((!part->body.type || part->body.type == TYPETEXT)
3823 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
3825 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
3826 bestpart = part;
3827 bestpartnum = partnum;
3831 part = bestpart;
3832 partnum = bestpartnum;
3836 * IF something's interesting insert it
3837 * AND forget the rest of the multipart
3839 if(part){
3840 body = mail_newbody();
3841 body->type = TYPETEXT;
3842 body->contents.text.data = msgtext;
3844 /* record character set, flowing, etc */
3845 body->parameter = copy_parameters(part->body.parameter);
3846 body->size.bytes = part->body.size.bytes;
3848 if(!(flags & FWD_ANON)){
3849 forward_delimiter(pc);
3850 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
3852 /* FWDTMPLEN = sizeof(tmp_buf) */
3853 snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d",
3854 FWDTMPLEN/2, sect_prefix ? sect_prefix : "",
3855 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3856 partnum);
3857 tmp_buf[sizeof(tmp_buf)-1] = '\0';
3858 get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc,
3859 NULL, &new_charset, GBPT_NONE);
3862 * get_body_part_text translated the data to a new charset.
3863 * We need to record that fact in body.
3865 if(new_charset)
3866 set_parameter(&body->parameter, "charset", new_charset);
3868 else
3869 q_status_message(SM_ORDER | SM_DING, 3, 3,
3870 "No suitable part found. Forwarding as attachment");
3872 return(body);
3876 void
3877 reply_append_addr(struct mail_address **dest, struct mail_address *src)
3879 for( ; *dest; dest = &(*dest)->next)
3882 *dest = src;