* Create help for explaining how encrypted password file support
[alpine.git] / pith / reply.c
blobd7270a2c4e15774934bf658ffa53c53fb9b2bfcb
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2014 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/reply.h"
21 #include "../pith/send.h"
22 #include "../pith/init.h"
23 #include "../pith/state.h"
24 #include "../pith/conf.h"
25 #include "../pith/remote.h"
26 #include "../pith/status.h"
27 #include "../pith/mailview.h"
28 #include "../pith/filter.h"
29 #include "../pith/newmail.h"
30 #include "../pith/bldaddr.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/mimedesc.h"
33 #include "../pith/detach.h"
34 #include "../pith/help.h"
35 #include "../pith/pipe.h"
36 #include "../pith/addrstring.h"
37 #include "../pith/news.h"
38 #include "../pith/util.h"
39 #include "../pith/pattern.h"
40 #include "../pith/detoken.h"
41 #include "../pith/stream.h"
42 #include "../pith/busy.h"
43 #include "../pith/readfile.h"
44 #include "../pith/text.h"
45 #include "../pith/list.h"
46 #include "../pith/ablookup.h"
47 #include "../pith/mailcmd.h"
48 #include "../pith/margin.h"
52 * Internal prototypes
54 void bounce_mask_header(char **, char *);
57 int (*pith_opt_replyto_prompt)(void);
58 int (*pith_opt_reply_to_all_prompt)(int *);
62 * standard type of storage object used for body parts...
64 #define PART_SO_TYPE CharStar
67 char *(*pith_opt_user_agent_prefix)(void);
71 * reply_harvest -
73 * Returns: 1 if addresses successfully copied
74 * 0 on user cancel or error
76 * Input flags:
77 * RSF_FORCE_REPLY_TO
78 * RSF_QUERY_REPLY_ALL
79 * RSF_FORCE_REPLY_ALL
81 * Output flags:
82 * RSF_FORCE_REPLY_ALL
85 int
86 reply_harvest(struct pine *ps, long int msgno, char *section, ENVELOPE *env,
87 struct mail_address **saved_from, struct mail_address **saved_to,
88 struct mail_address **saved_cc, struct mail_address **saved_resent,
89 int *flags)
91 ADDRESS *ap, *ap2, *rep_address;
92 int ret = 0, sniff_resent = 0;
93 char *rep_field;
96 * If Reply-To is same as From just treat it like it was From.
97 * Otherwise, always use the reply-to if we're replying to more
98 * than one msg or say ok to using it, even if it's us.
99 * If there's no reply-to or it's the same as the from, assume
100 * that the user doesn't want to reply to himself, unless there's
101 * nobody else.
103 if(env->reply_to && !addr_lists_same(env->reply_to, env->from)
104 && (F_ON(F_AUTO_REPLY_TO, ps_global)
105 || ((*flags) & RSF_FORCE_REPLY_TO)
106 || (pith_opt_replyto_prompt && (*pith_opt_replyto_prompt)() == 'y'))){
107 rep_field = "reply-to";
108 rep_address = env->reply_to;
110 else{
111 rep_field = "From";
112 rep_address = env->from;
115 ap = reply_cp_addr(ps, msgno, section, rep_field, *saved_from,
116 (ADDRESS *) NULL, rep_address, RCA_NOT_US);
118 if(ret == 'x') {
119 cmd_cancelled("Reply");
120 return(0);
123 reply_append_addr(saved_from, ap);
125 /*--------- check for other recipients ---------*/
126 if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){
128 if((ap = reply_cp_addr(ps, msgno, section, "To", *saved_to,
129 *saved_from, env->to, RCA_NOT_US)) != NULL)
130 reply_append_addr(saved_to, ap);
132 if((ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc,
133 *saved_from, env->cc, RCA_NOT_US)) != NULL)
134 reply_append_addr(saved_cc, ap);
137 * In these cases, we either need to look at the resent headers
138 * to include in the reply-to-all, or to decide whether or not
139 * we need to ask the reply-to-all question.
141 if(((*flags) & RSF_FORCE_REPLY_ALL)
142 || (((*flags) & RSF_QUERY_REPLY_ALL)
143 && ((!(*saved_from) && !(*saved_cc))
144 || (*saved_from && !(*saved_to) && !(*saved_cc))))){
146 sniff_resent++;
147 if((ap2 = reply_resent(ps, msgno, section)) != NULL){
149 * look for bogus addr entries and replace
151 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
152 *saved_from, ap2, RCA_NOT_US)) != NULL)
154 reply_append_addr(saved_resent, ap);
156 mail_free_address(&ap2);
161 * It makes sense to ask reply-to-all now.
163 if(((*flags) & RSF_QUERY_REPLY_ALL)
164 && ((*saved_from && (*saved_to || *saved_cc || *saved_resent))
165 || (*saved_cc || *saved_resent))){
166 *flags &= ~RSF_QUERY_REPLY_ALL;
167 if(pith_opt_reply_to_all_prompt
168 && (*pith_opt_reply_to_all_prompt)(flags) < 0){
169 cmd_cancelled("Reply");
170 return(0);
175 * If we just answered yes to the reply-to-all question and
176 * we still haven't collected the resent headers, do so now.
178 if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent
179 && (ap2 = reply_resent(ps, msgno, section))){
181 * look for bogus addr entries and replace
183 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
184 *saved_from, ap2, RCA_NOT_US)) != NULL)
185 reply_append_addr(saved_resent, ap);
187 mail_free_address(&ap2);
191 return(1);
195 /*----------------------------------------------------------------------
196 Return a pointer to a copy of the given address list
197 filtering out those already in the "mask" lists and ourself.
199 Args: mask1 -- Don't copy if in this list
200 mask2 -- or if in this list
201 source -- List to be copied
202 us_too -- Don't filter out ourself.
203 flags -- RCA_NOT_US copy all addrs except for our own
204 RCA_ALL copy all addrs, including our own
205 RCA_ONLY_US copy only addrs that are our own
207 ---*/
208 ADDRESS *
209 reply_cp_addr(struct pine *ps, long int msgno, char *section, char *field,
210 struct mail_address *mask1, struct mail_address *mask2,
211 struct mail_address *source, int flags)
213 ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail;
215 /* can only choose one of these flags values */
216 assert(!((flags & RCA_ALL && flags & RCA_ONLY_US)
217 || (flags & RCA_ALL && flags & RCA_NOT_US)
218 || (flags & RCA_ONLY_US && flags & RCA_NOT_US)));
220 for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next)
221 if(tmp1->host && tmp1->host[0] == '.'){
222 char *h, *fields[2];
224 fields[0] = field;
225 fields[1] = NULL;
226 if((h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL,
227 msgno, section, fields)) != NULL){
228 char *p, fname[32];
229 int q;
231 q = strlen(h);
233 strncpy(fname, field, sizeof(fname)-2);
234 fname[sizeof(fname)-2] = '\0';
235 strncat(fname, ":", sizeof(fname)-strlen(fname)-1);
236 fname[sizeof(fname)-1] = '\0';
238 for(p = h; (p = strstr(p, fname)) != NULL; )
239 rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */
241 sqznewlines(h); /* blat out CR's & LF's */
242 for(p = h; (p = strchr(p, TAB)) != NULL; )
243 *p++ = ' '; /* turn TABs to whitespace */
245 if(*h){
246 long l;
247 size_t ll;
249 ret = (ADDRESS *) fs_get(sizeof(ADDRESS));
250 memset(ret, 0, sizeof(ADDRESS));
252 /* get rid of leading white space */
253 for(p = h; *p == SPACE; p++)
256 if(p != h){
257 memmove(h, p, l = strlen(p));
258 h[l] = '\0';
261 /* base64 armor plate the gunk to protect against
262 * c-client quoting in output.
264 p = (char *) rfc822_binary(h, strlen(h),
265 (unsigned long *) &l);
266 sqznewlines(p);
267 fs_give((void **) &h);
269 * Seems like the 4 ought to be a 2, but I'll leave it
270 * to be safe, in case something else adds 2 chars later.
272 ll = strlen(p) + 4;
273 ret->mailbox = (char *) fs_get(ll * sizeof(char));
274 snprintf(ret->mailbox, ll, "&%s", p);
275 ret->mailbox[ll-1] = '\0';
276 fs_give((void **) &p);
277 ret->host = cpystr(RAWFIELD);
281 return(ret);
284 ret_tail = &ret;
285 for(source = first_addr(source); source; source = source->next){
286 for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next)
287 if(address_is_same(source, tmp1)) /* it is in mask1, skip it */
288 break;
290 for(tmp2 = first_addr(mask2); !tmp1 && tmp2; tmp2 = tmp2->next)
291 if(address_is_same(source, tmp2)) /* it is in mask2, skip it */
292 break;
295 * If there's no match in masks and this address satisfies the
296 * flags requirement, copy it.
298 if(!tmp1 && !tmp2 /* no mask match */
299 && ((flags & RCA_ALL) /* including everybody */
300 || (flags & RCA_ONLY_US && address_is_us(source, ps))
301 || (flags & RCA_NOT_US && !address_is_us(source, ps)))){
302 tmp1 = source->next;
303 source->next = NULL; /* only copy one addr! */
304 *ret_tail = rfc822_cpy_adr(source);
305 ret_tail = &(*ret_tail)->next;
306 source->next = tmp1; /* restore rest of list */
310 return(ret);
314 ACTION_S *
315 set_role_from_msg(struct pine *ps, long int rflags, long int msgno, char *section)
317 ACTION_S *role = NULL;
318 PAT_S *pat = NULL;
319 SEARCHSET *ss = NULL;
320 PAT_STATE pstate;
322 if(!nonempty_patterns(rflags, &pstate))
323 return(role);
325 if(msgno > 0L){
326 ss = mail_newsearchset();
327 ss->first = ss->last = (unsigned long)msgno;
330 /* Go through the possible roles one at a time until we get a match. */
331 pat = first_pattern(&pstate);
333 /* calculate this message's score if needed */
334 if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES &&
335 get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF)
336 (void)calculate_some_scores(ps->mail_stream, ss, 0);
338 while(!role && pat){
339 if(match_pattern(pat->patgrp, ps->mail_stream, ss, section,
340 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
341 if(!pat->action || pat->action->bogus)
342 break;
344 role = pat->action;
346 else
347 pat = next_pattern(&pstate);
350 if(ss)
351 mail_free_searchset(&ss);
353 return(role);
358 * reply_seed - fill in reply header
361 void
362 reply_seed(struct pine *ps, ENVELOPE *outgoing, ENVELOPE *env,
363 struct mail_address *saved_from, struct mail_address *saved_to,
364 struct mail_address *saved_cc, struct mail_address *saved_resent,
365 char **fcc, int replytoall, char **errmsg)
367 ADDRESS **to_tail, **cc_tail;
369 to_tail = &outgoing->to;
370 cc_tail = &outgoing->cc;
372 if(saved_from){
373 /* Put Reply-To or From in To. */
374 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
375 (ADDRESS *) NULL, saved_from, RCA_ALL);
376 if(replytoall){
377 if(ps->preserve){
378 while(*to_tail)
379 to_tail = &(*to_tail)->next;
381 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
382 (ADDRESS *) NULL, saved_to, RCA_ALL);
384 while(*to_tail)
385 to_tail = &(*to_tail)->next;
387 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
388 outgoing->to, saved_resent, RCA_ALL);
390 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
391 outgoing->to, saved_cc, RCA_ALL);
393 else{ /* and the rest in cc */
394 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
395 outgoing->to, saved_to, RCA_ALL);
396 while(*cc_tail) /* stay on last address */
397 cc_tail = &(*cc_tail)->next;
399 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
400 outgoing->to, saved_cc, RCA_ALL);
401 while(*cc_tail)
402 cc_tail = &(*cc_tail)->next;
404 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
405 outgoing->to, saved_resent, RCA_ALL);
409 else if(saved_to){
410 /* No From (maybe from us), put To in To. */
411 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
412 (ADDRESS *)NULL, saved_to, RCA_ALL);
413 /* and the rest in cc */
414 if(replytoall){
415 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
416 outgoing->to, saved_cc, RCA_ALL);
417 while(*cc_tail)
418 cc_tail = &(*cc_tail)->next;
420 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
421 outgoing->to, saved_resent, RCA_ALL);
424 else{
425 /* No From or To, put everything else in To if replytoall, */
426 if(replytoall){
427 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
428 (ADDRESS *) NULL, saved_cc, RCA_ALL);
429 while(*to_tail)
430 to_tail = &(*to_tail)->next;
432 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
433 (ADDRESS *) NULL, saved_resent, RCA_ALL);
435 /* else, reply to original From which must be us */
436 else{
438 * Put self in To if in original From.
440 if(!outgoing->newsgroups)
441 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
442 (ADDRESS *) NULL, env->from, RCA_ALL);
446 /* add any missing personal data */
447 reply_fish_personal(outgoing, env);
449 /* get fcc */
450 if(fcc && outgoing->to && outgoing->to->host[0] != '.'){
451 *fcc = get_fcc_based_on_to(outgoing->to);
453 else if(fcc && outgoing->newsgroups){
454 char *newsgroups_returned = NULL;
455 int rv;
457 rv = news_grouper(outgoing->newsgroups, &newsgroups_returned, errmsg, fcc, NULL);
458 if(rv != -1 &&
459 strcmp(outgoing->newsgroups, newsgroups_returned)){
460 fs_give((void **)&outgoing->newsgroups);
461 outgoing->newsgroups = newsgroups_returned;
463 else
464 fs_give((void **) &newsgroups_returned);
469 /*----------------------------------------------------------------------
470 Test the given address lists for equivalence
472 Args: x -- First address list for comparison
473 y -- Second address for comparison
475 ---*/
477 addr_lists_same(struct mail_address *x, struct mail_address *y)
479 for(x = first_addr(x), y = first_addr(y);
480 x && y;
481 x = first_addr(x->next), y = first_addr(y->next)){
482 if(!address_is_same(x, y))
483 return(0);
486 return(!x && !y); /* true if ran off both lists */
490 /*----------------------------------------------------------------------
491 Test the given address against those in the given envelope's to, cc
493 Args: addr -- address for comparison
494 env -- envelope to compare against
496 ---*/
498 addr_in_env(struct mail_address *addr, ENVELOPE *env)
500 ADDRESS *ap;
502 for(ap = env ? env->to : NULL; ap; ap = ap->next)
503 if(address_is_same(addr, ap))
504 return(1);
506 for(ap = env ? env->cc : NULL; ap; ap = ap->next)
507 if(address_is_same(addr, ap))
508 return(1);
510 return(0); /* not found! */
514 /*----------------------------------------------------------------------
515 Add missing personal info dest from src envelope
517 Args: dest -- envelope to add personal info to
518 src -- envelope to get personal info from
520 NOTE: This is just kind of a courtesy function. It's really not adding
521 anything needed to get the mail thru, but it is nice for the user
522 under some odd circumstances.
523 ---*/
524 void
525 reply_fish_personal(ENVELOPE *dest, ENVELOPE *src)
527 ADDRESS *da, *sa;
529 for(da = dest ? dest->to : NULL; da; da = da->next){
530 if(da->personal && !da->personal[0])
531 fs_give((void **)&da->personal);
533 for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next)
534 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
535 da->personal = cpystr(sa->personal);
537 for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
538 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
539 da->personal = cpystr(sa->personal);
542 for(da = dest ? dest->cc : NULL; da; da = da->next){
543 if(da->personal && !da->personal[0])
544 fs_give((void **)&da->personal);
546 for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next)
547 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
548 da->personal = cpystr(sa->personal);
550 for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
551 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
552 da->personal = cpystr(sa->personal);
557 /*----------------------------------------------------------------------
558 Given a header field and envelope, build "References: " header data
560 Args:
562 Returns:
563 ---*/
564 char *
565 reply_build_refs(ENVELOPE *env)
567 int len, id_len, first_ref_len = 0, foldslop;
568 char *p, *refs = NULL, *h = env->references;
569 char *first_ref = NULL, *tail_refs = NULL;
572 if(!(env->message_id && (id_len = strlen(env->message_id))))
573 return(NULL);
575 if(h){
577 * The length we have to work with doesn't seem to appear in any
578 * standards. Steve Jones says that in comp.news discussions he
579 * has seen 1024 as the longest length of a header value.
580 * In the inn news source we find MAXHEADERSIZE = 1024. It appears
581 * that is the maximum length of the header value, including
582 * newlines for folded lines (that is, the newlines are counted).
583 * We'll be conservative and figure every reference will take up a
584 * line of its own when we fold. We'll also count 2 for CRLF instead
585 * of just one for LF just to be safe. hubert 2001-jan
586 * J.B. Moreno <planb@newsreaders.com> says "The server limit is
587 * more commonly encountered at 999/1000 bytes [...]". So we'll
588 * back off to 999 instead of 1024.
590 #define MAXHEADERSIZE (999)
592 /* count the total number of potential folds, max of 2 bytes each */
593 for(foldslop = 2, p = h; (p = strstr(p+1, "> <")); )
594 foldslop += 2;
596 if((len=strlen(h)) + 1+id_len + foldslop >= MAXHEADERSIZE
597 && (p = strstr(h, "> <"))){
599 * If the references line is so long that we are going to have
600 * to delete some of the references, delete the 2nd, 3rd, ...
601 * We don't want to delete the first message in the thread.
603 p[1] = '\0';
604 first_ref = cpystr(h);
605 first_ref_len = strlen(first_ref)+1; /* len includes space */
606 p[1] = ' ';
607 tail_refs = p+2;
608 /* get rid of 2nd, 3rd, ... until it fits */
609 while((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
610 foldslop >= MAXHEADERSIZE
611 && (p = strstr(tail_refs, "> <"))){
612 tail_refs = p + 2;
613 foldslop -= 2;
617 * If the individual references are seriously long, somebody
618 * is messing with us and we don't care if it works right.
620 if((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
621 foldslop >= MAXHEADERSIZE){
622 first_ref_len = len = 0;
623 tail_refs = NULL;
624 if(first_ref)
625 fs_give((void **)&first_ref);
628 else
629 tail_refs = h;
631 refs = (char *)fs_get((first_ref_len + 1+id_len + len + 1) *
632 sizeof(char));
633 snprintf(refs, first_ref_len + 1+id_len + len + 1, "%s%s%s%s%s",
634 first_ref ? first_ref : "",
635 first_ref ? " " : "",
636 tail_refs ? tail_refs : "",
637 tail_refs ? " " : "",
638 env->message_id);
639 refs[first_ref_len + 1+id_len + len] = '\0';
642 if(!refs && id_len)
643 refs = cpystr(env->message_id);
645 if(first_ref)
646 fs_give((void **)&first_ref);
648 return(refs);
653 /*----------------------------------------------------------------------
654 Snoop for any Resent-* headers, and return an ADDRESS list
656 Args: stream --
657 msgno --
659 Returns: either NULL if no Resent-* or parsed ADDRESS struct list
660 ---*/
661 ADDRESS *
662 reply_resent(struct pine *pine_state, long int msgno, char *section)
664 #define RESENTFROM 0
665 #define RESENTTO 1
666 #define RESENTCC 2
667 ADDRESS *rlist = NULL, **a, **b;
668 char *hdrs, *values[RESENTCC+1];
669 int i;
670 static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL};
671 static char *fakedomain = "@";
673 if((hdrs = pine_fetchheader_lines(pine_state->mail_stream,
674 msgno, section, fields)) != NULL){
675 memset(values, 0, (RESENTCC+1) * sizeof(char *));
676 simple_header_parse(hdrs, fields, values);
677 for(i = RESENTFROM; i <= RESENTCC; i++)
678 rfc822_parse_adrlist(&rlist, values[i],
679 (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state))
680 ? fakedomain : pine_state->maildomain);
682 /* pare dup's ... */
683 for(a = &rlist; *a; a = &(*a)->next) /* compare every address */
684 for(b = &(*a)->next; *b; ) /* to the others */
685 if(address_is_same(*a, *b)){
686 ADDRESS *t = *b;
688 if(!(*a)->personal){ /* preserve personal name */
689 (*a)->personal = (*b)->personal;
690 (*b)->personal = NULL;
693 *b = t->next;
694 t->next = NULL;
695 mail_free_address(&t);
697 else
698 b = &(*b)->next;
701 if(hdrs)
702 fs_give((void **) &hdrs);
704 return(rlist);
708 /*----------------------------------------------------------------------
709 Format and return subject suitable for the reply command
711 Args: subject -- subject to build reply subject for
712 buf -- buffer to use for writing. If non supplied, alloc one.
713 buflen -- length of buf if supplied, else ignored
715 Returns: with either "Re:" prepended or not, if already there.
716 returned string is allocated.
717 ---*/
718 char *
719 reply_subject(char *subject, char *buf, size_t buflen)
721 size_t l = (subject && *subject) ? 4*strlen(subject) : 10;
722 char *tmp = fs_get(l + 1), *decoded, *p;
724 if(!buf){
725 buflen = l + 5;
726 buf = fs_get(buflen);
729 /* decode any 8bit into tmp buffer */
730 decoded = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp, l+1, subject);
732 buf[0] = '\0';
733 if(decoded /* already "re:" ? */
734 && (decoded[0] == 'R' || decoded[0] == 'r')
735 && (decoded[1] == 'E' || decoded[1] == 'e')){
737 if(decoded[2] == ':')
738 snprintf(buf, buflen, "%.*s", buflen-1, subject);
739 else if((decoded[2] == '[') && (p = strchr(decoded, ']'))){
740 p++;
741 while(*p && isspace((unsigned char)*p)) p++;
742 if(p[0] == ':')
743 snprintf(buf, buflen, "%.*s", buflen-1, subject);
747 if(!buf[0])
748 snprintf(buf, buflen, "Re: %.*s", buflen-1,
749 (subject && *subject) ? subject : "your mail");
751 buf[buflen-1] = '\0';
753 fs_give((void **) &tmp);
754 return(buf);
758 /*----------------------------------------------------------------------
759 return initials for the given personal name
761 Args: name -- Personal name to extract initials from
763 Returns: pointer to name overwritten with initials
764 ---*/
765 char *
766 reply_quote_initials(char *name)
768 char *s = name,
769 *w = name;
770 int i, j;
771 CBUF_S cbuf;
772 UCS ucs;
774 cbuf.cbuf[i = 0] = '\0';
775 cbuf.cbufp = cbuf.cbuf;
776 cbuf.cbufend = cbuf.cbuf;
778 /* while there are still characters to look at */
779 while(s && *s){
780 /* skip to next initial */
781 while(*s && (unsigned char) *s == ' ')
782 s++;
784 if(!utf8_to_ucs4_oneatatime((unsigned char) *s++ & 0xff, &cbuf, &ucs, NULL)){
785 i++;
786 continue;
789 /* copy cbuf */
790 for(j = 0; j <= i; j++) *w++ = cbuf.cbuf[j];
792 /* skip to end of this piece of name */
793 while(*s && (unsigned char) *s != ' ')
794 s++;
796 cbuf.cbuf[i = 0] = '\0';
797 cbuf.cbufp = cbuf.cbuf;
798 cbuf.cbufend = cbuf.cbuf;
801 if(w)
802 *w = '\0';
804 return(name);
808 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
809 * tokens being substituted for (only in the size of buf below).
811 #define MAX_SUBSTITUTION 6
812 #define MAX_PREFIX 63
813 static char *from_token = "_FROM_";
814 static char *nick_token = "_NICK_";
815 static char *init_token = "_INIT_";
817 /*----------------------------------------------------------------------
818 return a quoting string, "> " by default, for replied text
820 Args: env -- envelope of message being replied to
822 Returns: malloc'd array containing quoting string, freed by caller
823 ---*/
824 char *
825 reply_quote_str(ENVELOPE *env)
827 char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1];
829 strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1);
830 buf[sizeof(buf)-1] = '\0';
832 /* set up the prefix to quote included text */
833 if((p = strstr(buf, from_token)) != NULL){
834 repl = (env && env->from && env->from->mailbox) ? env->from->mailbox
835 : "";
836 strncpy(pbf, repl, sizeof(pbf)-1);
837 pbf[sizeof(pbf)-1] = '\0';;
838 rplstr(p, sizeof(buf)-(p-buf), strlen(from_token), pbf);
841 if((p = strstr(buf, nick_token)) != NULL){
842 repl = (env &&
843 env->from &&
844 env->from &&
845 get_nickname_from_addr(env->from, tmp_20k_buf, 1000))
846 ? tmp_20k_buf : "";
847 strncpy(pbf, repl, sizeof(pbf)-1);
848 pbf[sizeof(pbf)-1] = '\0';;
849 rplstr(p, sizeof(buf)-(p-buf), strlen(nick_token), pbf);
852 if((p = strstr(buf, init_token)) != NULL){
853 char *q = NULL;
854 char buftmp[MAILTMPLEN];
856 snprintf(buftmp, sizeof(buftmp), "%.200s",
857 (env && env->from && env->from->personal) ? env->from->personal : "");
858 buftmp[sizeof(buftmp)-1] = '\0';
860 repl = (env && env->from && env->from->personal)
861 ? reply_quote_initials(q = cpystr((char *)rfc1522_decode_to_utf8(
862 (unsigned char *)tmp_20k_buf,
863 SIZEOF_20KBUF, buftmp)))
864 : "";
866 istrncpy(pbf, repl, sizeof(pbf)-1);
867 pbf[sizeof(pbf)-1] = '\0';;
868 rplstr(p, sizeof(buf)-(p-buf), strlen(init_token), pbf);
869 if(q)
870 fs_give((void **)&q);
873 prefix = removing_quotes(cpystr(buf));
875 return(prefix);
879 reply_quote_str_contains_tokens(void)
881 return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] &&
882 (strstr(ps_global->VAR_REPLY_STRING, from_token) ||
883 strstr(ps_global->VAR_REPLY_STRING, nick_token) ||
884 strstr(ps_global->VAR_REPLY_STRING, init_token)));
888 /*----------------------------------------------------------------------
889 Build the body for the message number/part being replied to
891 Args:
893 Result: BODY structure suitable for sending
895 ----------------------------------------------------------------------*/
896 BODY *
897 reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
898 long int msgno, char *sect_prefix, void *msgtext, char *prefix,
899 int plustext, ACTION_S *role, int toplevel, REDRAFT_POS_S **redraft_pos)
901 char *p, *sig = NULL, *section, sect_buf[256];
902 BODY *body = NULL, *tmp_body = NULL;
903 PART *part;
904 gf_io_t pc;
905 int impl, template_len = 0, leave_cursor_at_top = 0, reply_raw_body = 0;
907 if(sect_prefix)
908 snprintf(section = sect_buf, sizeof(sect_buf), "%.*s.1", sizeof(sect_buf)-1, sect_prefix);
909 else
910 section = "1";
912 sect_buf[sizeof(sect_buf)-1] = '\0';
914 if(ps_global->full_header == 2
915 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
916 reply_raw_body = 1;
918 gf_set_so_writec(&pc, (STORE_S *) msgtext);
920 if(toplevel){
921 char *filtered;
923 impl = 0;
924 filtered = detoken(role, env, 0,
925 F_ON(F_SIG_AT_BOTTOM, ps_global) ? 1 : 0,
926 0, redraft_pos, &impl);
927 if(filtered){
928 if(*filtered){
929 so_puts((STORE_S *)msgtext, filtered);
930 if(impl == 1)
931 template_len = strlen(filtered);
932 else if(impl == 2)
933 leave_cursor_at_top++;
936 fs_give((void **)&filtered);
938 else
939 impl = 1;
941 else
942 impl = 1;
944 if(toplevel &&
945 (sig = reply_signature(role, env, redraft_pos, &impl)) &&
946 F_OFF(F_SIG_AT_BOTTOM, ps_global)){
949 * If CURSORPOS was set explicitly in sig_file, and there was a
950 * template file before that, we need to adjust the offset by the
951 * length of the template file. However, if the template had
952 * a set CURSORPOS in it then impl was 2 before getting to the
953 * signature, so offset wouldn't have been reset by the signature
954 * CURSORPOS and offset would already be correct. That case will
955 * be ok here because template_len will be 0 and adding it does
956 * nothing. If template
957 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
958 * by the CURSORPOS in the sig. In that case we have to adjust the
959 * offset. That's what the next line does. It adjusts it if
960 * template_len is nonzero and if CURSORPOS was set in sig_file.
962 if(impl == 2)
963 (*redraft_pos)->offset += template_len;
965 if(*sig)
966 so_puts((STORE_S *)msgtext, sig);
969 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
970 * is set, we won't have used it yet and want it to be non-NULL.
972 fs_give((void **)&sig);
976 * Only put cursor in sig if there is a cursorpos there but not
977 * one in the template, and sig-at-bottom.
979 if(!(sig && impl == 2 && !leave_cursor_at_top))
980 leave_cursor_at_top++;
982 if(plustext){
983 if(!orig_body
984 || orig_body->type == TYPETEXT
985 || reply_raw_body
986 || F_OFF(F_ATTACHMENTS_IN_REPLY, ps_global)){
987 char *charset = NULL;
989 /*------ Simple text-only message ----*/
990 body = mail_newbody();
991 body->type = TYPETEXT;
992 body->contents.text.data = msgtext;
993 reply_delimiter(env, role, pc);
994 if(F_ON(F_INCLUDE_HEADER, ps_global))
995 reply_forward_header(stream, msgno, sect_prefix,
996 env, pc, prefix);
998 if(!orig_body || reply_raw_body || reply_body_text(orig_body, &tmp_body)){
999 BODY *bodyp = NULL;
1001 bodyp = reply_raw_body ? NULL : tmp_body;
1004 * We set the charset in the outgoing message to the same
1005 * as the one in the message we're replying to unless it
1006 * is the unknown charset. We do that in order to attempt
1007 * to preserve the same charset in the reply if possible.
1008 * It may be safer to just set it to whatever we want instead
1009 * but then the receiver may not be able to read it.
1011 if(bodyp
1012 && (charset = parameter_val(bodyp->parameter, "charset"))
1013 && strucmp(charset, UNKNOWN_CHARSET))
1014 set_parameter(&body->parameter, "charset", charset);
1016 if(charset)
1017 fs_give((void **) &charset);
1019 get_body_part_text(stream, bodyp, msgno,
1020 bodyp ? (p = body_partno(stream, msgno, bodyp))
1021 : sect_prefix,
1022 0L, pc, prefix, NULL, GBPT_NONE);
1023 if(bodyp && p)
1024 fs_give((void **) &p);
1026 else{
1027 gf_puts(NEWLINE, pc);
1028 gf_puts(" [NON-Text Body part not included]", pc);
1029 gf_puts(NEWLINE, pc);
1032 else if(orig_body->type == TYPEMULTIPART){
1033 /*------ Message is Multipart ------*/
1034 if(orig_body->subtype
1035 && !strucmp(orig_body->subtype, "signed")
1036 && orig_body->nested.part){
1037 /* operate only on the signed part */
1038 body = reply_body(stream, env,
1039 &orig_body->nested.part->body,
1040 msgno, section, msgtext, prefix,
1041 plustext, role, 0, redraft_pos);
1043 else if(orig_body->subtype
1044 && !strucmp(orig_body->subtype, "alternative")){
1045 /* Set up the simple text reply */
1046 body = mail_newbody();
1047 body->type = TYPETEXT;
1048 body->contents.text.data = msgtext;
1050 if(reply_body_text(orig_body, &tmp_body)){
1051 reply_delimiter(env, role, pc);
1052 if(F_ON(F_INCLUDE_HEADER, ps_global))
1053 reply_forward_header(stream, msgno, sect_prefix,
1054 env, pc, prefix);
1056 get_body_part_text(stream, tmp_body, msgno,
1057 p = body_partno(stream,msgno,tmp_body),
1058 0L, pc, prefix, NULL, GBPT_NONE);
1059 if(p)
1060 fs_give((void **) &p);
1062 else
1063 q_status_message(SM_ORDER | SM_DING, 3, 3,
1064 "No suitable multipart text found for inclusion!");
1066 else{
1067 body = copy_body(NULL, orig_body);
1070 * whatever subtype it is, demote it
1071 * to plain old MIXED.
1073 if(body->subtype)
1074 fs_give((void **) &body->subtype);
1076 body->subtype = cpystr("Mixed");
1078 if(body->nested.part &&
1079 body->nested.part->body.type == TYPETEXT) {
1080 char *new_charset = NULL;
1082 /*---- First part of the message is text -----*/
1083 body->nested.part->body.contents.text.data = msgtext;
1084 if(body->nested.part->body.subtype &&
1085 strucmp(body->nested.part->body.subtype, "Plain")){
1086 fs_give((void **)&body->nested.part->body.subtype);
1087 body->nested.part->body.subtype = cpystr("Plain");
1089 reply_delimiter(env, role, pc);
1090 if(F_ON(F_INCLUDE_HEADER, ps_global))
1091 reply_forward_header(stream, msgno, sect_prefix,
1092 env, pc, prefix);
1094 if(!(get_body_part_text(stream,
1095 &orig_body->nested.part->body,
1096 msgno, section, 0L, pc, prefix,
1097 &new_charset, GBPT_NONE)
1098 && fetch_contents(stream, msgno, sect_prefix, body)))
1099 q_status_message(SM_ORDER | SM_DING, 3, 4,
1100 _("Error including all message parts"));
1101 else if(new_charset)
1102 set_parameter(&body->nested.part->body.parameter, "charset", new_charset);
1104 else if(orig_body->nested.part->body.type == TYPEMULTIPART
1105 && orig_body->nested.part->body.subtype
1106 && !strucmp(orig_body->nested.part->body.subtype,
1107 "alternative")
1108 && reply_body_text(&orig_body->nested.part->body,
1109 &tmp_body)){
1110 int partnum;
1112 reply_delimiter(env, role, pc);
1113 if(F_ON(F_INCLUDE_HEADER, ps_global))
1114 reply_forward_header(stream, msgno, sect_prefix,
1115 env, pc, prefix);
1117 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s",
1118 sizeof(sect_buf)/2-2,
1119 sect_prefix ? sect_prefix : "",
1120 sect_prefix ? "." : "",
1121 sizeof(sect_buf)/2-2,
1122 p = partno(orig_body, tmp_body));
1123 sect_buf[sizeof(sect_buf)-1] = '\0';
1124 fs_give((void **) &p);
1125 get_body_part_text(stream, tmp_body, msgno,
1126 sect_buf, 0L, pc, prefix,
1127 NULL, GBPT_NONE);
1129 part = body->nested.part->next;
1130 body->nested.part->next = NULL;
1131 mail_free_body_part(&body->nested.part);
1132 body->nested.part = mail_newbody_part();
1133 body->nested.part->body.type = TYPETEXT;
1134 body->nested.part->body.subtype = cpystr("Plain");
1135 body->nested.part->body.contents.text.data = msgtext;
1136 body->nested.part->next = part;
1138 for(partnum = 2; part != NULL; part = part->next){
1139 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d",
1140 sizeof(sect_buf)/2,
1141 sect_prefix ? sect_prefix : "",
1142 sect_prefix ? "." : "", partnum++);
1143 sect_buf[sizeof(sect_buf)-1] = '\0';
1145 if(!fetch_contents(stream, msgno,
1146 sect_buf, &part->body)){
1147 break;
1151 else {
1152 /*--- Fetch the original pieces ---*/
1153 if(!fetch_contents(stream, msgno, sect_prefix, body))
1154 q_status_message(SM_ORDER | SM_DING, 3, 4,
1155 _("Error including all message parts"));
1157 /*--- No text part, create a blank one ---*/
1158 part = mail_newbody_part();
1159 part->next = body->nested.part;
1160 body->nested.part = part;
1161 part->body.contents.text.data = msgtext;
1165 else{
1166 /*---- Single non-text message of some sort ----*/
1167 body = mail_newbody();
1168 body->type = TYPEMULTIPART;
1169 part = mail_newbody_part();
1170 body->nested.part = part;
1172 /*--- The first part, a blank text part to be edited ---*/
1173 part->body.type = TYPETEXT;
1174 part->body.contents.text.data = msgtext;
1176 /*--- The second part, what ever it is ---*/
1177 part->next = mail_newbody_part();
1178 part = part->next;
1179 part->body.id = generate_message_id();
1180 copy_body(&(part->body), orig_body);
1183 * the idea here is to fetch part into storage object
1185 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE,
1186 NULL,EDIT_ACCESS)) != NULL){
1187 if((p = pine_mail_fetch_body(stream, msgno, section,
1188 &part->body.size.bytes, NIL)) != NULL){
1189 so_nputs((STORE_S *)part->body.contents.text.data,
1190 p, part->body.size.bytes);
1192 else
1193 mail_free_body(&body);
1195 else
1196 mail_free_body(&body);
1199 else{
1200 /*--------- No text included --------*/
1201 body = mail_newbody();
1202 body->type = TYPETEXT;
1203 body->contents.text.data = msgtext;
1206 if(!leave_cursor_at_top){
1207 long cnt = 0L;
1208 unsigned char c;
1210 /* rewind and count chars to start of sig file */
1211 so_seek((STORE_S *)msgtext, 0L, 0);
1212 while(so_readc(&c, (STORE_S *)msgtext))
1213 cnt++;
1215 if(!*redraft_pos){
1216 *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
1217 memset((void *)*redraft_pos, 0,sizeof(**redraft_pos));
1218 (*redraft_pos)->hdrname = cpystr(":");
1222 * If explicit cursor positioning in sig file,
1223 * add offset to start of sig file plus offset into sig file.
1224 * Else, just offset to start of sig file.
1226 (*redraft_pos)->offset += cnt;
1229 if(sig){
1230 if(*sig)
1231 so_puts((STORE_S *)msgtext, sig);
1233 fs_give((void **)&sig);
1236 gf_clear_so_writec((STORE_S *) msgtext);
1238 return(body);
1243 * reply_part - first replyable multipart of a multipart.
1246 reply_body_text(struct mail_bodystruct *body, struct mail_bodystruct **new_body)
1248 if(body){
1249 switch(body->type){
1250 case TYPETEXT :
1251 *new_body = body;
1252 return(1);
1254 case TYPEMULTIPART :
1255 if(body->subtype && !strucmp(body->subtype, "alternative")){
1256 PART *part;
1257 int got_one = 0;
1259 if(ps_global->force_prefer_plain
1260 || (!ps_global->force_no_prefer_plain
1261 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
1262 for(part = body->nested.part; part; part = part->next)
1263 if((!part->body.type || part->body.type == TYPETEXT)
1264 && (!part->body.subtype
1265 || !strucmp(part->body.subtype, "plain"))){
1266 *new_body = &part->body;
1267 return(1);
1272 * Else choose last alternative among plain or html parts.
1273 * Perhaps we should really be using mime_show() to make this
1274 * potentially more general than just plain or html.
1276 for(part = body->nested.part; part; part = part->next){
1277 if((!part->body.type || part->body.type == TYPETEXT)
1278 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
1280 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
1281 got_one++;
1282 *new_body = &part->body;
1286 if(got_one)
1287 return(1);
1289 else if(body->nested.part)
1290 /* NOTE: we're only interested in "first" part of mixed */
1291 return(reply_body_text(&body->nested.part->body, new_body));
1293 break;
1295 default:
1296 break;
1300 return(0);
1304 char *
1305 reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl)
1307 char *sig;
1308 size_t l;
1310 sig = detoken(role, env,
1311 2, F_ON(F_SIG_AT_BOTTOM, ps_global) ? 0 : 1, 1,
1312 redraft_pos, impl);
1314 if(F_OFF(F_SIG_AT_BOTTOM, ps_global) && (!sig || !*sig)){
1315 if(sig)
1316 fs_give((void **)&sig);
1318 l = 2 * strlen(NEWLINE);
1319 sig = (char *)fs_get((l+1) * sizeof(char));
1320 strncpy(sig, NEWLINE, l);
1321 sig[l] = '\0';
1322 strncat(sig, NEWLINE, l+1-1-strlen(sig));
1323 sig[l] = '\0';
1324 return(sig);
1327 return(sig);
1332 * Buf is at least size maxlen+1
1334 void
1335 get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1337 ADDRESS *addr = NULL;
1338 ADDRESS *last_to = NULL;
1339 ADDRESS *first_addr = NULL, *second_addr = NULL;
1340 ADDRESS *third_addr = NULL, *fourth_addr = NULL;
1341 int cntaddr, l;
1342 size_t orig_maxlen;
1343 char *p;
1345 buf[0] = '\0';
1347 switch(type){
1348 case iFrom:
1349 addr = env ? env->from : NULL;
1350 break;
1352 case iTo:
1353 addr = env ? env->to : NULL;
1354 break;
1356 case iCc:
1357 addr = env ? env->cc : NULL;
1358 break;
1360 case iSender:
1361 addr = env ? env->sender : NULL;
1362 break;
1365 * Recips is To and Cc togeter. We hook the two adrlists together
1366 * temporarily.
1368 case iRecips:
1369 addr = env ? env->to : NULL;
1370 /* Find end of To list */
1371 for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
1374 /* Make the end of To list point to cc list */
1375 if(last_to)
1376 last_to->next = (env ? env->cc : NULL);
1378 break;
1381 * Initials.
1383 case iInit:
1384 if(env && env->from && env->from->personal){
1385 char *name, *initials = NULL;
1387 name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1388 SIZEOF_20KBUF, env->from->personal);
1389 if(name == env->from->personal){
1390 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
1391 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
1392 name = tmp_20k_buf;
1395 if(name && *name){
1396 initials = reply_quote_initials(name);
1397 iutf8ncpy(buf, initials, maxlen);
1398 buf[maxlen] = '\0';
1402 return;
1404 default:
1405 break;
1408 orig_maxlen = maxlen;
1410 first_addr = addr;
1411 /* skip over rest of c-client group addr */
1412 if(first_addr && first_addr->mailbox && !first_addr->host){
1413 for(second_addr = first_addr->next;
1414 second_addr && second_addr->host;
1415 second_addr = second_addr->next)
1418 if(second_addr && !second_addr->host)
1419 second_addr = second_addr->next;
1421 else if(!(first_addr && first_addr->host && first_addr->host[0] == '.'))
1422 second_addr = first_addr ? first_addr->next : NULL;
1424 if(second_addr && second_addr->mailbox && !second_addr->host){
1425 for(third_addr = second_addr->next;
1426 third_addr && third_addr->host;
1427 third_addr = third_addr->next)
1430 if(third_addr && !third_addr->host)
1431 third_addr = third_addr->next;
1433 else if(!(second_addr && second_addr->host && second_addr->host[0] == '.'))
1434 third_addr = second_addr ? second_addr->next : NULL;
1436 if(third_addr && third_addr->mailbox && !third_addr->host){
1437 for(fourth_addr = third_addr->next;
1438 fourth_addr && fourth_addr->host;
1439 fourth_addr = fourth_addr->next)
1442 if(fourth_addr && !fourth_addr->host)
1443 fourth_addr = fourth_addr->next;
1445 else if(!(third_addr && third_addr->host && third_addr->host[0] == '.'))
1446 fourth_addr = third_addr ? third_addr->next : NULL;
1448 /* Just attempting to make a nice display */
1449 if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
1450 (first_addr->mailbox && first_addr->mailbox[0]))){
1451 if(second_addr){
1452 if((second_addr->personal && second_addr->personal[0]) ||
1453 (second_addr->mailbox && second_addr->mailbox[0])){
1454 if(third_addr){
1455 if((third_addr->personal && third_addr->personal[0]) ||
1456 (third_addr->mailbox && third_addr->mailbox[0])){
1457 if(fourth_addr)
1458 cntaddr = 4;
1459 else
1460 cntaddr = 3;
1462 else
1463 cntaddr = -1;
1465 else
1466 cntaddr = 2;
1468 else
1469 cntaddr = -1;
1471 else
1472 cntaddr = 1;
1474 else
1475 cntaddr = -1;
1477 p = buf;
1478 if(cntaddr == 1)
1479 a_little_addr_string(first_addr, p, maxlen);
1480 else if(cntaddr == 2){
1481 a_little_addr_string(first_addr, p, maxlen);
1482 maxlen -= (l=strlen(p));
1483 p += l;
1484 if(maxlen > 7){
1485 strncpy(p, " and ", maxlen);
1486 maxlen -= 5;
1487 p += 5;
1488 a_little_addr_string(second_addr, p, maxlen);
1491 else if(cntaddr == 3){
1492 a_little_addr_string(first_addr, p, maxlen);
1493 maxlen -= (l=strlen(p));
1494 p += l;
1495 if(maxlen > 7){
1496 strncpy(p, ", ", maxlen);
1497 maxlen -= 2;
1498 p += 2;
1499 a_little_addr_string(second_addr, p, maxlen);
1500 maxlen -= (l=strlen(p));
1501 p += l;
1502 if(maxlen > 7){
1503 strncpy(p, ", and ", maxlen);
1504 maxlen -= 6;
1505 p += 6;
1506 a_little_addr_string(third_addr, p, maxlen);
1510 else if(cntaddr > 3){
1511 a_little_addr_string(first_addr, p, maxlen);
1512 maxlen -= (l=strlen(p));
1513 p += l;
1514 if(maxlen > 7){
1515 strncpy(p, ", ", maxlen);
1516 maxlen -= 2;
1517 p += 2;
1518 a_little_addr_string(second_addr, p, maxlen);
1519 maxlen -= (l=strlen(p));
1520 p += l;
1521 if(maxlen > 7){
1522 strncpy(p, ", ", maxlen);
1523 maxlen -= 2;
1524 p += 2;
1525 a_little_addr_string(third_addr, p, maxlen);
1526 maxlen -= (l=strlen(p));
1527 p += l;
1528 if(maxlen >= 12)
1529 strncpy(p, ", and others", maxlen);
1530 else if(maxlen >= 3)
1531 strncpy(p, "...", maxlen);
1535 else if(addr){
1536 char *a_string;
1538 a_string = addr_list_string(addr, NULL, 0);
1539 iutf8ncpy(buf, a_string, maxlen);
1541 fs_give((void **)&a_string);
1544 if(last_to)
1545 last_to->next = NULL;
1547 buf[orig_maxlen] = '\0';
1552 * Buf is at least size maxlen+1
1554 void
1555 get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1557 int cntnews = 0, orig_maxlen;
1558 char *news = NULL, *p, *q;
1560 switch(type){
1561 case iNews:
1562 case iNewsAndTo:
1563 case iToAndNews:
1564 case iNewsAndRecips:
1565 case iRecipsAndNews:
1566 news = env ? env->newsgroups : NULL;
1567 break;
1569 case iCurNews:
1570 if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream))
1571 news = ps_global->cur_folder;
1573 break;
1575 default:
1576 break;
1579 orig_maxlen = maxlen;
1581 if(news){
1582 q = news;
1583 while(isspace((unsigned char)*q))
1584 q++;
1586 if(*q)
1587 cntnews++;
1589 while((q = strindex(q, ',')) != NULL){
1590 q++;
1591 while(isspace((unsigned char)*q))
1592 q++;
1594 if(*q)
1595 cntnews++;
1596 else
1597 break;
1601 if(cntnews == 1){
1602 istrncpy(buf, news, maxlen);
1603 buf[maxlen] = '\0';
1604 removing_leading_and_trailing_white_space(buf);
1606 else if(cntnews == 2){
1607 p = buf;
1608 q = news;
1609 while(isspace((unsigned char)*q))
1610 q++;
1612 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1613 *p++ = *q++;
1614 maxlen--;
1617 if(maxlen > 7){
1618 strncpy(p, " and ", maxlen);
1619 p += 5;
1620 maxlen -= 5;
1623 while(isspace((unsigned char)*q) || *q == ',')
1624 q++;
1626 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1627 *p++ = *q++;
1628 maxlen--;
1631 *p = '\0';
1633 istrncpy(tmp_20k_buf, buf, 10000);
1634 strncpy(buf, tmp_20k_buf, orig_maxlen);
1636 else if(cntnews > 2){
1637 char b[100];
1639 p = buf;
1640 q = news;
1641 while(isspace((unsigned char)*q))
1642 q++;
1644 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1645 *p++ = *q++;
1646 maxlen--;
1649 *p = '\0';
1650 snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1);
1651 b[sizeof(b)-1] = '\0';
1652 if(maxlen >= strlen(b))
1653 strncpy(p, b, maxlen);
1654 else if(maxlen >= 3)
1655 strncpy(p, "...", maxlen);
1657 buf[orig_maxlen] = '\0';
1659 istrncpy(tmp_20k_buf, buf, 10000);
1660 tmp_20k_buf[10000-1] = '\0';
1661 strncpy(buf, tmp_20k_buf, orig_maxlen);
1664 buf[orig_maxlen] = '\0';
1669 * Buf is at least size maxlen+1
1671 char *
1672 get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen)
1674 char *space = NULL;
1675 IndexColType addrtype;
1677 buf[0] = '\0';
1679 switch(type){
1680 case iRDate: case iSDate: case iSTime:
1681 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
1682 case iSDateIso: case iSDateIsoS:
1683 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1684 case iSDateTime:
1685 case iSDateTimeIso: case iSDateTimeIsoS:
1686 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1687 case iSDateTime24:
1688 case iSDateTimeIso24: case iSDateTimeIsoS24:
1689 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1690 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
1691 case iDay: case iDayOrdinal: case iDay2Digit:
1692 case iMonAbb: case iMonLong: case iMon: case iMon2Digit:
1693 case iYear: case iYear2Digit:
1694 case iDate: case iLDate:
1695 case iTimezone: case iDayOfWeekAbb: case iDayOfWeek:
1696 case iPrefDate: case iPrefTime: case iPrefDateTime:
1697 if(env && env->date && env->date[0] && maxlen >= 20)
1698 date_str((char *) env->date, type, 1, buf, maxlen+1, 0);
1700 break;
1702 case iCurDate:
1703 case iCurDateIso:
1704 case iCurDateIsoS:
1705 case iCurTime24:
1706 case iCurTime12:
1707 case iCurDay:
1708 case iCurDay2Digit:
1709 case iCurDayOfWeek:
1710 case iCurDayOfWeekAbb:
1711 case iCurMon:
1712 case iCurMon2Digit:
1713 case iCurMonLong:
1714 case iCurMonAbb:
1715 case iCurYear:
1716 case iCurYear2Digit:
1717 case iCurPrefDate:
1718 case iCurPrefDateTime:
1719 case iCurPrefTime:
1720 case iLstMon:
1721 case iLstMon2Digit:
1722 case iLstMonLong:
1723 case iLstMonAbb:
1724 case iLstMonYear:
1725 case iLstMonYear2Digit:
1726 case iLstYear:
1727 case iLstYear2Digit:
1728 if(maxlen >= 20)
1729 date_str(NULL, type, 1, buf, maxlen+1, 0);
1731 break;
1733 case iFrom:
1734 case iTo:
1735 case iCc:
1736 case iSender:
1737 case iRecips:
1738 case iInit:
1739 get_addr_data(env, type, buf, maxlen);
1740 break;
1742 case iRoleNick:
1743 if(role && role->nick){
1744 strncpy(buf, role->nick, maxlen);
1745 buf[maxlen] = '\0';
1747 break;
1749 case iNewLine:
1750 if(maxlen >= strlen(NEWLINE)){
1751 strncpy(buf, NEWLINE, maxlen);
1752 buf[maxlen] = '\0';
1754 break;
1756 case iAddress:
1757 case iMailbox:
1758 if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
1759 strlen(env->from->mailbox) <= maxlen){
1760 strncpy(buf, env->from->mailbox, maxlen);
1761 buf[maxlen] = '\0';
1762 if(type == iAddress &&
1763 env->from->host &&
1764 env->from->host[0] &&
1765 env->from->host[0] != '.' &&
1766 strlen(buf) + strlen(env->from->host) + 1 <= maxlen){
1767 strncat(buf, "@", maxlen+1-1-strlen(buf));
1768 buf[maxlen] = '\0';
1769 strncat(buf, env->from->host, maxlen+1-1-strlen(buf));
1770 buf[maxlen] = '\0';
1774 break;
1776 case iNews:
1777 case iCurNews:
1778 get_news_data(env, type, buf, maxlen);
1779 break;
1781 case iToAndNews:
1782 case iNewsAndTo:
1783 case iRecipsAndNews:
1784 case iNewsAndRecips:
1785 if(type == iToAndNews || type == iNewsAndTo)
1786 addrtype = iTo;
1787 else
1788 addrtype = iRecips;
1790 if(env && env->newsgroups){
1791 space = (char *)fs_get((maxlen+1) * sizeof(char));
1792 get_news_data(env, type, space, maxlen);
1795 get_addr_data(env, addrtype, buf, maxlen);
1797 if(space && *space && *buf){
1798 if(strlen(space) + strlen(buf) + 5 > maxlen){
1799 if(strlen(space) > maxlen/2)
1800 get_news_data(env, type, space, maxlen - strlen(buf) - 5);
1801 else
1802 get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
1805 if(type == iToAndNews || type == iRecipsAndNews)
1806 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space);
1807 else
1808 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf);
1810 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1812 strncpy(buf, tmp_20k_buf, maxlen);
1813 buf[maxlen] = '\0';
1815 else if(space && *space){
1816 strncpy(buf, space, maxlen);
1817 buf[maxlen] = '\0';
1820 if(space)
1821 fs_give((void **)&space);
1823 break;
1825 case iSubject:
1826 if(env && env->subject){
1827 size_t n, len;
1828 unsigned char *p, *tmp = NULL;
1830 if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){
1831 len = n+1;
1832 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
1834 else{
1835 len = SIZEOF_20KBUF;
1836 p = (unsigned char *)tmp_20k_buf;
1839 istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen);
1841 buf[maxlen] = '\0';
1843 if(tmp)
1844 fs_give((void **)&tmp);
1847 break;
1849 case iMsgID:
1850 if(env && env->message_id){
1851 strncpy(buf, env->message_id, maxlen);
1852 buf[maxlen] = '\0';
1855 break;
1857 default:
1858 break;
1861 buf[maxlen] = '\0';
1862 return(buf);
1867 * reply_delimiter - output formatted reply delimiter for given envelope
1868 * with supplied character writing function.
1870 void
1871 reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc)
1873 #define MAX_DELIM 2000
1874 char buf[MAX_DELIM+1];
1875 char *p;
1876 char *filtered = NULL;
1877 int contains_newline_token = 0;
1880 if(!env)
1881 return;
1883 strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
1884 buf[MAX_DELIM] = '\0';
1885 /* preserve exact default behavior from before */
1886 if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
1887 struct date d;
1888 int include_date;
1890 parse_date((char *) env->date, &d);
1891 include_date = !(d.day == -1 || d.month == -1 || d.year == -1);
1892 if(include_date){
1893 gf_puts("On ", pc); /* All delims have... */
1894 if(d.wkday != -1){ /* "On day, date month year" */
1895 gf_puts(day_abbrev(d.wkday), pc); /* in common */
1896 gf_puts(", ", pc);
1899 gf_puts(int2string(d.day), pc);
1900 (*pc)(' ');
1901 gf_puts(month_abbrev(d.month), pc);
1902 (*pc)(' ');
1903 gf_puts(int2string(d.year), pc);
1906 if(env->from
1907 && ((env->from->personal && env->from->personal[0])
1908 || (env->from->mailbox && env->from->mailbox[0]))){
1909 char buftmp[MAILTMPLEN];
1911 a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1);
1912 if(include_date)
1913 gf_puts(", ", pc);
1915 gf_puts(buftmp, pc);
1916 gf_puts(" wrote:", pc);
1918 else{
1919 if(include_date)
1920 gf_puts(", it was written", pc);
1921 else
1922 gf_puts("It was written", pc);
1926 else{
1928 * This is here for backwards compatibility. There didn't used
1929 * to be a _NEWLINE_ token. The user would enter text that should
1930 * all fit on one line and then that was followed by two newlines.
1931 * Also, truncation occurs if it is long.
1932 * Now, if _NEWLINE_ is not in the text, same thing still works
1933 * the same. However, if _NEWLINE_ is in there, then all bets are
1934 * off and the user is on his or her own. No automatic newlines
1935 * are added, only those that come from the tokens. No truncation
1936 * is done, the user is trusted to get it right. Newlines may be
1937 * embedded so that the leadin is multi-line.
1939 contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL);
1940 filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role,
1941 NULL, NULL);
1943 /* try to truncate if too long */
1944 if(!contains_newline_token && filtered && utf8_width(filtered) > 80){
1945 int ended_with_colon = 0;
1946 int ended_with_quote = 0;
1947 int ended_with_quote_colon = 0;
1948 int l;
1950 l = strlen(filtered);
1952 if(filtered[l-1] == ':'){
1953 ended_with_colon = ':';
1954 if(filtered[l-2] == QUOTE || filtered[l-2] == '\'')
1955 ended_with_quote_colon = filtered[l-2];
1957 else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'')
1958 ended_with_quote = filtered[l-1];
1960 /* try to find space to break at */
1961 for(p = &filtered[75]; p > &filtered[60] &&
1962 !isspace((unsigned char)*p); p--)
1965 if(!isspace((unsigned char)*p))
1966 p = &filtered[75];
1968 *p++ = '.';
1969 *p++ = '.';
1970 *p++ = '.';
1971 if(ended_with_quote_colon){
1972 *p++ = ended_with_quote_colon;
1973 *p++ = ':';
1975 else if(ended_with_colon)
1976 *p++ = ended_with_colon;
1977 else if(ended_with_quote)
1978 *p++ = ended_with_quote;
1980 *p = '\0';
1983 if(filtered && *filtered)
1984 gf_puts(filtered, pc);
1987 /* and end with two newlines unless no leadin at all */
1988 if(!contains_newline_token){
1989 if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
1990 gf_puts(NEWLINE, pc);
1991 gf_puts(NEWLINE, pc);
1995 if(filtered)
1996 fs_give((void **)&filtered);
2000 void
2001 free_redraft_pos(REDRAFT_POS_S **redraft_pos)
2003 if(redraft_pos && *redraft_pos){
2004 if((*redraft_pos)->hdrname)
2005 fs_give((void **)&(*redraft_pos)->hdrname);
2007 fs_give((void **)redraft_pos);
2012 /*----------------------------------------------------------------------
2013 Build the body for the message number/part being forwarded as ATTACHMENT
2015 Args:
2017 Result: PARTS suitably attached to body
2019 ----------------------------------------------------------------------*/
2021 forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext)
2023 char *tmp_text;
2024 unsigned long len;
2025 BODY *b;
2027 *partp = mail_newbody_part();
2028 b = &(*partp)->body;
2029 b->type = TYPEMESSAGE;
2030 b->id = generate_message_id();
2031 b->description = cpystr("Forwarded Message");
2032 b->nested.msg = mail_newmsg();
2033 b->disposition.type = cpystr("inline");
2035 /*---- Package each message in a storage object ----*/
2036 if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
2037 && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
2038 && *tmp_text){
2039 so_puts((STORE_S *) b->contents.text.data, tmp_text);
2041 b->size.bytes = strlen(tmp_text);
2042 so_puts((STORE_S *) b->contents.text.data, "\015\012");
2043 if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){
2044 so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
2045 b->size.bytes += len;
2046 return(1);
2050 return(0);
2055 * forward_delimiter - return delimiter for forwarded text
2057 void
2058 forward_delimiter(gf_io_t pc)
2060 gf_puts(NEWLINE, pc);
2061 /* TRANSLATORS: When a message is forwarded by the user this is the
2062 text that shows where the forwarded part of the message begins. */
2063 gf_puts(_("---------- Forwarded message ----------"), pc);
2064 gf_puts(NEWLINE, pc);
2068 /*----------------------------------------------------------------------
2069 Wrapper for header formatting tool
2071 Args: stream --
2072 msgno --
2073 env --
2074 pc --
2075 prefix --
2077 Result: header suitable for reply/forward text written using "pc"
2079 ----------------------------------------------------------------------*/
2080 void
2081 reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env,
2082 gf_io_t pc, char *prefix)
2084 int rv;
2085 HEADER_S h;
2086 char **list, **new_list = NULL;
2088 list = ps_global->VAR_VIEW_HEADERS;
2091 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2092 * the user doesn't inadvertently forward the BCC header.
2094 if(list && list[0]){
2095 int i, cnt = 0;
2096 char **p;
2098 while(list[cnt++])
2101 p = new_list = (char **) fs_get((cnt+1) * sizeof(char *));
2103 for(i=0; list[i]; i++)
2104 if(strucmp(list[i], "bcc"))
2105 *p++ = cpystr(list[i]);
2107 *p = NULL;
2109 if(new_list && new_list[0])
2110 list = new_list;
2114 HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC);
2115 if((rv = format_header(stream, msgno, part, env, &h,
2116 prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){
2117 if(rv == 1)
2118 gf_puts(" [Error fetching message header data]", pc);
2120 else
2121 gf_puts(NEWLINE, pc); /* write header delimiter */
2123 if(new_list)
2124 free_list_array(&new_list);
2128 /*----------------------------------------------------------------------
2129 Build the subject for the message number being forwarded
2131 Args: pine_state -- The usual pine structure
2132 msgno -- The message number to build subject for
2134 Result: malloc'd string containing new subject or NULL on error
2136 ----------------------------------------------------------------------*/
2137 char *
2138 forward_subject(ENVELOPE *env, int flags)
2140 size_t l;
2141 char *p, buftmp[MAILTMPLEN];
2143 if(!env)
2144 return(NULL);
2146 dprint((9, "checking subject: \"%s\"\n",
2147 env->subject ? env->subject : "NULL"));
2149 if(env->subject && env->subject[0]){ /* add (fwd)? */
2150 snprintf(buftmp, sizeof(buftmp), "%s", env->subject);
2151 buftmp[sizeof(buftmp)-1] = '\0';
2152 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2153 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
2154 SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp)
2155 strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF);
2157 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2159 removing_trailing_white_space(tmp_20k_buf);
2160 if((l = strlen(tmp_20k_buf)) < 1000 &&
2161 (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){
2162 snprintf(tmp_20k_buf+2000, SIZEOF_20KBUF-2000, "%s (fwd)", tmp_20k_buf);
2163 tmp_20k_buf[SIZEOF_20KBUF-2000-1] = '\0';
2164 strncpy(tmp_20k_buf, tmp_20k_buf+2000, SIZEOF_20KBUF);
2165 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2169 * HACK: composer can't handle embedded double quotes in attachment
2170 * comments so we substitute two single quotes.
2172 if(flags & FS_CONVERT_QUOTES)
2173 while((p = strchr(tmp_20k_buf, QUOTE)) != NULL)
2174 (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''");
2176 return(cpystr(tmp_20k_buf));
2180 return(cpystr("Forwarded mail...."));
2184 /*----------------------------------------------------------------------
2185 Build the body for the message number/part being forwarded
2187 Args:
2189 Result: BODY structure suitable for sending
2191 ----------------------------------------------------------------------*/
2192 BODY *
2193 forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
2194 long int msgno, char *sect_prefix, void *msgtext, int flags)
2196 BODY *body = NULL, *text_body, *tmp_body;
2197 PART *part;
2198 gf_io_t pc;
2199 char *tmp_text, *section, sect_buf[256];
2200 int forward_raw_body = 0;
2203 * Check to see if messages got expunged out from underneath us. This
2204 * could have happened during the prompt to the user asking whether to
2205 * include the message as an attachment. Either the message is gone or
2206 * it might be at a different sequence number. We'd better bail.
2208 if(ps_global->full_header == 2
2209 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
2210 forward_raw_body = 1;
2211 if(sp_expunge_count(stream))
2212 return(NULL);
2214 if(sect_prefix && forward_raw_body == 0)
2215 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
2216 else if(sect_prefix && forward_raw_body)
2217 section = sect_prefix;
2218 else if(!sect_prefix && forward_raw_body)
2219 section = NULL;
2220 else
2221 section = "1";
2223 sect_buf[sizeof(sect_buf)-1] = '\0';
2225 gf_set_so_writec(&pc, (STORE_S *) msgtext);
2226 if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) {
2227 char *charset = NULL;
2229 /*---- Message has a single text part -----*/
2230 body = mail_newbody();
2231 body->type = TYPETEXT;
2232 body->contents.text.data = msgtext;
2233 if(orig_body
2234 && (charset = parameter_val(orig_body->parameter, "charset")))
2235 set_parameter(&body->parameter, "charset", charset);
2237 if(charset)
2238 fs_give((void **) &charset);
2240 if(!(flags & FWD_ANON)){
2241 forward_delimiter(pc);
2242 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
2245 if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body,
2246 msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){
2247 mail_free_body(&body);
2248 return(NULL);
2251 else if(orig_body->type == TYPEMULTIPART) {
2252 if(orig_body->subtype && !strucmp(orig_body->subtype, "signed")
2253 && orig_body->nested.part){
2254 /* only operate on the signed data (not the signature) */
2255 body = forward_body(stream, env, &orig_body->nested.part->body,
2256 msgno, sect_prefix, msgtext, flags);
2258 /*---- Message is multipart ----*/
2259 else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
2260 "alternative")
2261 && (body = forward_multi_alt(stream, env, orig_body, msgno,
2262 sect_prefix, msgtext,
2263 pc, flags)))){
2264 /*--- Copy the body and entire structure ---*/
2265 body = copy_body(NULL, orig_body);
2268 * whatever subtype it is, demote it
2269 * to plain old MIXED.
2271 if(body->subtype)
2272 fs_give((void **) &body->subtype);
2274 body->subtype = cpystr("Mixed");
2276 /*--- The text part of the message ---*/
2277 if(!body->nested.part){
2278 q_status_message(SM_ORDER | SM_DING, 3, 6,
2279 "Error referencing body part 1");
2280 mail_free_body(&body);
2282 else if(body->nested.part->body.type == TYPETEXT) {
2283 char *new_charset = NULL;
2285 /*--- The first part is text ----*/
2286 text_body = &body->nested.part->body;
2287 text_body->contents.text.data = msgtext;
2288 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
2289 /* this text is going to the composer, it should be Plain */
2290 fs_give((void **)&text_body->subtype);
2291 text_body->subtype = cpystr("PLAIN");
2293 if(!(flags & FWD_ANON)){
2294 forward_delimiter(pc);
2295 reply_forward_header(stream, msgno,
2296 sect_prefix, env, pc, "");
2299 if(!(get_body_part_text(stream, &orig_body->nested.part->body,
2300 msgno, section, 0L, pc,
2301 NULL, &new_charset, GBPT_NONE)
2302 && fetch_contents(stream, msgno, sect_prefix, body)))
2303 mail_free_body(&body);
2304 else if(new_charset)
2305 set_parameter(&text_body->parameter, "charset", new_charset);
2307 /* BUG: ? matter that we're not setting body.size.bytes */
2309 else if(body->nested.part->body.type == TYPEMULTIPART
2310 && body->nested.part->body.subtype
2311 && !strucmp(body->nested.part->body.subtype, "alternative")
2312 && (tmp_body = forward_multi_alt(stream, env,
2313 &body->nested.part->body,
2314 msgno, sect_prefix,
2315 msgtext, pc,
2316 flags | FWD_NESTED))){
2317 /* for the forward_multi_alt call above, we want to pass
2318 * sect_prefix instead of section so we can obtain the header.
2319 * Set the FWD_NESTED flag so we fetch the right body_part.
2321 int partnum;
2323 part = body->nested.part->next;
2324 body->nested.part->next = NULL;
2325 mail_free_body_part(&body->nested.part);
2326 body->nested.part = mail_newbody_part();
2327 body->nested.part->body = *tmp_body;
2328 body->nested.part->next = part;
2330 for(partnum = 2; part != NULL; part = part->next){
2331 snprintf(sect_buf, sizeof(sect_buf), "%s%s%d",
2332 sect_prefix ? sect_prefix : "",
2333 sect_prefix ? "." : "", partnum++);
2334 sect_buf[sizeof(sect_buf)-1] = '\0';
2336 if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
2337 mail_free_body(&body);
2338 break;
2342 else {
2343 if(fetch_contents(stream, msgno, sect_prefix, body)){
2344 /*--- Create a new blank text part ---*/
2345 part = mail_newbody_part();
2346 part->next = body->nested.part;
2347 body->nested.part = part;
2348 part->body.contents.text.data = msgtext;
2350 else
2351 mail_free_body(&body);
2355 else {
2356 /*---- A single part message, not of type text ----*/
2357 body = mail_newbody();
2358 body->type = TYPEMULTIPART;
2359 part = mail_newbody_part();
2360 body->nested.part = part;
2362 /*--- The first part, a blank text part to be edited ---*/
2363 part->body.type = TYPETEXT;
2364 part->body.contents.text.data = msgtext;
2366 /*--- The second part, what ever it is ---*/
2367 part->next = mail_newbody_part();
2368 part = part->next;
2369 part->body.id = generate_message_id();
2370 copy_body(&(part->body), orig_body);
2373 * the idea here is to fetch part into storage object
2375 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
2376 EDIT_ACCESS)) != NULL){
2377 if((tmp_text = pine_mail_fetch_body(stream, msgno, section,
2378 &part->body.size.bytes, NIL)) != NULL)
2379 so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
2380 part->body.size.bytes);
2381 else
2382 mail_free_body(&body);
2384 else
2385 mail_free_body(&body);
2388 gf_clear_so_writec((STORE_S *) msgtext);
2390 return(body);
2396 * bounce_msg_body - build body from specified message suitable
2397 * for sending as bounced message
2399 char *
2400 bounce_msg_body(MAILSTREAM *stream,
2401 long int rawno,
2402 char *part,
2403 char **to,
2404 char *subject,
2405 ENVELOPE **outgoingp,
2406 BODY **bodyp,
2407 int *seenp)
2409 char *h, *p, *errstr = NULL;
2410 int i;
2411 STORE_S *msgtext;
2412 gf_io_t pc;
2414 *outgoingp = mail_newenvelope();
2415 (*outgoingp)->message_id = generate_message_id();
2416 (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail....");
2419 * Fill in destination if we were given one. If so, note that we
2420 * call p_s_s() below such that it won't prompt...
2422 if(to && *to){
2423 static char *fakedomain = "@";
2424 char *tmp_a_string;
2426 /* rfc822_parse_adrlist feels free to destroy input so copy */
2427 tmp_a_string = cpystr(*to);
2428 rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string,
2429 (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
2430 ? fakedomain : ps_global->maildomain);
2431 fs_give((void **) &tmp_a_string);
2434 /* build remail'd header */
2435 if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){
2436 for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++)
2437 i++;
2439 /* allocate it */
2440 (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);
2443 * copy it, "X-"ing out transport headers bothersome to
2444 * software but potentially useful to the human recipient...
2446 p = (*outgoingp)->remail;
2447 bounce_mask_header(&p, h);
2449 if(*h == '\015' && *(h+1) == '\012'){
2450 *p++ = *h++; /* copy CR LF */
2451 *p++ = *h++;
2452 bounce_mask_header(&p, h);
2454 while((*p++ = *h++) != '\0');
2456 /* BUG: else complain? */
2458 /* NOT bound for the composer, so no need for PicoText */
2459 if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){
2460 mail_free_envelope(outgoingp);
2461 return(_("Error allocating message text"));
2464 /* mark object for special handling */
2465 so_attr(msgtext, "rawbody", "1");
2468 * Build a fake body description. It's ignored by pine_rfc822_header,
2469 * but we need to set it to something that makes set_mime_types
2470 * not sniff it and pine_rfc822_output_body not re-encode it.
2471 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2472 * problems unless something tries to access body_encodings[] using
2473 * it without proper precautions. We don't want to use ENCOTHER
2474 * cause that tells set_mime_types to sniff it, and we don't want to
2475 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2476 * it. When there's time, it'd be nice to clean this interaction
2477 * up...
2479 *bodyp = mail_newbody();
2480 (*bodyp)->type = TYPETEXT;
2481 (*bodyp)->encoding = ENCMAX + 1;
2482 (*bodyp)->subtype = cpystr("Plain");
2483 (*bodyp)->contents.text.data = (void *) msgtext;
2484 gf_set_so_writec(&pc, msgtext);
2486 if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){
2487 MESSAGECACHE *mc;
2489 if((mc = mail_elt(stream, rawno)) != NULL)
2490 *seenp = mc->seen;
2493 /* pass NULL body to force mail_fetchtext */
2494 if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE))
2495 errstr = _("Error fetching message contents. Can't Bounce message");
2497 gf_clear_so_writec(msgtext);
2499 return(errstr);
2504 /*----------------------------------------------------------------------
2505 Mask off any header entries we don't want xport software to see
2507 Args: d -- destination string pointer pointer
2508 s -- source string pointer pointer
2510 Postfix uses Delivered-To to detect loops.
2511 Received line counting is also used to detect loops in places.
2513 ----*/
2514 void
2515 bounce_mask_header(char **d, char *s)
2517 if(((*s == 'R' || *s == 'r')
2518 && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8)
2519 || !struncmp(s+1, "eturn-Path", 10)))
2520 || !struncmp(s, "Delivered-To:", 13)){
2521 *(*d)++ = 'X'; /* write mask */
2522 *(*d)++ = '-';
2527 /*----------------------------------------------------------------------
2528 Fetch and format text for forwarding
2530 Args: stream -- Mail stream to fetch text from
2531 body -- Body structure of message being forwarded
2532 msg_no -- Message number of text for forward
2533 part_no -- Part number of text to forward
2534 partial -- If this is > 0 a partial fetch will be done and it will
2535 be done using FT_PEEK so the message will remain unseen.
2536 pc -- Function to write to
2537 prefix -- Prefix for each line
2538 ret_charset -- If we translate to another charset return that
2539 new charset here
2541 Returns: true if OK, false if problem occured while filtering
2543 If the text is richtext, it will be converted to plain text, since there's
2544 no rich text editing capabilities in Pine (yet).
2546 It's up to calling routines to plug in signature appropriately
2548 As with all internal text, NVT end-of-line conventions are observed.
2549 DOESN'T sanity check the prefix given!!!
2550 ----*/
2552 get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body,
2553 long int msg_no, char *part_no, long partial, gf_io_t pc,
2554 char *prefix, char **ret_charset, unsigned flags)
2556 int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0;
2557 FILTLIST_S filters[12];
2558 long len;
2559 char *err, *charset, *prefix_p = NULL;
2560 int filtcnt = 0;
2561 char *free_this = NULL;
2562 DELQ_S dq;
2564 memset(filters, 0, sizeof(filters));
2565 if(ret_charset)
2566 *ret_charset = NULL;
2568 if(!pc_is_picotext(pc))
2569 we_cancel = busy_cue(NULL, NULL, 1);
2571 /* if null body, we must be talking to a non-IMAP2bis server.
2572 * No MIME parsing provided, so we just grab the message text...
2574 if(body == NULL){
2575 char *text, *decode_error;
2576 gf_io_t gc;
2577 SourceType src = CharStar;
2578 int rv = 0;
2580 (void) pine_mail_fetchstructure(stream, msg_no, NULL);
2582 if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){
2583 gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0);
2585 gf_filter_init(); /* no filters needed */
2586 if(prefix)
2587 gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
2588 if((decode_error = gf_pipe(gc, pc)) != NULL){
2589 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s",
2590 NEWLINE, NEWLINE,
2591 decode_error, NEWLINE);
2592 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2593 gf_puts(tmp_20k_buf, pc);
2594 rv++;
2597 else{
2598 gf_puts(NEWLINE, pc);
2599 gf_puts(_(" [ERROR fetching text of message]"), pc);
2600 gf_puts(NEWLINE, pc);
2601 gf_puts(NEWLINE, pc);
2602 rv++;
2605 if(we_cancel)
2606 cancel_busy_cue(-1);
2608 return(rv == 0);
2611 charset = parameter_val(body->parameter, "charset");
2613 if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
2614 if(ret_charset)
2615 *ret_charset = "UTF-8";
2619 * just use detach, but add an auxiliary filter to insert prefix,
2620 * and, perhaps, digest richtext
2622 if(ps_global->full_header != 2
2623 && !ps_global->postpone_no_flow
2624 && (!body->subtype || !strucmp(body->subtype, "plain"))){
2625 char *parmval;
2627 flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
2628 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
2629 && (!prefix || (strucmp(prefix,"> ") == 0)
2630 || strucmp(prefix, ">") == 0));
2631 if((parmval = parameter_val(body->parameter,
2632 "format")) != NULL){
2633 if(!strucmp(parmval, "flowed")){
2634 wrapflags |= GFW_FLOWED;
2636 fs_give((void **) &parmval);
2637 if((parmval = parameter_val(body->parameter, "delsp")) != NULL){
2638 if(!strucmp(parmval, "yes")){
2639 filters[filtcnt++].filter = gf_preflow;
2640 wrapflags |= GFW_DELSP;
2643 fs_give((void **) &parmval);
2647 * if there's no prefix we're forwarding text
2648 * otherwise it's reply text. unless the user's
2649 * tied our hands, alter the prefix to continue flowed
2650 * formatting...
2652 if(flow_res)
2653 wrapflags |= GFW_FLOW_RESULT;
2655 filters[filtcnt].filter = gf_wrap;
2657 * The 80 will cause longer lines than what is likely
2658 * set by composer_fillcol, so we'll have longer
2659 * quoted and forwarded lines than the lines we type.
2660 * We're fine with that since the alternative is the
2661 * possible wrapping of lines we shouldn't have, which
2662 * is mitigated by this higher 80 limit.
2664 * If we were to go back to using composer_fillcol,
2665 * the correct value is composer_fillcol + 1, pine
2666 * is off-by-one from pico.
2668 filters[filtcnt++].data = gf_wrap_filter_opt(
2669 MAX(MIN(ps_global->ttyo->screen_cols
2670 - (prefix ? strlen(prefix) : 0),
2671 80 - (prefix ? strlen(prefix) : 0)),
2672 30), /* doesn't have to be 30 */
2673 990, /* 998 is the SMTP limit */
2674 NULL, 0, wrapflags);
2679 * if not flowed, remove trailing whitespace to reduce
2680 * confusion, since we're sending out as flowed if we
2681 * can. At some future point, we might try
2682 * plugging in a user-option-controlled heuristic
2683 * flowing filter
2685 * We also want to fold "> " quotes so we get the
2686 * attributions correct.
2688 if(flow_res && prefix && !strucmp(prefix, "> "))
2689 *(prefix_p = prefix + 1) = '\0';
2691 if(!(wrapflags & GFW_FLOWED)
2692 && flow_res){
2693 filters[filtcnt].filter = gf_line_test;
2694 filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL);
2696 filters[filtcnt].filter = gf_line_test;
2697 filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL);
2700 else if(body->subtype){
2701 int plain_opt = 1;
2703 if(strucmp(body->subtype,"richtext") == 0){
2704 filters[filtcnt].filter = gf_rich2plain;
2705 filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
2707 else if(strucmp(body->subtype,"enriched") == 0){
2708 filters[filtcnt].filter = gf_enriched2plain;
2709 filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
2711 else if(strucmp(body->subtype,"html") == 0){
2712 if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){
2713 filters[filtcnt].filter = gf_html2plain;
2714 filters[filtcnt++].data = gf_html2plain_opt(NULL,
2715 ps_global->ttyo->screen_cols,
2716 non_messageview_margin(),
2717 NULL, NULL, GFHP_STRIPPED);
2722 if(prefix){
2723 if(ps_global->full_header != 2
2724 && (F_ON(F_ENABLE_SIGDASHES, ps_global)
2725 || F_ON(F_ENABLE_STRIP_SIGDASHES, ps_global))){
2726 dashdata = 0;
2727 filters[filtcnt].filter = gf_line_test;
2728 filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata);
2731 filters[filtcnt].filter = gf_prefix;
2732 filters[filtcnt++].data = gf_prefix_opt(prefix);
2734 if(wrapflags & GFW_FLOWED || flow_res){
2735 filters[filtcnt].filter = gf_line_test;
2736 filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL);
2740 if(flags & GBPT_DELQUOTES){
2741 memset(&dq, 0, sizeof(dq));
2742 dq.lines = Q_DEL_ALL;
2743 dq.is_flowed = 0;
2744 dq.indent_length = 0;
2745 dq.saved_line = &free_this;
2746 dq.handlesp = NULL;
2747 dq.do_color = 0;
2748 dq.delete_all = 1;
2750 filters[filtcnt].filter = gf_line_test;
2751 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
2754 err = detach(stream, msg_no, part_no, partial, &len, pc,
2755 filters[0].filter ? filters : NULL,
2756 ((flags & GBPT_PEEK) ? FT_PEEK : 0)
2757 | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0));
2759 if(free_this)
2760 fs_give((void **) &free_this);
2762 if(prefix_p)
2763 *prefix_p = ' ';
2765 if (err != (char *) NULL)
2766 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2767 q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
2768 err, (void *) msg_no);
2770 if(we_cancel)
2771 cancel_busy_cue(-1);
2773 return((int) len);
2778 quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local)
2780 char *p;
2782 if(*line == '>'){
2783 for(p = line; *p; p++){
2784 if(isspace((unsigned char) *p)){
2785 if(*(p+1) == '>')
2786 ins = gf_line_test_new_ins(ins, p, "", -1);
2788 else if(*p != '>')
2789 break;
2793 return(0);
2798 twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2800 char *p, *ws = NULL;
2802 for(p = line; *p; p++){
2803 /* don't strip trailing space on signature line */
2804 if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))
2805 break;
2807 if(isspace((unsigned char) *p)){
2808 if(!ws)
2809 ws = p;
2811 else
2812 ws = NULL;
2815 if(ws)
2816 ins = gf_line_test_new_ins(ins, ws, "", -(p - ws));
2818 return(0);
2822 post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local)
2824 char *p;
2826 for(p = line; *p; p++)
2827 if(*p != '>'){
2828 if(p != line && *p != ' ')
2829 ins = gf_line_test_new_ins(ins, p, " ", 1);
2831 break;
2834 return(0);
2839 sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2841 if(*((int *)local)
2842 || (*line == '-' && *(line+1) == '-'
2843 && *(line+2) == ' ' && !*(line+3))){
2844 *((int *) local) = 1;
2845 return(2); /* skip this line! */
2848 return(0);
2852 /*----------------------------------------------------------------------
2853 return the c-client reference name for the given end_body part
2854 ----*/
2855 char *
2856 body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body)
2858 BODY *body;
2860 (void) pine_mail_fetchstructure(stream, msgno, &body);
2861 return(partno(body, end_body));
2865 /*----------------------------------------------------------------------
2866 return the c-client reference name for the given end_body part
2867 ----*/
2868 char *
2869 partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body)
2871 PART *part;
2872 int num = 0;
2873 char tmp[64], *p = NULL;
2875 if(body && body->type == TYPEMULTIPART) {
2876 part = body->nested.part; /* first body part */
2878 do { /* for each part */
2879 num++;
2880 if(&part->body == end_body || (p = partno(&part->body, end_body))){
2881 snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "",
2882 sizeof(tmp)-10, (p) ? p : "");
2883 tmp[sizeof(tmp)-1] = '\0';
2884 if(p)
2885 fs_give((void **)&p);
2887 return(cpystr(tmp));
2889 } while ((part = part->next) != NULL); /* until done */
2891 return(NULL);
2893 else if(body && body->type == TYPEMESSAGE && body->subtype
2894 && !strucmp(body->subtype, "rfc822")){
2895 return(partno(body->nested.msg->body, end_body));
2898 return((body == end_body) ? cpystr("1") : NULL);
2902 /*----------------------------------------------------------------------
2903 Fill in the contents of each body part
2905 Args: stream -- Stream the message is on
2906 msgno -- Message number the body structure is for
2907 section -- body section associated with body pointer
2908 body -- Body pointer to fill in
2910 Result: 1 if all went OK, 0 if there was a problem
2912 This function copies the contents from an original message/body to
2913 a new message/body. It recurses down all multipart levels.
2915 If one or more part (but not all) can't be fetched, a status message
2916 will be queued.
2917 ----*/
2919 fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body)
2921 char *tp;
2922 int got_one = 0;
2924 if(!body->id)
2925 body->id = generate_message_id();
2927 if(body->type == TYPEMULTIPART){
2928 char subsection[256], *subp;
2929 int n, last_one = 10; /* remember worst case */
2930 PART *part = body->nested.part;
2932 if(!(part = body->nested.part))
2933 return(0);
2935 subp = subsection;
2936 if(section && *section){
2937 for(n = 0;
2938 n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++)
2941 *subp++ = '.';
2944 n = 1;
2945 do {
2946 snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++);
2947 subsection[sizeof(subsection)-1] = '\0';
2948 got_one = fetch_contents(stream, msgno, subsection, &part->body);
2949 last_one = MIN(last_one, got_one);
2951 while((part = part->next) != NULL);
2953 return(last_one);
2956 if(body->contents.text.data)
2957 return(1); /* already taken care of... */
2959 if(body->type == TYPEMESSAGE){
2960 if(body->subtype && strucmp(body->subtype,"external-body")){
2962 * the idea here is to fetch everything into storage objects
2964 body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
2965 EDIT_ACCESS);
2966 if(body->contents.text.data
2967 && (tp = pine_mail_fetch_body(stream, msgno, section,
2968 &body->size.bytes, NIL))){
2969 so_truncate((STORE_S *)body->contents.text.data,
2970 body->size.bytes + 2048);
2971 so_nputs((STORE_S *)body->contents.text.data, tp,
2972 body->size.bytes);
2973 got_one = 1;
2975 else
2976 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2977 _("Error fetching part %s"), section);
2978 } else {
2979 got_one = 1;
2981 } else {
2983 * the idea here is to fetch everything into storage objects
2984 * so, grab one, then fetch the body part
2986 body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
2987 if(body->contents.text.data
2988 && (tp=pine_mail_fetch_body(stream, msgno, section,
2989 &body->size.bytes, NIL))){
2990 so_truncate((STORE_S *)body->contents.text.data,
2991 body->size.bytes + 2048);
2992 so_nputs((STORE_S *)body->contents.text.data, tp,
2993 body->size.bytes);
2994 got_one = 1;
2996 else
2997 q_status_message1(SM_ORDER | SM_DING, 3, 3,
2998 _("Error fetching part %s"), section);
3001 return(got_one);
3005 /*----------------------------------------------------------------------
3006 Copy the body structure
3008 Args: new_body -- Pointer to already allocated body, or NULL, if none
3009 old_body -- The Body to copy
3012 This is traverses the body structure recursively copying all elements.
3013 The new_body parameter can be NULL in which case a new body is
3014 allocated. Alternatively it can point to an already allocated body
3015 structure. This is used when copying body parts since a PART includes a
3016 BODY. The contents fields are *not* filled in.
3017 ----*/
3019 BODY *
3020 copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body)
3022 if(old_body == NULL)
3023 return(NULL);
3025 if(new_body == NULL)
3026 new_body = mail_newbody();
3028 new_body->type = old_body->type;
3029 new_body->encoding = old_body->encoding;
3031 if(old_body->subtype)
3032 new_body->subtype = cpystr(old_body->subtype);
3034 new_body->parameter = copy_parameters(old_body->parameter);
3036 if(old_body->id)
3037 new_body->id = cpystr(old_body->id);
3039 if(old_body->description)
3040 new_body->description = cpystr(old_body->description);
3042 if(old_body->disposition.type)
3043 new_body->disposition.type = cpystr(old_body->disposition.type);
3045 new_body->disposition.parameter
3046 = copy_parameters(old_body->disposition.parameter);
3048 new_body->size = old_body->size;
3050 if(new_body->type == TYPEMESSAGE
3051 && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
3052 new_body->nested.msg = mail_newmsg();
3053 new_body->nested.msg->body
3054 = copy_body(NULL, old_body->nested.msg->body);
3056 else if(new_body->type == TYPEMULTIPART) {
3057 PART **new_partp, *old_part;
3059 new_partp = &new_body->nested.part;
3060 for(old_part = old_body->nested.part;
3061 old_part != NULL;
3062 old_part = old_part->next){
3063 *new_partp = mail_newbody_part();
3064 copy_body(&(*new_partp)->body, &old_part->body);
3065 new_partp = &(*new_partp)->next;
3069 return(new_body);
3073 /*----------------------------------------------------------------------
3074 Copy the MIME parameter list
3076 Allocates storage for new part, and returns pointer to new paramter
3077 list. If old_p is NULL, NULL is returned.
3078 ----*/
3079 PARAMETER *
3080 copy_parameters(PARAMETER *old_p)
3082 PARAMETER *new_p, *p1, *p2;
3084 if(old_p == NULL)
3085 return((PARAMETER *)NULL);
3087 new_p = p2 = NULL;
3088 for(p1 = old_p; p1 != NULL; p1 = p1->next){
3089 set_parameter(&p2, p1->attribute, p1->value);
3090 if(new_p == NULL)
3091 new_p = p2;
3094 return(new_p);
3098 /*----------------------------------------------------------------------
3099 Make a complete copy of an envelope and all it's fields
3101 Args: e -- the envelope to copy
3103 Result: returns the new envelope, or NULL, if the given envelope was NULL
3105 ----*/
3107 ENVELOPE *
3108 copy_envelope(register ENVELOPE *e)
3110 register ENVELOPE *e2;
3112 if(!e)
3113 return(NULL);
3115 e2 = mail_newenvelope();
3116 e2->remail = e->remail ? cpystr(e->remail) : NULL;
3117 e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
3118 e2->date = e->date ? (unsigned char *)cpystr((char *) e->date)
3119 : NULL;
3120 e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL;
3121 e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL;
3122 e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL;
3123 e2->subject = e->subject ? cpystr(e->subject) : NULL;
3124 e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL;
3125 e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL;
3126 e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL;
3127 e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL;
3128 e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL;
3129 e2->message_id = e->message_id ? cpystr(e->message_id) : NULL;
3130 e2->references = e->references ? cpystr(e->references) : NULL;
3131 e2->followup_to = e->followup_to ? cpystr(e->references) : NULL;
3132 return(e2);
3136 /*----------------------------------------------------------------------
3137 Generate the "In-reply-to" text from message header
3139 Args: message -- Envelope of original message
3141 Result: returns an alloc'd string or NULL if there is a problem
3142 ----*/
3143 char *
3144 reply_in_reply_to(ENVELOPE *env)
3146 return((env && env->message_id) ? cpystr(env->message_id) : NULL);
3150 /*----------------------------------------------------------------------
3151 Generate a unique message id string.
3153 Args: ps -- The usual pine structure
3155 Result: Alloc'd unique string is returned
3157 Uniqueness is gaurenteed by using the host name, process id, date to the
3158 second and a single unique character
3159 *----------------------------------------------------------------------*/
3160 char *
3161 generate_message_id(void)
3163 static short osec = 0, cnt = 0;
3164 char idbuf[128];
3165 char *id;
3166 time_t now;
3167 struct tm *now_x;
3168 char *hostpart = NULL;
3170 now = time((time_t *)0);
3171 now_x = localtime(&now);
3173 if(now_x->tm_sec == osec)
3174 cnt++;
3175 else{
3176 cnt = 0;
3177 osec = now_x->tm_sec;
3180 hostpart = F_ON(F_ROT13_MESSAGE_ID, ps_global)
3181 ? rot13(ps_global->hostname)
3182 : cpystr(ps_global->hostname);
3184 if(!hostpart)
3185 hostpart = cpystr("huh");
3187 snprintf(idbuf, sizeof(idbuf), "<alpine.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
3188 SYSTYPE, ALPINE_VERSION, (now_x->tm_year) % 100, now_x->tm_mon + 1,
3189 now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec,
3190 cnt, getpid(), hostpart);
3191 idbuf[sizeof(idbuf)-1] = '\0';
3193 id = cpystr(idbuf);
3195 if(hostpart)
3196 fs_give((void **) &hostpart);
3198 return(id);
3202 char *
3203 generate_user_agent(void)
3205 char buf[128];
3206 char rev[128];
3208 if(F_ON(F_QUELL_USERAGENT, ps_global))
3209 return(NULL);
3211 snprintf(buf, sizeof(buf),
3212 "%sAlpine %s (%s %s)",
3213 (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "",
3214 ALPINE_VERSION, SYSTYPE,
3215 get_alpine_revision_string(rev, sizeof(rev)));
3217 return(cpystr(buf));
3221 char *
3222 rot13(char *src)
3224 char byte, cap, *p, *ret = NULL;
3226 if(src && *src){
3227 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3228 p = ret;
3229 while((byte = *src++) != '\0'){
3230 cap = byte & 32;
3231 byte &= ~cap;
3232 *p++ = ((byte >= 'A') && (byte <= 'Z')
3233 ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap;
3236 *p = '\0';
3239 return(ret);
3243 /*----------------------------------------------------------------------
3244 Return the first true address pointer (modulo group syntax allowance)
3246 Args: addr -- Address list
3248 Result: First real address pointer, or NULL
3249 ----------------------------------------------------------------------*/
3250 ADDRESS *
3251 first_addr(struct mail_address *addr)
3253 while(addr && !addr->host)
3254 addr = addr->next;
3256 return(addr);
3260 /*----------------------------------------------------------------------
3261 lit -- this is the source
3262 prenewlines -- prefix the file contents with this many newlines
3263 postnewlines -- postfix the file contents with this many newlines
3264 is_sig -- this is a signature (not a template)
3265 decode_constants -- change C-style constants into their values
3266 ----*/
3267 char *
3268 get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants)
3270 char *sig = NULL;
3273 * Should make this smart enough not to do the copying and double
3274 * allocation of space.
3276 if(lit){
3277 char *tmplit = NULL, *p, *q, *d, save;
3278 size_t len;
3280 if(decode_constants){
3281 tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char));
3282 tmplit[0] = '\0';
3283 cstring_to_string(lit, tmplit);
3285 else
3286 tmplit = cpystr(lit);
3288 len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE);
3289 sig = (char *) fs_get((len+1) * sizeof(char));
3290 memset(sig, 0, len+1);
3291 d = sig;
3292 while(prenewlines--)
3293 sstrncpy(&d, NEWLINE, len-(d-sig));
3295 if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
3296 !sigdashes_are_present(tmplit)){
3297 sstrncpy(&d, SIGDASHES, len-(d-sig));
3298 sstrncpy(&d, NEWLINE, len-(d-sig));
3301 sig[len] = '\0';
3303 p = tmplit;
3304 while(*p){
3305 /* get a line */
3306 q = strpbrk(p, "\n\r");
3307 if(q){
3308 save = *q;
3309 *q = '\0';
3313 * Strip trailing space if we are doing a signature and
3314 * this line is not sigdashes.
3316 if(is_sig && strcmp(p, SIGDASHES))
3317 removing_trailing_white_space(p);
3319 while((d-sig) <= len && (*d = *p++) != '\0')
3320 d++;
3322 if(q){
3323 if((d-sig) <= len)
3324 *d++ = save;
3326 p = q+1;
3328 else
3329 break;
3332 while(postnewlines--)
3333 sstrncpy(&d, NEWLINE, len-(d-sig));
3335 sig[len] = '\0';
3337 if((d-sig) <= len)
3338 *d = '\0';
3340 if(tmplit)
3341 fs_give((void **) &tmplit);
3344 return(sig);
3349 sigdashes_are_present(char *sig)
3351 char *p;
3353 p = srchstr(sig, SIGDASHES);
3354 while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
3355 (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
3356 p = srchstr(p+1, SIGDASHES);
3358 return(p ? 1 : 0);
3362 /*----------------------------------------------------------------------
3363 Acquire the pinerc defined signature file pathname
3365 ----*/
3366 char *
3367 signature_path(char *sname, char *sbuf, size_t len)
3369 *sbuf = '\0';
3370 if(sname && *sname){
3371 size_t spl = strlen(sname);
3372 if(IS_REMOTE(sname)){
3373 if(spl < len - 1)
3374 strncpy(sbuf, sname, len-1);
3376 else if(is_absolute_path(sname)){
3377 strncpy(sbuf, sname, len-1);
3378 sbuf[len-1] = '\0';
3379 fnexpand(sbuf, len);
3381 else if(ps_global->VAR_OPER_DIR){
3382 if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
3383 build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len);
3385 else{
3386 char *lc = last_cmpnt(ps_global->pinerc);
3388 sbuf[0] = '\0';
3389 if(lc != NULL){
3390 strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc));
3391 sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0';
3394 strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0));
3395 sbuf[len-1] = '\0';
3399 return(*sbuf ? sbuf : NULL);
3403 char *
3404 simple_read_remote_file(char *name, char *subtype)
3406 int try_cache;
3407 REMDATA_S *rd;
3408 char *file = NULL;
3411 dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?"));
3414 * We could parse the name here to find what type it is. So far we
3415 * only have type RemImap.
3417 rd = rd_create_remote(RemImap, name, subtype,
3418 NULL, _("Error: "), _("Can't fetch remote configuration."));
3419 if(!rd)
3420 goto bail_out;
3422 try_cache = rd_read_metadata(rd);
3424 if(rd->access == MaybeRorW){
3425 if(rd->read_status == 'R')
3426 rd->access = ReadOnly;
3427 else
3428 rd->access = ReadWrite;
3431 if(rd->access != NoExists){
3433 rd_check_remvalid(rd, 1L);
3436 * If the cached info says it is readonly but
3437 * it looks like it's been fixed now, change it to readwrite.
3439 if(rd->read_status == 'R'){
3441 * We go to this trouble since readonly sigfiles
3442 * are likely a mistake. They are usually supposed to be
3443 * readwrite so we open it and check if it's been fixed.
3445 rd_check_readonly_access(rd);
3446 if(rd->read_status == 'W'){
3447 rd->access = ReadWrite;
3448 rd->flags |= REM_OUTOFDATE;
3450 else
3451 rd->access = ReadOnly;
3454 if(rd->flags & REM_OUTOFDATE){
3455 if(rd_update_local(rd) != 0){
3457 dprint((1,
3458 "simple_read_remote_file: rd_update_local failed\n"));
3460 * Don't give up altogether. We still may be
3461 * able to use a cached copy.
3464 else{
3465 dprint((7,
3466 "%s: copied remote to local (%ld)\n",
3467 rd->rn ? rd->rn : "?", (long)rd->last_use));
3471 if(rd->access == ReadWrite)
3472 rd->flags |= DO_REMTRIM;
3475 /* If we couldn't get to remote folder, try using the cached copy */
3476 if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){
3477 if(try_cache){
3478 rd->access = ReadOnly;
3479 rd->flags |= USE_OLD_CACHE;
3480 q_status_message(SM_ORDER, 3, 4,
3481 "Can't contact remote server, using cached copy");
3482 dprint((2,
3483 "Can't open remote file %s, using local cached copy %s readonly\n",
3484 rd->rn ? rd->rn : "?",
3485 rd->lf ? rd->lf : "?"));
3487 else{
3488 rd->flags &= ~DO_REMTRIM;
3489 goto bail_out;
3493 file = read_file(rd->lf, READ_FROM_LOCALE);
3495 bail_out:
3496 if(rd)
3497 rd_close_remdata(&rd);
3499 return(file);
3503 /*----------------------------------------------------------------------
3504 Build the body for the multipart/alternative part
3506 Args:
3508 Result:
3510 ----------------------------------------------------------------------*/
3511 BODY *
3512 forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3513 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3515 BODY *body = NULL;
3516 PART *part = NULL, *bestpart = NULL;
3517 char tmp_buf[256];
3518 char *new_charset = NULL;
3519 int partnum, bestpartnum;
3521 if(ps_global->force_prefer_plain
3522 || (!ps_global->force_no_prefer_plain
3523 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
3524 for(part = orig_body->nested.part, partnum = 1;
3525 part;
3526 part = part->next, partnum++)
3527 if((!part->body.type || part->body.type == TYPETEXT)
3528 && (!part->body.subtype
3529 || !strucmp(part->body.subtype, "plain")))
3530 break;
3534 * Else choose last alternative among plain or html parts.
3535 * Perhaps we should really be using mime_show() to make this
3536 * potentially more general than just plain or html.
3538 if(!part){
3539 for(part = orig_body->nested.part, partnum = 1;
3540 part;
3541 part = part->next, partnum++){
3542 if((!part->body.type || part->body.type == TYPETEXT)
3543 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
3545 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
3546 bestpart = part;
3547 bestpartnum = partnum;
3551 part = bestpart;
3552 partnum = bestpartnum;
3556 * IF something's interesting insert it
3557 * AND forget the rest of the multipart
3559 if(part){
3560 body = mail_newbody();
3561 body->type = TYPETEXT;
3562 body->contents.text.data = msgtext;
3564 /* record character set, flowing, etc */
3565 body->parameter = copy_parameters(part->body.parameter);
3566 body->size.bytes = part->body.size.bytes;
3568 if(!(flags & FWD_ANON)){
3569 forward_delimiter(pc);
3570 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
3573 snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d",
3574 sizeof(tmp_buf)/2, sect_prefix ? sect_prefix : "",
3575 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3576 partnum);
3577 tmp_buf[sizeof(tmp_buf)-1] = '\0';
3578 get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc,
3579 NULL, &new_charset, GBPT_NONE);
3582 * get_body_part_text translated the data to a new charset.
3583 * We need to record that fact in body.
3585 if(new_charset)
3586 set_parameter(&body->parameter, "charset", new_charset);
3588 else
3589 q_status_message(SM_ORDER | SM_DING, 3, 3,
3590 "No suitable part found. Forwarding as attachment");
3592 return(body);
3596 void
3597 reply_append_addr(struct mail_address **dest, struct mail_address *src)
3599 for( ; *dest; dest = &(*dest)->next)
3602 *dest = src;