* new version 2.19.9993
[alpine.git] / pith / reply.c
blob0b3aaa62773aa8cfa55d1bd9fcbdbd8b0593a16a
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2014 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/reply.h"
21 #include "../pith/send.h"
22 #include "../pith/init.h"
23 #include "../pith/state.h"
24 #include "../pith/conf.h"
25 #include "../pith/remote.h"
26 #include "../pith/status.h"
27 #include "../pith/mailview.h"
28 #include "../pith/filter.h"
29 #include "../pith/newmail.h"
30 #include "../pith/bldaddr.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/mimedesc.h"
33 #include "../pith/detach.h"
34 #include "../pith/help.h"
35 #include "../pith/pipe.h"
36 #include "../pith/addrstring.h"
37 #include "../pith/news.h"
38 #include "../pith/util.h"
39 #include "../pith/pattern.h"
40 #include "../pith/detoken.h"
41 #include "../pith/stream.h"
42 #include "../pith/busy.h"
43 #include "../pith/readfile.h"
44 #include "../pith/text.h"
45 #include "../pith/list.h"
46 #include "../pith/ablookup.h"
47 #include "../pith/mailcmd.h"
48 #include "../pith/margin.h"
49 #include "../pith/smime.h"
53 * Internal prototypes
55 void bounce_mask_header(char **, char *);
58 int (*pith_opt_replyto_prompt)(void);
59 int (*pith_opt_reply_to_all_prompt)(int *);
63 * standard type of storage object used for body parts...
65 #define PART_SO_TYPE CharStar
68 char *(*pith_opt_user_agent_prefix)(void);
70 /* compare two subjects and return if they are the same.
71 We compare stripped subjects, that is, those that do
72 not have re/fwd. Before we compare the subjects, we
73 decode them in case they were encoded.
74 Return value: 0 - not the same subject, 1 - same subject.
77 int
78 same_subject(char *s, char *t)
80 int rv = 0;
81 int i, j, len;
82 char *s1, *s2; /* holds decoded subjects from s and t */
83 char *u, *v;
85 if (s == NULL || t == NULL)
86 return s == t ? 1 : 0;
88 i = strlen(s); j = strlen(t);
89 len = i < j ? j : i;
90 u = fs_get(6*len+1);
91 v = fs_get(6*len+1);
92 s1 = (char *) rfc1522_decode_to_utf8(u, 6*len + 1, s);
93 s2 = (char *) rfc1522_decode_to_utf8(v, 6*len + 1, t);
94 mail_strip_subject(s1, &u);
95 mail_strip_subject(s2, &v);
97 rv = !strucmp(u, v);
99 fs_give((void **) &u);
100 fs_give((void **) &v);
101 return rv;
105 * reply_harvest -
107 * Returns: 1 if addresses successfully copied
108 * 0 on user cancel or error
110 * Input flags:
111 * RSF_FORCE_REPLY_TO
112 * RSF_QUERY_REPLY_ALL
113 * RSF_FORCE_REPLY_ALL
115 * Output flags:
116 * RSF_FORCE_REPLY_ALL
120 reply_harvest(struct pine *ps, long int msgno, char *section, ENVELOPE *env,
121 struct mail_address **saved_from, struct mail_address **saved_to,
122 struct mail_address **saved_cc, struct mail_address **saved_resent,
123 int *flags)
125 ADDRESS *ap, *ap2, *rep_address;
126 int ret = 0, sniff_resent = 0;
127 char *rep_field;
130 * If Reply-To is same as From just treat it like it was From.
131 * Otherwise, always use the reply-to if we're replying to more
132 * than one msg or say ok to using it, even if it's us.
133 * If there's no reply-to or it's the same as the from, assume
134 * that the user doesn't want to reply to himself, unless there's
135 * nobody else.
137 if(env->reply_to && !addr_lists_same(env->reply_to, env->from)
138 && (F_ON(F_AUTO_REPLY_TO, ps_global)
139 || ((*flags) & RSF_FORCE_REPLY_TO)
140 || (pith_opt_replyto_prompt && (*pith_opt_replyto_prompt)() == 'y'))){
141 rep_field = "reply-to";
142 rep_address = env->reply_to;
144 else{
145 rep_field = "From";
146 rep_address = env->from;
149 ap = reply_cp_addr(ps, msgno, section, rep_field, *saved_from,
150 (ADDRESS *) NULL, rep_address, RCA_NOT_US);
152 if(ret == 'x') {
153 cmd_cancelled("Reply");
154 return(0);
157 reply_append_addr(saved_from, ap);
159 /*--------- check for other recipients ---------*/
160 if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){
162 if((ap = reply_cp_addr(ps, msgno, section, "To", *saved_to,
163 *saved_from, env->to, RCA_NOT_US)) != NULL)
164 reply_append_addr(saved_to, ap);
166 if((ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc,
167 *saved_from, env->cc, RCA_NOT_US)) != NULL)
168 reply_append_addr(saved_cc, ap);
171 * In these cases, we either need to look at the resent headers
172 * to include in the reply-to-all, or to decide whether or not
173 * we need to ask the reply-to-all question.
175 if(((*flags) & RSF_FORCE_REPLY_ALL)
176 || (((*flags) & RSF_QUERY_REPLY_ALL)
177 && ((!(*saved_from) && !(*saved_cc))
178 || (*saved_from && !(*saved_to) && !(*saved_cc))))){
180 sniff_resent++;
181 if((ap2 = reply_resent(ps, msgno, section)) != NULL){
183 * look for bogus addr entries and replace
185 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
186 *saved_from, ap2, RCA_NOT_US)) != NULL)
188 reply_append_addr(saved_resent, ap);
190 mail_free_address(&ap2);
195 * It makes sense to ask reply-to-all now.
197 if(((*flags) & RSF_QUERY_REPLY_ALL)
198 && ((*saved_from && (*saved_to || *saved_cc || *saved_resent))
199 || (*saved_cc || *saved_resent))){
200 *flags &= ~RSF_QUERY_REPLY_ALL;
201 if(pith_opt_reply_to_all_prompt
202 && (*pith_opt_reply_to_all_prompt)(flags) < 0){
203 cmd_cancelled("Reply");
204 return(0);
209 * If we just answered yes to the reply-to-all question and
210 * we still haven't collected the resent headers, do so now.
212 if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent
213 && (ap2 = reply_resent(ps, msgno, section))){
215 * look for bogus addr entries and replace
217 if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent,
218 *saved_from, ap2, RCA_NOT_US)) != NULL)
219 reply_append_addr(saved_resent, ap);
221 mail_free_address(&ap2);
225 return(1);
229 /*----------------------------------------------------------------------
230 Return a pointer to a copy of the given address list
231 filtering out those already in the "mask" lists and ourself.
233 Args: mask1 -- Don't copy if in this list
234 mask2 -- or if in this list
235 source -- List to be copied
236 us_too -- Don't filter out ourself.
237 flags -- RCA_NOT_US copy all addrs except for our own
238 RCA_ALL copy all addrs, including our own
239 RCA_ONLY_US copy only addrs that are our own
241 ---*/
242 ADDRESS *
243 reply_cp_addr(struct pine *ps, long int msgno, char *section, char *field,
244 struct mail_address *mask1, struct mail_address *mask2,
245 struct mail_address *source, int flags)
247 ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail;
249 /* can only choose one of these flags values */
250 assert(!((flags & RCA_ALL && flags & RCA_ONLY_US)
251 || (flags & RCA_ALL && flags & RCA_NOT_US)
252 || (flags & RCA_ONLY_US && flags & RCA_NOT_US)));
254 for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next)
255 if(tmp1->host && tmp1->host[0] == '.'){
256 char *h, *fields[2];
258 fields[0] = field;
259 fields[1] = NULL;
260 if((h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL,
261 msgno, section, fields)) != NULL){
262 char *p, fname[32];
263 int q;
265 q = strlen(h);
267 strncpy(fname, field, sizeof(fname)-2);
268 fname[sizeof(fname)-2] = '\0';
269 strncat(fname, ":", sizeof(fname)-strlen(fname)-1);
270 fname[sizeof(fname)-1] = '\0';
272 for(p = h; (p = strstr(p, fname)) != NULL; )
273 rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */
275 sqznewlines(h); /* blat out CR's & LF's */
276 for(p = h; (p = strchr(p, TAB)) != NULL; )
277 *p++ = ' '; /* turn TABs to whitespace */
279 if(*h){
280 long l;
281 size_t ll;
283 ret = (ADDRESS *) fs_get(sizeof(ADDRESS));
284 memset(ret, 0, sizeof(ADDRESS));
286 /* get rid of leading white space */
287 for(p = h; *p == SPACE; p++)
290 if(p != h){
291 memmove(h, p, l = strlen(p));
292 h[l] = '\0';
295 /* base64 armor plate the gunk to protect against
296 * c-client quoting in output.
298 p = (char *) rfc822_binary(h, strlen(h),
299 (unsigned long *) &l);
300 sqznewlines(p);
301 fs_give((void **) &h);
303 * Seems like the 4 ought to be a 2, but I'll leave it
304 * to be safe, in case something else adds 2 chars later.
306 ll = strlen(p) + 4;
307 ret->mailbox = (char *) fs_get(ll * sizeof(char));
308 snprintf(ret->mailbox, ll, "&%s", p);
309 ret->mailbox[ll-1] = '\0';
310 fs_give((void **) &p);
311 ret->host = cpystr(RAWFIELD);
315 return(ret);
318 ret_tail = &ret;
319 for(source = first_addr(source); source; source = source->next){
320 for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next)
321 if(address_is_same(source, tmp1)) /* it is in mask1, skip it */
322 break;
324 for(tmp2 = first_addr(mask2); !tmp1 && tmp2; tmp2 = tmp2->next)
325 if(address_is_same(source, tmp2)) /* it is in mask2, skip it */
326 break;
329 * If there's no match in masks and this address satisfies the
330 * flags requirement, copy it.
332 if(!tmp1 && !tmp2 /* no mask match */
333 && ((flags & RCA_ALL) /* including everybody */
334 || (flags & RCA_ONLY_US && address_is_us(source, ps))
335 || (flags & RCA_NOT_US && !address_is_us(source, ps)))){
336 tmp1 = source->next;
337 source->next = NULL; /* only copy one addr! */
338 *ret_tail = rfc822_cpy_adr(source);
339 ret_tail = &(*ret_tail)->next;
340 source->next = tmp1; /* restore rest of list */
344 return(ret);
348 ACTION_S *
349 set_role_from_msg(struct pine *ps, long int rflags, long int msgno, char *section)
351 ACTION_S *role = NULL;
352 PAT_S *pat = NULL;
353 SEARCHSET *ss = NULL;
354 PAT_STATE pstate;
356 if(!nonempty_patterns(rflags, &pstate))
357 return(role);
359 if(msgno > 0L){
360 ss = mail_newsearchset();
361 ss->first = ss->last = (unsigned long)msgno;
364 /* Go through the possible roles one at a time until we get a match. */
365 pat = first_pattern(&pstate);
367 /* calculate this message's score if needed */
368 if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES &&
369 get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF)
370 (void)calculate_some_scores(ps->mail_stream, ss, 0);
372 while(!role && pat){
373 if(match_pattern(pat->patgrp, ps->mail_stream, ss, section,
374 get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){
375 if(!pat->action || pat->action->bogus)
376 break;
378 role = pat->action;
380 else
381 pat = next_pattern(&pstate);
384 if(ss)
385 mail_free_searchset(&ss);
387 return(role);
392 * reply_seed - fill in reply header
395 void
396 reply_seed(struct pine *ps, ENVELOPE *outgoing, ENVELOPE *env,
397 struct mail_address *saved_from, struct mail_address *saved_to,
398 struct mail_address *saved_cc, struct mail_address *saved_resent,
399 char **fcc, int replytoall, char **errmsg)
401 ADDRESS **to_tail, **cc_tail;
403 to_tail = &outgoing->to;
404 cc_tail = &outgoing->cc;
406 if(saved_from){
407 /* Put Reply-To or From in To. */
408 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
409 (ADDRESS *) NULL, saved_from, RCA_ALL);
410 if(replytoall){
411 if(ps->preserve){
412 while(*to_tail)
413 to_tail = &(*to_tail)->next;
415 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
416 (ADDRESS *) NULL, saved_to, RCA_ALL);
418 while(*to_tail)
419 to_tail = &(*to_tail)->next;
421 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
422 outgoing->to, saved_resent, RCA_ALL);
424 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
425 outgoing->to, saved_cc, RCA_ALL);
427 else{ /* and the rest in cc */
428 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
429 outgoing->to, saved_to, RCA_ALL);
430 while(*cc_tail) /* stay on last address */
431 cc_tail = &(*cc_tail)->next;
433 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
434 outgoing->to, saved_cc, RCA_ALL);
435 while(*cc_tail)
436 cc_tail = &(*cc_tail)->next;
438 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
439 outgoing->to, saved_resent, RCA_ALL);
443 else if(saved_to){
444 /* No From (maybe from us), put To in To. */
445 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
446 (ADDRESS *)NULL, saved_to, RCA_ALL);
447 /* and the rest in cc */
448 if(replytoall){
449 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
450 outgoing->to, saved_cc, RCA_ALL);
451 while(*cc_tail)
452 cc_tail = &(*cc_tail)->next;
454 *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc,
455 outgoing->to, saved_resent, RCA_ALL);
458 else{
459 /* No From or To, put everything else in To if replytoall, */
460 if(replytoall){
461 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
462 (ADDRESS *) NULL, saved_cc, RCA_ALL);
463 while(*to_tail)
464 to_tail = &(*to_tail)->next;
466 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
467 (ADDRESS *) NULL, saved_resent, RCA_ALL);
469 /* else, reply to original From which must be us */
470 else{
472 * Put self in To if in original From.
474 if(!outgoing->newsgroups)
475 *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to,
476 (ADDRESS *) NULL, env->from, RCA_ALL);
480 /* add any missing personal data */
481 reply_fish_personal(outgoing, env);
483 /* get fcc */
484 if(fcc && outgoing->to && outgoing->to->host[0] != '.'){
485 *fcc = get_fcc_based_on_to(outgoing->to);
487 else if(fcc && outgoing->newsgroups){
488 char *newsgroups_returned = NULL;
489 int rv;
491 rv = news_grouper(outgoing->newsgroups, &newsgroups_returned, errmsg, fcc, NULL);
492 if(rv != -1 &&
493 strcmp(outgoing->newsgroups, newsgroups_returned)){
494 fs_give((void **)&outgoing->newsgroups);
495 outgoing->newsgroups = newsgroups_returned;
497 else
498 fs_give((void **) &newsgroups_returned);
503 /*----------------------------------------------------------------------
504 Test the given address lists for equivalence
506 Args: x -- First address list for comparison
507 y -- Second address for comparison
509 ---*/
511 addr_lists_same(struct mail_address *x, struct mail_address *y)
513 for(x = first_addr(x), y = first_addr(y);
514 x && y;
515 x = first_addr(x->next), y = first_addr(y->next)){
516 if(!address_is_same(x, y))
517 return(0);
520 return(!x && !y); /* true if ran off both lists */
524 /*----------------------------------------------------------------------
525 Test the given address against those in the given envelope's to, cc
527 Args: addr -- address for comparison
528 env -- envelope to compare against
530 ---*/
532 addr_in_env(struct mail_address *addr, ENVELOPE *env)
534 ADDRESS *ap;
536 for(ap = env ? env->to : NULL; ap; ap = ap->next)
537 if(address_is_same(addr, ap))
538 return(1);
540 for(ap = env ? env->cc : NULL; ap; ap = ap->next)
541 if(address_is_same(addr, ap))
542 return(1);
544 return(0); /* not found! */
548 /*----------------------------------------------------------------------
549 Add missing personal info dest from src envelope
551 Args: dest -- envelope to add personal info to
552 src -- envelope to get personal info from
554 NOTE: This is just kind of a courtesy function. It's really not adding
555 anything needed to get the mail thru, but it is nice for the user
556 under some odd circumstances.
557 ---*/
558 void
559 reply_fish_personal(ENVELOPE *dest, ENVELOPE *src)
561 ADDRESS *da, *sa;
563 for(da = dest ? dest->to : NULL; da; da = da->next){
564 if(da->personal && !da->personal[0])
565 fs_give((void **)&da->personal);
567 for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next)
568 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
569 da->personal = cpystr(sa->personal);
571 for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
572 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
573 da->personal = cpystr(sa->personal);
576 for(da = dest ? dest->cc : NULL; da; da = da->next){
577 if(da->personal && !da->personal[0])
578 fs_give((void **)&da->personal);
580 for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next)
581 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
582 da->personal = cpystr(sa->personal);
584 for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next)
585 if(address_is_same(da, sa) && sa->personal && sa->personal[0])
586 da->personal = cpystr(sa->personal);
591 /*----------------------------------------------------------------------
592 Given a header field and envelope, build "References: " header data
594 Args:
596 Returns:
597 ---*/
598 char *
599 reply_build_refs(ENVELOPE *env)
601 int len, id_len, first_ref_len = 0, foldslop;
602 char *p, *refs = NULL, *h = env->references;
603 char *first_ref = NULL, *tail_refs = NULL;
606 if(!(env->message_id && (id_len = strlen(env->message_id))))
607 return(NULL);
609 if(h){
611 * The length we have to work with doesn't seem to appear in any
612 * standards. Steve Jones says that in comp.news discussions he
613 * has seen 1024 as the longest length of a header value.
614 * In the inn news source we find MAXHEADERSIZE = 1024. It appears
615 * that is the maximum length of the header value, including
616 * newlines for folded lines (that is, the newlines are counted).
617 * We'll be conservative and figure every reference will take up a
618 * line of its own when we fold. We'll also count 2 for CRLF instead
619 * of just one for LF just to be safe. hubert 2001-jan
620 * J.B. Moreno <planb@newsreaders.com> says "The server limit is
621 * more commonly encountered at 999/1000 bytes [...]". So we'll
622 * back off to 999 instead of 1024.
624 #define MAXHEADERSIZE (999)
626 /* count the total number of potential folds, max of 2 bytes each */
627 for(foldslop = 2, p = h; (p = strstr(p+1, "> <")); )
628 foldslop += 2;
630 if((len=strlen(h)) + 1+id_len + foldslop >= MAXHEADERSIZE
631 && (p = strstr(h, "> <"))){
633 * If the references line is so long that we are going to have
634 * to delete some of the references, delete the 2nd, 3rd, ...
635 * We don't want to delete the first message in the thread.
637 p[1] = '\0';
638 first_ref = cpystr(h);
639 first_ref_len = strlen(first_ref)+1; /* len includes space */
640 p[1] = ' ';
641 tail_refs = p+2;
642 /* get rid of 2nd, 3rd, ... until it fits */
643 while((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
644 foldslop >= MAXHEADERSIZE
645 && (p = strstr(tail_refs, "> <"))){
646 tail_refs = p + 2;
647 foldslop -= 2;
651 * If the individual references are seriously long, somebody
652 * is messing with us and we don't care if it works right.
654 if((len=strlen(tail_refs)) + first_ref_len + 1+id_len +
655 foldslop >= MAXHEADERSIZE){
656 first_ref_len = len = 0;
657 tail_refs = NULL;
658 if(first_ref)
659 fs_give((void **)&first_ref);
662 else
663 tail_refs = h;
665 refs = (char *)fs_get((first_ref_len + 1+id_len + len + 1) *
666 sizeof(char));
667 snprintf(refs, first_ref_len + 1+id_len + len + 1, "%s%s%s%s%s",
668 first_ref ? first_ref : "",
669 first_ref ? " " : "",
670 tail_refs ? tail_refs : "",
671 tail_refs ? " " : "",
672 env->message_id);
673 refs[first_ref_len + 1+id_len + len] = '\0';
676 if(!refs && id_len)
677 refs = cpystr(env->message_id);
679 if(first_ref)
680 fs_give((void **)&first_ref);
682 return(refs);
687 /*----------------------------------------------------------------------
688 Snoop for any Resent-* headers, and return an ADDRESS list
690 Args: stream --
691 msgno --
693 Returns: either NULL if no Resent-* or parsed ADDRESS struct list
694 ---*/
695 ADDRESS *
696 reply_resent(struct pine *pine_state, long int msgno, char *section)
698 #define RESENTFROM 0
699 #define RESENTTO 1
700 #define RESENTCC 2
701 ADDRESS *rlist = NULL, **a, **b;
702 char *hdrs, *values[RESENTCC+1];
703 int i;
704 static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL};
705 static char *fakedomain = "@";
707 if((hdrs = pine_fetchheader_lines(pine_state->mail_stream,
708 msgno, section, fields)) != NULL){
709 memset(values, 0, (RESENTCC+1) * sizeof(char *));
710 simple_header_parse(hdrs, fields, values);
711 for(i = RESENTFROM; i <= RESENTCC; i++)
712 rfc822_parse_adrlist(&rlist, values[i],
713 (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state))
714 ? fakedomain : pine_state->maildomain);
716 /* pare dup's ... */
717 for(a = &rlist; *a; a = &(*a)->next) /* compare every address */
718 for(b = &(*a)->next; *b; ) /* to the others */
719 if(address_is_same(*a, *b)){
720 ADDRESS *t = *b;
722 if(!(*a)->personal){ /* preserve personal name */
723 (*a)->personal = (*b)->personal;
724 (*b)->personal = NULL;
727 *b = t->next;
728 t->next = NULL;
729 mail_free_address(&t);
731 else
732 b = &(*b)->next;
735 if(hdrs)
736 fs_give((void **) &hdrs);
738 return(rlist);
742 /*----------------------------------------------------------------------
743 Format and return subject suitable for the reply command
745 Args: subject -- subject to build reply subject for
746 buf -- buffer to use for writing. If non supplied, alloc one.
747 buflen -- length of buf if supplied, else ignored
749 Returns: with either "Re:" prepended or not, if already there.
750 returned string is allocated.
751 ---*/
752 char *
753 reply_subject(char *subject, char *buf, size_t buflen)
755 size_t l = (subject && *subject) ? 4*strlen(subject) : 10;
756 char *tmp = fs_get(l + 1), *decoded, *p;
758 if(!buf){
759 buflen = l + 5;
760 buf = fs_get(buflen);
763 /* decode any 8bit into tmp buffer */
764 decoded = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp, l+1, subject);
766 buf[0] = '\0';
767 if(decoded /* already "re:" ? */
768 && (decoded[0] == 'R' || decoded[0] == 'r')
769 && (decoded[1] == 'E' || decoded[1] == 'e')){
771 if(decoded[2] == ':')
772 snprintf(buf, buflen, "%.*s", buflen-1, subject);
773 else if((decoded[2] == '[') && (p = strchr(decoded, ']'))){
774 p++;
775 while(*p && isspace((unsigned char)*p)) p++;
776 if(p[0] == ':')
777 snprintf(buf, buflen, "%.*s", buflen-1, subject);
781 if(!buf[0])
782 snprintf(buf, buflen, "Re: %.*s", buflen-1,
783 (subject && *subject) ? subject : "your mail");
785 buf[buflen-1] = '\0';
787 fs_give((void **) &tmp);
788 return(buf);
792 /*----------------------------------------------------------------------
793 return initials for the given personal name
795 Args: name -- Personal name to extract initials from
797 Returns: pointer to name overwritten with initials
798 ---*/
799 char *
800 reply_quote_initials(char *name)
802 char *s = name,
803 *w = name;
804 int i, j;
805 CBUF_S cbuf;
806 UCS ucs;
808 cbuf.cbuf[i = 0] = '\0';
809 cbuf.cbufp = cbuf.cbuf;
810 cbuf.cbufend = cbuf.cbuf;
812 /* while there are still characters to look at */
813 while(s && *s){
814 /* skip to next initial */
815 while(*s && (unsigned char) *s == ' ')
816 s++;
818 if(!utf8_to_ucs4_oneatatime((unsigned char) *s++ & 0xff, &cbuf, &ucs, NULL)){
819 i++;
820 continue;
823 /* copy cbuf */
824 for(j = 0; j <= i; j++) *w++ = cbuf.cbuf[j];
826 /* skip to end of this piece of name */
827 while(*s && (unsigned char) *s != ' ')
828 s++;
830 cbuf.cbuf[i = 0] = '\0';
831 cbuf.cbufp = cbuf.cbuf;
832 cbuf.cbufend = cbuf.cbuf;
835 if(w)
836 *w = '\0';
838 return(name);
842 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
843 * tokens being substituted for (only in the size of buf below).
845 #define MAX_SUBSTITUTION 6
846 #define MAX_PREFIX 63
847 static char *from_token = "_FROM_";
848 static char *nick_token = "_NICK_";
849 static char *init_token = "_INIT_";
851 /*----------------------------------------------------------------------
852 return a quoting string, "> " by default, for replied text
854 Args: env -- envelope of message being replied to
856 Returns: malloc'd array containing quoting string, freed by caller
857 ---*/
858 char *
859 reply_quote_str(ENVELOPE *env)
861 char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1];
863 strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1);
864 buf[sizeof(buf)-1] = '\0';
866 /* set up the prefix to quote included text */
867 if((p = strstr(buf, from_token)) != NULL){
868 repl = (env && env->from && env->from->mailbox) ? env->from->mailbox
869 : "";
870 strncpy(pbf, repl, sizeof(pbf)-1);
871 pbf[sizeof(pbf)-1] = '\0';;
872 rplstr(p, sizeof(buf)-(p-buf), strlen(from_token), pbf);
875 if((p = strstr(buf, nick_token)) != NULL){
876 repl = (env &&
877 env->from &&
878 env->from &&
879 get_nickname_from_addr(env->from, tmp_20k_buf, 1000))
880 ? tmp_20k_buf : "";
881 strncpy(pbf, repl, sizeof(pbf)-1);
882 pbf[sizeof(pbf)-1] = '\0';;
883 rplstr(p, sizeof(buf)-(p-buf), strlen(nick_token), pbf);
886 if((p = strstr(buf, init_token)) != NULL){
887 char *q = NULL;
888 char buftmp[MAILTMPLEN];
890 snprintf(buftmp, sizeof(buftmp), "%.200s",
891 (env && env->from && env->from->personal) ? env->from->personal : "");
892 buftmp[sizeof(buftmp)-1] = '\0';
894 repl = (env && env->from && env->from->personal)
895 ? reply_quote_initials(q = cpystr((char *)rfc1522_decode_to_utf8(
896 (unsigned char *)tmp_20k_buf,
897 SIZEOF_20KBUF, buftmp)))
898 : "";
900 istrncpy(pbf, repl, sizeof(pbf)-1);
901 pbf[sizeof(pbf)-1] = '\0';;
902 rplstr(p, sizeof(buf)-(p-buf), strlen(init_token), pbf);
903 if(q)
904 fs_give((void **)&q);
907 prefix = removing_quotes(cpystr(buf));
909 return(prefix);
913 reply_quote_str_contains_tokens(void)
915 return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] &&
916 (strstr(ps_global->VAR_REPLY_STRING, from_token) ||
917 strstr(ps_global->VAR_REPLY_STRING, nick_token) ||
918 strstr(ps_global->VAR_REPLY_STRING, init_token)));
922 /*----------------------------------------------------------------------
923 Build the body for the message number/part being replied to
925 Args:
927 Result: BODY structure suitable for sending
929 ----------------------------------------------------------------------*/
930 BODY *
931 reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
932 long int msgno, char *sect_prefix, void *msgtext, char *prefix,
933 int plustext, ACTION_S *role, int toplevel, REDRAFT_POS_S **redraft_pos)
935 char *p, *sig = NULL, *section, sect_buf[256];
936 BODY *body = NULL, *tmp_body = NULL;
937 PART *part;
938 gf_io_t pc;
939 int impl, template_len = 0, leave_cursor_at_top = 0, reply_raw_body = 0;
941 if(sect_prefix)
942 snprintf(section = sect_buf, sizeof(sect_buf), "%.*s.1", sizeof(sect_buf)-1, sect_prefix);
943 else
944 section = "1";
946 sect_buf[sizeof(sect_buf)-1] = '\0';
948 if(ps_global->full_header == 2
949 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
950 reply_raw_body = 1;
952 gf_set_so_writec(&pc, (STORE_S *) msgtext);
954 if(toplevel){
955 char *filtered;
957 impl = 0;
958 filtered = detoken(role, env, 0,
959 F_ON(F_SIG_AT_BOTTOM, ps_global) ? 1 : 0,
960 0, redraft_pos, &impl);
961 if(filtered){
962 if(*filtered){
963 so_puts((STORE_S *)msgtext, filtered);
964 if(impl == 1)
965 template_len = strlen(filtered);
966 else if(impl == 2)
967 leave_cursor_at_top++;
970 fs_give((void **)&filtered);
972 else
973 impl = 1;
975 else
976 impl = 1;
978 if(toplevel &&
979 (sig = reply_signature(role, env, redraft_pos, &impl)) &&
980 F_OFF(F_SIG_AT_BOTTOM, ps_global)){
983 * If CURSORPOS was set explicitly in sig_file, and there was a
984 * template file before that, we need to adjust the offset by the
985 * length of the template file. However, if the template had
986 * a set CURSORPOS in it then impl was 2 before getting to the
987 * signature, so offset wouldn't have been reset by the signature
988 * CURSORPOS and offset would already be correct. That case will
989 * be ok here because template_len will be 0 and adding it does
990 * nothing. If template
991 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
992 * by the CURSORPOS in the sig. In that case we have to adjust the
993 * offset. That's what the next line does. It adjusts it if
994 * template_len is nonzero and if CURSORPOS was set in sig_file.
996 if(impl == 2)
997 (*redraft_pos)->offset += template_len;
999 if(*sig)
1000 so_puts((STORE_S *)msgtext, sig);
1003 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
1004 * is set, we won't have used it yet and want it to be non-NULL.
1006 fs_give((void **)&sig);
1010 * Only put cursor in sig if there is a cursorpos there but not
1011 * one in the template, and sig-at-bottom.
1013 if(!(sig && impl == 2 && !leave_cursor_at_top))
1014 leave_cursor_at_top++;
1016 if(plustext){
1017 if(!orig_body
1018 || orig_body->type == TYPETEXT
1019 || reply_raw_body
1020 || F_OFF(F_ATTACHMENTS_IN_REPLY, ps_global)){
1021 char *charset = NULL;
1023 /*------ Simple text-only message ----*/
1024 body = mail_newbody();
1025 body->type = TYPETEXT;
1026 body->contents.text.data = msgtext;
1027 reply_delimiter(env, role, pc);
1028 if(F_ON(F_INCLUDE_HEADER, ps_global))
1029 reply_forward_header(stream, msgno, sect_prefix,
1030 env, pc, prefix);
1032 if(!orig_body || reply_raw_body || reply_body_text(orig_body, &tmp_body)){
1033 BODY *bodyp = NULL;
1035 bodyp = reply_raw_body ? NULL : tmp_body;
1038 * We set the charset in the outgoing message to the same
1039 * as the one in the message we're replying to unless it
1040 * is the unknown charset. We do that in order to attempt
1041 * to preserve the same charset in the reply if possible.
1042 * It may be safer to just set it to whatever we want instead
1043 * but then the receiver may not be able to read it.
1045 if(bodyp
1046 && (charset = parameter_val(bodyp->parameter, "charset"))
1047 && strucmp(charset, UNKNOWN_CHARSET))
1048 set_parameter(&body->parameter, "charset", charset);
1050 if(charset)
1051 fs_give((void **) &charset);
1053 get_body_part_text(stream, bodyp, msgno,
1054 bodyp ? (p = body_partno(stream, msgno, bodyp))
1055 : sect_prefix,
1056 0L, pc, prefix, NULL, GBPT_NONE);
1057 if(bodyp && p)
1058 fs_give((void **) &p);
1060 else{
1061 gf_puts(NEWLINE, pc);
1062 gf_puts(" [NON-Text Body part not included]", pc);
1063 gf_puts(NEWLINE, pc);
1066 else if(orig_body->type == TYPEMULTIPART){
1067 /*------ Message is Multipart ------*/
1068 if(orig_body->subtype
1069 && !strucmp(orig_body->subtype, "signed")
1070 && orig_body->nested.part){
1071 /* operate only on the signed part */
1072 body = reply_body(stream, env,
1073 &orig_body->nested.part->body,
1074 msgno, section, msgtext, prefix,
1075 plustext, role, 0, redraft_pos);
1077 else if(orig_body->subtype
1078 && !strucmp(orig_body->subtype, "alternative")){
1079 /* Set up the simple text reply */
1080 body = mail_newbody();
1081 body->type = TYPETEXT;
1082 body->contents.text.data = msgtext;
1084 if(reply_body_text(orig_body, &tmp_body)){
1085 reply_delimiter(env, role, pc);
1086 if(F_ON(F_INCLUDE_HEADER, ps_global))
1087 reply_forward_header(stream, msgno, sect_prefix,
1088 env, pc, prefix);
1090 get_body_part_text(stream, tmp_body, msgno,
1091 p = body_partno(stream,msgno,tmp_body),
1092 0L, pc, prefix, NULL, GBPT_NONE);
1093 if(p)
1094 fs_give((void **) &p);
1096 else
1097 q_status_message(SM_ORDER | SM_DING, 3, 3,
1098 "No suitable multipart text found for inclusion!");
1100 else{
1101 body = copy_body(NULL, orig_body);
1104 * whatever subtype it is, demote it
1105 * to plain old MIXED.
1107 if(body->subtype)
1108 fs_give((void **) &body->subtype);
1110 body->subtype = cpystr("Mixed");
1112 if(body->nested.part &&
1113 body->nested.part->body.type == TYPETEXT) {
1114 char *new_charset = NULL;
1116 /*---- First part of the message is text -----*/
1117 body->nested.part->body.contents.text.data = msgtext;
1118 if(body->nested.part->body.subtype &&
1119 strucmp(body->nested.part->body.subtype, "Plain")){
1120 fs_give((void **)&body->nested.part->body.subtype);
1121 body->nested.part->body.subtype = cpystr("Plain");
1123 reply_delimiter(env, role, pc);
1124 if(F_ON(F_INCLUDE_HEADER, ps_global))
1125 reply_forward_header(stream, msgno, sect_prefix,
1126 env, pc, prefix);
1128 if(!(get_body_part_text(stream,
1129 &orig_body->nested.part->body,
1130 msgno, section, 0L, pc, prefix,
1131 &new_charset, GBPT_NONE)
1132 && fetch_contents(stream, msgno, sect_prefix, body)))
1133 q_status_message(SM_ORDER | SM_DING, 3, 4,
1134 _("Error including all message parts"));
1135 else if(new_charset)
1136 set_parameter(&body->nested.part->body.parameter, "charset", new_charset);
1138 else if(orig_body->nested.part->body.type == TYPEMULTIPART
1139 && orig_body->nested.part->body.subtype
1140 && !strucmp(orig_body->nested.part->body.subtype,
1141 "alternative")
1142 && reply_body_text(&orig_body->nested.part->body,
1143 &tmp_body)){
1144 int partnum;
1146 reply_delimiter(env, role, pc);
1147 if(F_ON(F_INCLUDE_HEADER, ps_global))
1148 reply_forward_header(stream, msgno, sect_prefix,
1149 env, pc, prefix);
1151 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s",
1152 sizeof(sect_buf)/2-2,
1153 sect_prefix ? sect_prefix : "",
1154 sect_prefix ? "." : "",
1155 sizeof(sect_buf)/2-2,
1156 p = partno(orig_body, tmp_body));
1157 sect_buf[sizeof(sect_buf)-1] = '\0';
1158 fs_give((void **) &p);
1159 get_body_part_text(stream, tmp_body, msgno,
1160 sect_buf, 0L, pc, prefix,
1161 NULL, GBPT_NONE);
1163 part = body->nested.part->next;
1164 body->nested.part->next = NULL;
1165 mail_free_body_part(&body->nested.part);
1166 body->nested.part = mail_newbody_part();
1167 body->nested.part->body.type = TYPETEXT;
1168 body->nested.part->body.subtype = cpystr("Plain");
1169 body->nested.part->body.contents.text.data = msgtext;
1170 body->nested.part->next = part;
1172 for(partnum = 2; part != NULL; part = part->next){
1173 snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d",
1174 sizeof(sect_buf)/2,
1175 sect_prefix ? sect_prefix : "",
1176 sect_prefix ? "." : "", partnum++);
1177 sect_buf[sizeof(sect_buf)-1] = '\0';
1179 if(!fetch_contents(stream, msgno,
1180 sect_buf, &part->body)){
1181 break;
1185 else {
1186 /*--- Fetch the original pieces ---*/
1187 if(!fetch_contents(stream, msgno, sect_prefix, body))
1188 q_status_message(SM_ORDER | SM_DING, 3, 4,
1189 _("Error including all message parts"));
1191 /*--- No text part, create a blank one ---*/
1192 part = mail_newbody_part();
1193 part->next = body->nested.part;
1194 body->nested.part = part;
1195 part->body.contents.text.data = msgtext;
1199 else{
1200 /*---- Single non-text message of some sort ----*/
1201 body = mail_newbody();
1202 body->type = TYPEMULTIPART;
1203 part = mail_newbody_part();
1204 body->nested.part = part;
1206 /*--- The first part, a blank text part to be edited ---*/
1207 part->body.type = TYPETEXT;
1208 part->body.contents.text.data = msgtext;
1210 /*--- The second part, what ever it is ---*/
1211 part->next = mail_newbody_part();
1212 part = part->next;
1213 part->body.id = generate_message_id();
1214 copy_body(&(part->body), orig_body);
1217 * the idea here is to fetch part into storage object
1219 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE,
1220 NULL,EDIT_ACCESS)) != NULL){
1221 if((p = pine_mail_fetch_body(stream, msgno, section,
1222 &part->body.size.bytes, NIL)) != NULL){
1223 so_nputs((STORE_S *)part->body.contents.text.data,
1224 p, part->body.size.bytes);
1226 else
1227 mail_free_body(&body);
1229 else
1230 mail_free_body(&body);
1233 else{
1234 /*--------- No text included --------*/
1235 body = mail_newbody();
1236 body->type = TYPETEXT;
1237 body->contents.text.data = msgtext;
1240 if(!leave_cursor_at_top){
1241 long cnt = 0L;
1242 unsigned char c;
1244 /* rewind and count chars to start of sig file */
1245 so_seek((STORE_S *)msgtext, 0L, 0);
1246 while(so_readc(&c, (STORE_S *)msgtext))
1247 cnt++;
1249 if(!*redraft_pos){
1250 *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
1251 memset((void *)*redraft_pos, 0,sizeof(**redraft_pos));
1252 (*redraft_pos)->hdrname = cpystr(":");
1256 * If explicit cursor positioning in sig file,
1257 * add offset to start of sig file plus offset into sig file.
1258 * Else, just offset to start of sig file.
1260 (*redraft_pos)->offset += cnt;
1263 if(sig){
1264 if(*sig)
1265 so_puts((STORE_S *)msgtext, sig);
1267 fs_give((void **)&sig);
1270 gf_clear_so_writec((STORE_S *) msgtext);
1272 return(body);
1277 * reply_part - first replyable multipart of a multipart.
1280 reply_body_text(struct mail_bodystruct *body, struct mail_bodystruct **new_body)
1282 if(body){
1283 switch(body->type){
1284 case TYPETEXT :
1285 *new_body = body;
1286 return(1);
1288 case TYPEMULTIPART :
1289 if(body->subtype && !strucmp(body->subtype, "alternative")){
1290 PART *part;
1291 int got_one = 0;
1293 if(ps_global->force_prefer_plain
1294 || (!ps_global->force_no_prefer_plain
1295 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
1296 for(part = body->nested.part; part; part = part->next)
1297 if((!part->body.type || part->body.type == TYPETEXT)
1298 && (!part->body.subtype
1299 || !strucmp(part->body.subtype, "plain"))){
1300 *new_body = &part->body;
1301 return(1);
1306 * Else choose last alternative among plain or html parts.
1307 * Perhaps we should really be using mime_show() to make this
1308 * potentially more general than just plain or html.
1310 for(part = body->nested.part; part; part = part->next){
1311 if((!part->body.type || part->body.type == TYPETEXT)
1312 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
1314 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
1315 got_one++;
1316 *new_body = &part->body;
1320 if(got_one)
1321 return(1);
1323 else if(body->nested.part)
1324 /* NOTE: we're only interested in "first" part of mixed */
1325 return(reply_body_text(&body->nested.part->body, new_body));
1327 break;
1329 default:
1330 break;
1334 return(0);
1338 char *
1339 reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl)
1341 char *sig;
1342 size_t l;
1344 sig = detoken(role, env,
1345 2, F_ON(F_SIG_AT_BOTTOM, ps_global) ? 0 : 1, 1,
1346 redraft_pos, impl);
1348 if(F_OFF(F_SIG_AT_BOTTOM, ps_global) && (!sig || !*sig)){
1349 if(sig)
1350 fs_give((void **)&sig);
1352 l = 2 * strlen(NEWLINE);
1353 sig = (char *)fs_get((l+1) * sizeof(char));
1354 strncpy(sig, NEWLINE, l);
1355 sig[l] = '\0';
1356 strncat(sig, NEWLINE, l+1-1-strlen(sig));
1357 sig[l] = '\0';
1358 return(sig);
1361 return(sig);
1366 * Buf is at least size maxlen+1
1368 void
1369 get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1371 ADDRESS *addr = NULL;
1372 ADDRESS *last_to = NULL;
1373 ADDRESS *first_addr = NULL, *second_addr = NULL;
1374 ADDRESS *third_addr = NULL, *fourth_addr = NULL;
1375 int cntaddr, l;
1376 size_t orig_maxlen;
1377 char *p;
1379 buf[0] = '\0';
1381 switch(type){
1382 case iFrom:
1383 addr = env ? env->from : NULL;
1384 break;
1386 case iTo:
1387 addr = env ? env->to : NULL;
1388 break;
1390 case iCc:
1391 addr = env ? env->cc : NULL;
1392 break;
1394 case iSender:
1395 addr = env ? env->sender : NULL;
1396 break;
1399 * Recips is To and Cc togeter. We hook the two adrlists together
1400 * temporarily.
1402 case iRecips:
1403 addr = env ? env->to : NULL;
1404 /* Find end of To list */
1405 for(last_to = addr; last_to && last_to->next; last_to = last_to->next)
1408 /* Make the end of To list point to cc list */
1409 if(last_to)
1410 last_to->next = (env ? env->cc : NULL);
1412 break;
1415 * Initials.
1417 case iInit:
1418 if(env && env->from && env->from->personal){
1419 char *name, *initials = NULL;
1421 name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
1422 SIZEOF_20KBUF, env->from->personal);
1423 if(name == env->from->personal){
1424 strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1);
1425 tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0';
1426 name = tmp_20k_buf;
1429 if(name && *name){
1430 initials = reply_quote_initials(name);
1431 iutf8ncpy(buf, initials, maxlen);
1432 buf[maxlen] = '\0';
1436 return;
1438 default:
1439 break;
1442 orig_maxlen = maxlen;
1444 first_addr = addr;
1445 /* skip over rest of c-client group addr */
1446 if(first_addr && first_addr->mailbox && !first_addr->host){
1447 for(second_addr = first_addr->next;
1448 second_addr && second_addr->host;
1449 second_addr = second_addr->next)
1452 if(second_addr && !second_addr->host)
1453 second_addr = second_addr->next;
1455 else if(!(first_addr && first_addr->host && first_addr->host[0] == '.'))
1456 second_addr = first_addr ? first_addr->next : NULL;
1458 if(second_addr && second_addr->mailbox && !second_addr->host){
1459 for(third_addr = second_addr->next;
1460 third_addr && third_addr->host;
1461 third_addr = third_addr->next)
1464 if(third_addr && !third_addr->host)
1465 third_addr = third_addr->next;
1467 else if(!(second_addr && second_addr->host && second_addr->host[0] == '.'))
1468 third_addr = second_addr ? second_addr->next : NULL;
1470 if(third_addr && third_addr->mailbox && !third_addr->host){
1471 for(fourth_addr = third_addr->next;
1472 fourth_addr && fourth_addr->host;
1473 fourth_addr = fourth_addr->next)
1476 if(fourth_addr && !fourth_addr->host)
1477 fourth_addr = fourth_addr->next;
1479 else if(!(third_addr && third_addr->host && third_addr->host[0] == '.'))
1480 fourth_addr = third_addr ? third_addr->next : NULL;
1482 /* Just attempting to make a nice display */
1483 if(first_addr && ((first_addr->personal && first_addr->personal[0]) ||
1484 (first_addr->mailbox && first_addr->mailbox[0]))){
1485 if(second_addr){
1486 if((second_addr->personal && second_addr->personal[0]) ||
1487 (second_addr->mailbox && second_addr->mailbox[0])){
1488 if(third_addr){
1489 if((third_addr->personal && third_addr->personal[0]) ||
1490 (third_addr->mailbox && third_addr->mailbox[0])){
1491 if(fourth_addr)
1492 cntaddr = 4;
1493 else
1494 cntaddr = 3;
1496 else
1497 cntaddr = -1;
1499 else
1500 cntaddr = 2;
1502 else
1503 cntaddr = -1;
1505 else
1506 cntaddr = 1;
1508 else
1509 cntaddr = -1;
1511 p = buf;
1512 if(cntaddr == 1)
1513 a_little_addr_string(first_addr, p, maxlen);
1514 else if(cntaddr == 2){
1515 a_little_addr_string(first_addr, p, maxlen);
1516 maxlen -= (l=strlen(p));
1517 p += l;
1518 if(maxlen > 7){
1519 strncpy(p, " and ", maxlen);
1520 maxlen -= 5;
1521 p += 5;
1522 a_little_addr_string(second_addr, p, maxlen);
1525 else if(cntaddr == 3){
1526 a_little_addr_string(first_addr, p, maxlen);
1527 maxlen -= (l=strlen(p));
1528 p += l;
1529 if(maxlen > 7){
1530 strncpy(p, ", ", maxlen);
1531 maxlen -= 2;
1532 p += 2;
1533 a_little_addr_string(second_addr, p, maxlen);
1534 maxlen -= (l=strlen(p));
1535 p += l;
1536 if(maxlen > 7){
1537 strncpy(p, ", and ", maxlen);
1538 maxlen -= 6;
1539 p += 6;
1540 a_little_addr_string(third_addr, p, maxlen);
1544 else if(cntaddr > 3){
1545 a_little_addr_string(first_addr, p, maxlen);
1546 maxlen -= (l=strlen(p));
1547 p += l;
1548 if(maxlen > 7){
1549 strncpy(p, ", ", maxlen);
1550 maxlen -= 2;
1551 p += 2;
1552 a_little_addr_string(second_addr, p, maxlen);
1553 maxlen -= (l=strlen(p));
1554 p += l;
1555 if(maxlen > 7){
1556 strncpy(p, ", ", maxlen);
1557 maxlen -= 2;
1558 p += 2;
1559 a_little_addr_string(third_addr, p, maxlen);
1560 maxlen -= (l=strlen(p));
1561 p += l;
1562 if(maxlen >= 12)
1563 strncpy(p, ", and others", maxlen);
1564 else if(maxlen >= 3)
1565 strncpy(p, "...", maxlen);
1569 else if(addr){
1570 char *a_string;
1572 a_string = addr_list_string(addr, NULL, 0);
1573 iutf8ncpy(buf, a_string, maxlen);
1575 fs_give((void **)&a_string);
1578 if(last_to)
1579 last_to->next = NULL;
1581 buf[orig_maxlen] = '\0';
1586 * Buf is at least size maxlen+1
1588 void
1589 get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen)
1591 int cntnews = 0, orig_maxlen;
1592 char *news = NULL, *p, *q;
1594 switch(type){
1595 case iNews:
1596 case iNewsAndTo:
1597 case iToAndNews:
1598 case iNewsAndRecips:
1599 case iRecipsAndNews:
1600 news = env ? env->newsgroups : NULL;
1601 break;
1603 case iCurNews:
1604 if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream))
1605 news = ps_global->cur_folder;
1607 break;
1609 default:
1610 break;
1613 orig_maxlen = maxlen;
1615 if(news){
1616 q = news;
1617 while(isspace((unsigned char)*q))
1618 q++;
1620 if(*q)
1621 cntnews++;
1623 while((q = strindex(q, ',')) != NULL){
1624 q++;
1625 while(isspace((unsigned char)*q))
1626 q++;
1628 if(*q)
1629 cntnews++;
1630 else
1631 break;
1635 if(cntnews == 1){
1636 istrncpy(buf, news, maxlen);
1637 buf[maxlen] = '\0';
1638 removing_leading_and_trailing_white_space(buf);
1640 else if(cntnews == 2){
1641 p = buf;
1642 q = news;
1643 while(isspace((unsigned char)*q))
1644 q++;
1646 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1647 *p++ = *q++;
1648 maxlen--;
1651 if(maxlen > 7){
1652 strncpy(p, " and ", maxlen);
1653 p += 5;
1654 maxlen -= 5;
1657 while(isspace((unsigned char)*q) || *q == ',')
1658 q++;
1660 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1661 *p++ = *q++;
1662 maxlen--;
1665 *p = '\0';
1667 istrncpy(tmp_20k_buf, buf, 10000);
1668 strncpy(buf, tmp_20k_buf, orig_maxlen);
1670 else if(cntnews > 2){
1671 char b[100];
1673 p = buf;
1674 q = news;
1675 while(isspace((unsigned char)*q))
1676 q++;
1678 while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){
1679 *p++ = *q++;
1680 maxlen--;
1683 *p = '\0';
1684 snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1);
1685 b[sizeof(b)-1] = '\0';
1686 if(maxlen >= strlen(b))
1687 strncpy(p, b, maxlen);
1688 else if(maxlen >= 3)
1689 strncpy(p, "...", maxlen);
1691 buf[orig_maxlen] = '\0';
1693 istrncpy(tmp_20k_buf, buf, 10000);
1694 tmp_20k_buf[10000-1] = '\0';
1695 strncpy(buf, tmp_20k_buf, orig_maxlen);
1698 buf[orig_maxlen] = '\0';
1703 * Buf is at least size maxlen+1
1705 char *
1706 get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen)
1708 char *space = NULL;
1709 IndexColType addrtype;
1711 buf[0] = '\0';
1713 switch(type){
1714 case iRDate: case iSDate: case iSTime:
1715 case iS1Date: case iS2Date: case iS3Date: case iS4Date:
1716 case iSDateIso: case iSDateIsoS:
1717 case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4:
1718 case iSDateTime:
1719 case iSDateTimeIso: case iSDateTimeIsoS:
1720 case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4:
1721 case iSDateTime24:
1722 case iSDateTimeIso24: case iSDateTimeIsoS24:
1723 case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424:
1724 case iDateIso: case iDateIsoS: case iTime24: case iTime12:
1725 case iDay: case iDayOrdinal: case iDay2Digit:
1726 case iMonAbb: case iMonLong: case iMon: case iMon2Digit:
1727 case iYear: case iYear2Digit:
1728 case iDate: case iLDate:
1729 case iTimezone: case iDayOfWeekAbb: case iDayOfWeek:
1730 case iPrefDate: case iPrefTime: case iPrefDateTime:
1731 if(env && env->date && env->date[0] && maxlen >= 20)
1732 date_str((char *) env->date, type, 1, buf, maxlen+1, 0);
1734 break;
1736 case iCurDate:
1737 case iCurDateIso:
1738 case iCurDateIsoS:
1739 case iCurTime24:
1740 case iCurTime12:
1741 case iCurDay:
1742 case iCurDay2Digit:
1743 case iCurDayOfWeek:
1744 case iCurDayOfWeekAbb:
1745 case iCurMon:
1746 case iCurMon2Digit:
1747 case iCurMonLong:
1748 case iCurMonAbb:
1749 case iCurYear:
1750 case iCurYear2Digit:
1751 case iCurPrefDate:
1752 case iCurPrefDateTime:
1753 case iCurPrefTime:
1754 case iLstMon:
1755 case iLstMon2Digit:
1756 case iLstMonLong:
1757 case iLstMonAbb:
1758 case iLstMonYear:
1759 case iLstMonYear2Digit:
1760 case iLstYear:
1761 case iLstYear2Digit:
1762 if(maxlen >= 20)
1763 date_str(NULL, type, 1, buf, maxlen+1, 0);
1765 break;
1767 case iFrom:
1768 case iTo:
1769 case iCc:
1770 case iSender:
1771 case iRecips:
1772 case iInit:
1773 get_addr_data(env, type, buf, maxlen);
1774 break;
1776 case iRoleNick:
1777 if(role && role->nick){
1778 strncpy(buf, role->nick, maxlen);
1779 buf[maxlen] = '\0';
1781 break;
1783 case iNewLine:
1784 if(maxlen >= strlen(NEWLINE)){
1785 strncpy(buf, NEWLINE, maxlen);
1786 buf[maxlen] = '\0';
1788 break;
1790 case iAddress:
1791 case iMailbox:
1792 if(env && env->from && env->from->mailbox && env->from->mailbox[0] &&
1793 strlen(env->from->mailbox) <= maxlen){
1794 strncpy(buf, env->from->mailbox, maxlen);
1795 buf[maxlen] = '\0';
1796 if(type == iAddress &&
1797 env->from->host &&
1798 env->from->host[0] &&
1799 env->from->host[0] != '.' &&
1800 strlen(buf) + strlen(env->from->host) + 1 <= maxlen){
1801 strncat(buf, "@", maxlen+1-1-strlen(buf));
1802 buf[maxlen] = '\0';
1803 strncat(buf, env->from->host, maxlen+1-1-strlen(buf));
1804 buf[maxlen] = '\0';
1808 break;
1810 case iNews:
1811 case iCurNews:
1812 get_news_data(env, type, buf, maxlen);
1813 break;
1815 case iToAndNews:
1816 case iNewsAndTo:
1817 case iRecipsAndNews:
1818 case iNewsAndRecips:
1819 if(type == iToAndNews || type == iNewsAndTo)
1820 addrtype = iTo;
1821 else
1822 addrtype = iRecips;
1824 if(env && env->newsgroups){
1825 space = (char *)fs_get((maxlen+1) * sizeof(char));
1826 get_news_data(env, type, space, maxlen);
1829 get_addr_data(env, addrtype, buf, maxlen);
1831 if(space && *space && *buf){
1832 if(strlen(space) + strlen(buf) + 5 > maxlen){
1833 if(strlen(space) > maxlen/2)
1834 get_news_data(env, type, space, maxlen - strlen(buf) - 5);
1835 else
1836 get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5);
1839 if(type == iToAndNews || type == iRecipsAndNews)
1840 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space);
1841 else
1842 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf);
1844 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1846 strncpy(buf, tmp_20k_buf, maxlen);
1847 buf[maxlen] = '\0';
1849 else if(space && *space){
1850 strncpy(buf, space, maxlen);
1851 buf[maxlen] = '\0';
1854 if(space)
1855 fs_give((void **)&space);
1857 break;
1859 case iSubject:
1860 if(env && env->subject){
1861 size_t n, len;
1862 unsigned char *p, *tmp = NULL;
1864 if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){
1865 len = n+1;
1866 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
1868 else{
1869 len = SIZEOF_20KBUF;
1870 p = (unsigned char *)tmp_20k_buf;
1873 istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen);
1875 buf[maxlen] = '\0';
1877 if(tmp)
1878 fs_give((void **)&tmp);
1881 break;
1883 case iMsgID:
1884 if(env && env->message_id){
1885 strncpy(buf, env->message_id, maxlen);
1886 buf[maxlen] = '\0';
1889 break;
1891 default:
1892 break;
1895 buf[maxlen] = '\0';
1896 return(buf);
1901 * reply_delimiter - output formatted reply delimiter for given envelope
1902 * with supplied character writing function.
1904 void
1905 reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc)
1907 #define MAX_DELIM 2000
1908 char buf[MAX_DELIM+1];
1909 char *p;
1910 char *filtered = NULL;
1911 int contains_newline_token = 0;
1914 if(!env)
1915 return;
1917 strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM);
1918 buf[MAX_DELIM] = '\0';
1919 /* preserve exact default behavior from before */
1920 if(!strcmp(buf, DEFAULT_REPLY_INTRO)){
1921 struct date d;
1922 int include_date;
1924 parse_date((char *) env->date, &d);
1925 include_date = !(d.day == -1 || d.month == -1 || d.year == -1);
1926 if(include_date){
1927 gf_puts("On ", pc); /* All delims have... */
1928 if(d.wkday != -1){ /* "On day, date month year" */
1929 gf_puts(day_abbrev(d.wkday), pc); /* in common */
1930 gf_puts(", ", pc);
1933 gf_puts(int2string(d.day), pc);
1934 (*pc)(' ');
1935 gf_puts(month_abbrev(d.month), pc);
1936 (*pc)(' ');
1937 gf_puts(int2string(d.year), pc);
1940 if(env->from
1941 && ((env->from->personal && env->from->personal[0])
1942 || (env->from->mailbox && env->from->mailbox[0]))){
1943 char buftmp[MAILTMPLEN];
1945 a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1);
1946 if(include_date)
1947 gf_puts(", ", pc);
1949 gf_puts(buftmp, pc);
1950 gf_puts(" wrote:", pc);
1952 else{
1953 if(include_date)
1954 gf_puts(", it was written", pc);
1955 else
1956 gf_puts("It was written", pc);
1960 else{
1962 * This is here for backwards compatibility. There didn't used
1963 * to be a _NEWLINE_ token. The user would enter text that should
1964 * all fit on one line and then that was followed by two newlines.
1965 * Also, truncation occurs if it is long.
1966 * Now, if _NEWLINE_ is not in the text, same thing still works
1967 * the same. However, if _NEWLINE_ is in there, then all bets are
1968 * off and the user is on his or her own. No automatic newlines
1969 * are added, only those that come from the tokens. No truncation
1970 * is done, the user is trusted to get it right. Newlines may be
1971 * embedded so that the leadin is multi-line.
1973 contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL);
1974 filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role,
1975 NULL, NULL);
1977 /* try to truncate if too long */
1978 if(!contains_newline_token && filtered && utf8_width(filtered) > 80){
1979 int ended_with_colon = 0;
1980 int ended_with_quote = 0;
1981 int ended_with_quote_colon = 0;
1982 int l;
1984 l = strlen(filtered);
1986 if(filtered[l-1] == ':'){
1987 ended_with_colon = ':';
1988 if(filtered[l-2] == QUOTE || filtered[l-2] == '\'')
1989 ended_with_quote_colon = filtered[l-2];
1991 else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'')
1992 ended_with_quote = filtered[l-1];
1994 /* try to find space to break at */
1995 for(p = &filtered[75]; p > &filtered[60] &&
1996 !isspace((unsigned char)*p); p--)
1999 if(!isspace((unsigned char)*p))
2000 p = &filtered[75];
2002 *p++ = '.';
2003 *p++ = '.';
2004 *p++ = '.';
2005 if(ended_with_quote_colon){
2006 *p++ = ended_with_quote_colon;
2007 *p++ = ':';
2009 else if(ended_with_colon)
2010 *p++ = ended_with_colon;
2011 else if(ended_with_quote)
2012 *p++ = ended_with_quote;
2014 *p = '\0';
2017 if(filtered && *filtered)
2018 gf_puts(filtered, pc);
2021 /* and end with two newlines unless no leadin at all */
2022 if(!contains_newline_token){
2023 if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){
2024 gf_puts(NEWLINE, pc);
2025 gf_puts(NEWLINE, pc);
2029 if(filtered)
2030 fs_give((void **)&filtered);
2034 void
2035 free_redraft_pos(REDRAFT_POS_S **redraft_pos)
2037 if(redraft_pos && *redraft_pos){
2038 if((*redraft_pos)->hdrname)
2039 fs_give((void **)&(*redraft_pos)->hdrname);
2041 fs_give((void **)redraft_pos);
2046 /*----------------------------------------------------------------------
2047 Build the body for the message number/part being forwarded as ATTACHMENT
2049 Args:
2051 Result: PARTS suitably attached to body
2053 ----------------------------------------------------------------------*/
2055 forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext)
2057 char *tmp_text;
2058 unsigned long len;
2059 BODY *b;
2061 *partp = mail_newbody_part();
2062 b = &(*partp)->body;
2063 b->type = TYPEMESSAGE;
2064 b->id = generate_message_id();
2065 b->description = cpystr("Forwarded Message");
2066 b->nested.msg = mail_newmsg();
2067 b->disposition.type = cpystr("inline");
2069 /*---- Package each message in a storage object ----*/
2070 if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS))
2071 && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK))
2072 && *tmp_text){
2073 so_puts((STORE_S *) b->contents.text.data, tmp_text);
2075 b->size.bytes = strlen(tmp_text);
2076 so_puts((STORE_S *) b->contents.text.data, "\015\012");
2077 if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){
2078 so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len);
2079 b->size.bytes += len;
2080 return(1);
2084 return(0);
2089 * forward_delimiter - return delimiter for forwarded text
2091 void
2092 forward_delimiter(gf_io_t pc)
2094 gf_puts(NEWLINE, pc);
2095 /* TRANSLATORS: When a message is forwarded by the user this is the
2096 text that shows where the forwarded part of the message begins. */
2097 gf_puts(_("---------- Forwarded message ----------"), pc);
2098 gf_puts(NEWLINE, pc);
2102 /*----------------------------------------------------------------------
2103 Wrapper for header formatting tool
2105 Args: stream --
2106 msgno --
2107 env --
2108 pc --
2109 prefix --
2111 Result: header suitable for reply/forward text written using "pc"
2113 ----------------------------------------------------------------------*/
2114 void
2115 reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env,
2116 gf_io_t pc, char *prefix)
2118 int rv;
2119 HEADER_S h;
2120 char **list, **new_list = NULL;
2122 list = ps_global->VAR_VIEW_HEADERS;
2125 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2126 * the user doesn't inadvertently forward the BCC header.
2128 if(list && list[0]){
2129 int i, cnt = 0;
2130 char **p;
2132 while(list[cnt++])
2135 p = new_list = (char **) fs_get((cnt+1) * sizeof(char *));
2137 for(i=0; list[i]; i++)
2138 if(strucmp(list[i], "bcc"))
2139 *p++ = cpystr(list[i]);
2141 *p = NULL;
2143 if(new_list && new_list[0])
2144 list = new_list;
2148 HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC);
2149 if((rv = format_header(stream, msgno, part, env, &h,
2150 prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){
2151 if(rv == 1)
2152 gf_puts(" [Error fetching message header data]", pc);
2154 else
2155 gf_puts(NEWLINE, pc); /* write header delimiter */
2157 if(new_list)
2158 free_list_array(&new_list);
2162 /*----------------------------------------------------------------------
2163 Build the subject for the message number being forwarded
2165 Args: pine_state -- The usual pine structure
2166 msgno -- The message number to build subject for
2168 Result: malloc'd string containing new subject or NULL on error
2170 ----------------------------------------------------------------------*/
2171 char *
2172 forward_subject(ENVELOPE *env, int flags)
2174 size_t l;
2175 char *p, buftmp[MAILTMPLEN];
2177 if(!env)
2178 return(NULL);
2180 dprint((9, "checking subject: \"%s\"\n",
2181 env->subject ? env->subject : "NULL"));
2183 if(env->subject && env->subject[0]){ /* add (fwd)? */
2184 snprintf(buftmp, sizeof(buftmp), "%s", env->subject);
2185 buftmp[sizeof(buftmp)-1] = '\0';
2186 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2187 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf,
2188 SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp)
2189 strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF);
2191 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2193 removing_trailing_white_space(tmp_20k_buf);
2194 if((l = strlen(tmp_20k_buf)) < 1000 &&
2195 (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){
2196 snprintf(tmp_20k_buf+2000, SIZEOF_20KBUF-2000, "%s (fwd)", tmp_20k_buf);
2197 tmp_20k_buf[SIZEOF_20KBUF-2000-1] = '\0';
2198 strncpy(tmp_20k_buf, tmp_20k_buf+2000, SIZEOF_20KBUF);
2199 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2203 * HACK: composer can't handle embedded double quotes in attachment
2204 * comments so we substitute two single quotes.
2206 if(flags & FS_CONVERT_QUOTES)
2207 while((p = strchr(tmp_20k_buf, QUOTE)) != NULL)
2208 (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''");
2210 return(cpystr(tmp_20k_buf));
2214 return(cpystr("Forwarded mail...."));
2218 /*----------------------------------------------------------------------
2219 Build the body for the message number/part being forwarded
2221 Args:
2223 Result: BODY structure suitable for sending
2225 ----------------------------------------------------------------------*/
2226 BODY *
2227 forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
2228 long int msgno, char *sect_prefix, void *msgtext, int flags)
2230 BODY *body = NULL, *text_body, *tmp_body;
2231 PART *part;
2232 gf_io_t pc;
2233 char *tmp_text, *section, sect_buf[256];
2234 int forward_raw_body = 0;
2237 * Check to see if messages got expunged out from underneath us. This
2238 * could have happened during the prompt to the user asking whether to
2239 * include the message as an attachment. Either the message is gone or
2240 * it might be at a different sequence number. We'd better bail.
2242 if(ps_global->full_header == 2
2243 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
2244 forward_raw_body = 1;
2245 if(sp_expunge_count(stream))
2246 return(NULL);
2248 if(sect_prefix && forward_raw_body == 0)
2249 snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix);
2250 else if(sect_prefix && forward_raw_body)
2251 section = sect_prefix;
2252 else if(!sect_prefix && forward_raw_body)
2253 section = NULL;
2254 else
2255 section = "1";
2257 sect_buf[sizeof(sect_buf)-1] = '\0';
2259 gf_set_so_writec(&pc, (STORE_S *) msgtext);
2260 if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) {
2261 char *charset = NULL;
2263 /*---- Message has a single text part -----*/
2264 body = mail_newbody();
2265 body->type = TYPETEXT;
2266 body->contents.text.data = msgtext;
2267 if(orig_body
2268 && (charset = parameter_val(orig_body->parameter, "charset")))
2269 set_parameter(&body->parameter, "charset", charset);
2271 if(charset)
2272 fs_give((void **) &charset);
2274 if(!(flags & FWD_ANON)){
2275 forward_delimiter(pc);
2276 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
2279 if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body,
2280 msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){
2281 mail_free_body(&body);
2282 return(NULL);
2285 else if(orig_body->type == TYPEMULTIPART) {
2286 if(orig_body->subtype
2287 && ((!strucmp(orig_body->subtype, "signed") && orig_body->nested.part)
2288 #ifdef SMIME
2289 || (!strucmp(orig_body->subtype, "mixed")
2290 && orig_body->nested.part
2291 && orig_body->nested.part->body.type == TYPEMULTIPART
2292 && orig_body->nested.part->body.subtype
2293 && (!strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2294 || !strucmp(orig_body->nested.part->body.subtype, "signed")))
2295 || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE)
2296 #endif /* SMIME */
2298 /* only operate on the signed data (not the signature) */
2299 body = forward_body(stream, env, &orig_body->nested.part->body,
2300 msgno,
2301 orig_body->nested.part->body.type == TYPEMULTIPART
2302 ? section : sect_prefix, msgtext, flags);
2304 /*---- Message is multipart ----*/
2305 else if(!(orig_body->subtype && !strucmp(orig_body->subtype,
2306 "alternative")
2307 && (body = forward_multi_alt(stream, env, orig_body, msgno,
2308 sect_prefix, msgtext,
2309 pc, flags)))){
2310 /*--- Copy the body and entire structure ---*/
2311 body = copy_body(NULL, orig_body);
2314 * whatever subtype it is, demote it
2315 * to plain old MIXED.
2317 if(body->subtype)
2318 fs_give((void **) &body->subtype);
2320 body->subtype = cpystr("Mixed");
2322 /*--- The text part of the message ---*/
2323 if(!body->nested.part){
2324 q_status_message(SM_ORDER | SM_DING, 3, 6,
2325 "Error referencing body part 1");
2326 mail_free_body(&body);
2328 else if(body->nested.part->body.type == TYPETEXT) {
2329 char *new_charset = NULL;
2331 /*--- The first part is text ----*/
2332 text_body = &body->nested.part->body;
2333 text_body->contents.text.data = msgtext;
2334 if(text_body->subtype && strucmp(text_body->subtype, "Plain")){
2335 /* this text is going to the composer, it should be Plain */
2336 fs_give((void **)&text_body->subtype);
2337 text_body->subtype = cpystr("PLAIN");
2339 if(!(flags & FWD_ANON)){
2340 forward_delimiter(pc);
2341 reply_forward_header(stream, msgno,
2342 sect_prefix, env, pc, "");
2345 if(!(get_body_part_text(stream, &orig_body->nested.part->body,
2346 msgno, section, 0L, pc,
2347 NULL, &new_charset, GBPT_NONE)
2348 && fetch_contents(stream, msgno, sect_prefix, body)))
2349 mail_free_body(&body);
2350 else if(new_charset)
2351 set_parameter(&text_body->parameter, "charset", new_charset);
2353 /* BUG: ? matter that we're not setting body.size.bytes */
2355 else if(body->nested.part->body.type == TYPEMULTIPART
2356 && body->nested.part->body.subtype
2357 && !strucmp(body->nested.part->body.subtype, "alternative")
2358 && (tmp_body = forward_multi_alt(stream, env,
2359 &body->nested.part->body,
2360 msgno, sect_prefix,
2361 msgtext, pc,
2362 flags | FWD_NESTED))){
2363 /* for the forward_multi_alt call above, we want to pass
2364 * sect_prefix instead of section so we can obtain the header.
2365 * Set the FWD_NESTED flag so we fetch the right body_part.
2367 int partnum;
2369 part = body->nested.part->next;
2370 body->nested.part->next = NULL;
2371 mail_free_body_part(&body->nested.part);
2372 body->nested.part = mail_newbody_part();
2373 body->nested.part->body = *tmp_body;
2374 body->nested.part->next = part;
2376 for(partnum = 2; part != NULL; part = part->next){
2377 snprintf(sect_buf, sizeof(sect_buf), "%s%s%d",
2378 sect_prefix ? sect_prefix : "",
2379 sect_prefix ? "." : "", partnum++);
2380 sect_buf[sizeof(sect_buf)-1] = '\0';
2382 if(!fetch_contents(stream, msgno, sect_buf, &part->body)){
2383 mail_free_body(&body);
2384 break;
2388 else {
2389 if(fetch_contents(stream, msgno, sect_prefix, body)){
2390 /*--- Create a new blank text part ---*/
2391 part = mail_newbody_part();
2392 part->next = body->nested.part;
2393 body->nested.part = part;
2394 part->body.contents.text.data = msgtext;
2396 else
2397 mail_free_body(&body);
2401 else {
2402 /*---- A single part message, not of type text ----*/
2403 body = mail_newbody();
2404 body->type = TYPEMULTIPART;
2405 part = mail_newbody_part();
2406 body->nested.part = part;
2408 /*--- The first part, a blank text part to be edited ---*/
2409 part->body.type = TYPETEXT;
2410 part->body.contents.text.data = msgtext;
2412 /*--- The second part, what ever it is ---*/
2413 part->next = mail_newbody_part();
2414 part = part->next;
2415 part->body.id = generate_message_id();
2416 copy_body(&(part->body), orig_body);
2419 * the idea here is to fetch part into storage object
2421 if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
2422 EDIT_ACCESS)) != NULL){
2423 if((tmp_text = pine_mail_fetch_body(stream, msgno, section,
2424 &part->body.size.bytes, NIL)) != NULL)
2425 so_nputs((STORE_S *)part->body.contents.text.data, tmp_text,
2426 part->body.size.bytes);
2427 else
2428 mail_free_body(&body);
2430 else
2431 mail_free_body(&body);
2434 gf_clear_so_writec((STORE_S *) msgtext);
2436 return(body);
2442 * bounce_msg_body - build body from specified message suitable
2443 * for sending as bounced message
2445 char *
2446 bounce_msg_body(MAILSTREAM *stream,
2447 long int rawno,
2448 char *part,
2449 char **to,
2450 char *subject,
2451 ENVELOPE **outgoingp,
2452 BODY **bodyp,
2453 int *seenp)
2455 char *h, *p, *errstr = NULL;
2456 int i;
2457 STORE_S *msgtext;
2458 gf_io_t pc;
2460 *outgoingp = mail_newenvelope();
2461 (*outgoingp)->message_id = generate_message_id();
2462 (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail....");
2465 * Fill in destination if we were given one. If so, note that we
2466 * call p_s_s() below such that it won't prompt...
2468 if(to && *to){
2469 static char *fakedomain = "@";
2470 char *tmp_a_string;
2472 /* rfc822_parse_adrlist feels free to destroy input so copy */
2473 tmp_a_string = cpystr(*to);
2474 rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string,
2475 (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global))
2476 ? fakedomain : ps_global->maildomain);
2477 fs_give((void **) &tmp_a_string);
2480 /* build remail'd header */
2481 if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){
2482 for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++)
2483 i++;
2485 /* allocate it */
2486 (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1);
2489 * copy it, "X-"ing out transport headers bothersome to
2490 * software but potentially useful to the human recipient...
2492 p = (*outgoingp)->remail;
2493 bounce_mask_header(&p, h);
2495 if(*h == '\015' && *(h+1) == '\012'){
2496 *p++ = *h++; /* copy CR LF */
2497 *p++ = *h++;
2498 bounce_mask_header(&p, h);
2500 while((*p++ = *h++) != '\0');
2502 /* BUG: else complain? */
2504 /* NOT bound for the composer, so no need for PicoText */
2505 if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){
2506 mail_free_envelope(outgoingp);
2507 return(_("Error allocating message text"));
2510 /* mark object for special handling */
2511 so_attr(msgtext, "rawbody", "1");
2514 * Build a fake body description. It's ignored by pine_rfc822_header,
2515 * but we need to set it to something that makes set_mime_types
2516 * not sniff it and pine_rfc822_output_body not re-encode it.
2517 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2518 * problems unless something tries to access body_encodings[] using
2519 * it without proper precautions. We don't want to use ENCOTHER
2520 * cause that tells set_mime_types to sniff it, and we don't want to
2521 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2522 * it. When there's time, it'd be nice to clean this interaction
2523 * up...
2525 *bodyp = mail_newbody();
2526 (*bodyp)->type = TYPETEXT;
2527 (*bodyp)->encoding = ENCMAX + 1;
2528 (*bodyp)->subtype = cpystr("Plain");
2529 (*bodyp)->contents.text.data = (void *) msgtext;
2530 gf_set_so_writec(&pc, msgtext);
2532 if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){
2533 MESSAGECACHE *mc;
2535 if((mc = mail_elt(stream, rawno)) != NULL)
2536 *seenp = mc->seen;
2539 /* pass NULL body to force mail_fetchtext */
2540 if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE))
2541 errstr = _("Error fetching message contents. Can't Bounce message");
2543 gf_clear_so_writec(msgtext);
2545 return(errstr);
2550 /*----------------------------------------------------------------------
2551 Mask off any header entries we don't want xport software to see
2553 Args: d -- destination string pointer pointer
2554 s -- source string pointer pointer
2556 Postfix uses Delivered-To to detect loops.
2557 Received line counting is also used to detect loops in places.
2559 ----*/
2560 void
2561 bounce_mask_header(char **d, char *s)
2563 if(((*s == 'R' || *s == 'r')
2564 && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8)
2565 || !struncmp(s+1, "eturn-Path", 10)))
2566 || !struncmp(s, "Delivered-To:", 13)){
2567 *(*d)++ = 'X'; /* write mask */
2568 *(*d)++ = '-';
2573 /*----------------------------------------------------------------------
2574 Fetch and format text for forwarding
2576 Args: stream -- Mail stream to fetch text from
2577 body -- Body structure of message being forwarded
2578 msg_no -- Message number of text for forward
2579 part_no -- Part number of text to forward
2580 partial -- If this is > 0 a partial fetch will be done and it will
2581 be done using FT_PEEK so the message will remain unseen.
2582 pc -- Function to write to
2583 prefix -- Prefix for each line
2584 ret_charset -- If we translate to another charset return that
2585 new charset here
2587 Returns: true if OK, false if problem occured while filtering
2589 If the text is richtext, it will be converted to plain text, since there's
2590 no rich text editing capabilities in Pine (yet).
2592 It's up to calling routines to plug in signature appropriately
2594 As with all internal text, NVT end-of-line conventions are observed.
2595 DOESN'T sanity check the prefix given!!!
2596 ----*/
2598 get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body,
2599 long int msg_no, char *part_no, long partial, gf_io_t pc,
2600 char *prefix, char **ret_charset, unsigned flags)
2602 int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0;
2603 FILTLIST_S filters[12];
2604 long len;
2605 char *err, *charset, *prefix_p = NULL;
2606 int filtcnt = 0;
2607 char *free_this = NULL;
2608 DELQ_S dq;
2610 memset(filters, 0, sizeof(filters));
2611 if(ret_charset)
2612 *ret_charset = NULL;
2614 if(!pc_is_picotext(pc))
2615 we_cancel = busy_cue(NULL, NULL, 1);
2617 /* if null body, we must be talking to a non-IMAP2bis server.
2618 * No MIME parsing provided, so we just grab the message text...
2620 if(body == NULL){
2621 char *text, *decode_error;
2622 gf_io_t gc;
2623 SourceType src = CharStar;
2624 int rv = 0;
2626 (void) pine_mail_fetchstructure(stream, msg_no, NULL);
2628 if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){
2629 gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0);
2631 gf_filter_init(); /* no filters needed */
2632 if(prefix)
2633 gf_link_filter(gf_prefix, gf_prefix_opt(prefix));
2634 if((decode_error = gf_pipe(gc, pc)) != NULL){
2635 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s",
2636 NEWLINE, NEWLINE,
2637 decode_error, NEWLINE);
2638 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2639 gf_puts(tmp_20k_buf, pc);
2640 rv++;
2643 else{
2644 gf_puts(NEWLINE, pc);
2645 gf_puts(_(" [ERROR fetching text of message]"), pc);
2646 gf_puts(NEWLINE, pc);
2647 gf_puts(NEWLINE, pc);
2648 rv++;
2651 if(we_cancel)
2652 cancel_busy_cue(-1);
2654 return(rv == 0);
2657 charset = parameter_val(body->parameter, "charset");
2659 if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){
2660 if(ret_charset)
2661 *ret_charset = "UTF-8";
2665 * just use detach, but add an auxiliary filter to insert prefix,
2666 * and, perhaps, digest richtext
2668 if(ps_global->full_header != 2
2669 && !ps_global->postpone_no_flow
2670 && (!body->subtype || !strucmp(body->subtype, "plain"))){
2671 char *parmval;
2673 flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global)
2674 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global)
2675 && (!prefix || (strucmp(prefix,"> ") == 0)
2676 || strucmp(prefix, ">") == 0));
2677 if((parmval = parameter_val(body->parameter,
2678 "format")) != NULL){
2679 if(!strucmp(parmval, "flowed")){
2680 wrapflags |= GFW_FLOWED;
2682 fs_give((void **) &parmval);
2683 if((parmval = parameter_val(body->parameter, "delsp")) != NULL){
2684 if(!strucmp(parmval, "yes")){
2685 filters[filtcnt++].filter = gf_preflow;
2686 wrapflags |= GFW_DELSP;
2689 fs_give((void **) &parmval);
2693 * if there's no prefix we're forwarding text
2694 * otherwise it's reply text. unless the user's
2695 * tied our hands, alter the prefix to continue flowed
2696 * formatting...
2698 if(flow_res)
2699 wrapflags |= GFW_FLOW_RESULT;
2701 filters[filtcnt].filter = gf_wrap;
2703 * The 80 will cause longer lines than what is likely
2704 * set by composer_fillcol, so we'll have longer
2705 * quoted and forwarded lines than the lines we type.
2706 * We're fine with that since the alternative is the
2707 * possible wrapping of lines we shouldn't have, which
2708 * is mitigated by this higher 80 limit.
2710 * If we were to go back to using composer_fillcol,
2711 * the correct value is composer_fillcol + 1, pine
2712 * is off-by-one from pico.
2714 filters[filtcnt++].data = gf_wrap_filter_opt(
2715 MAX(MIN(ps_global->ttyo->screen_cols
2716 - (prefix ? strlen(prefix) : 0),
2717 80 - (prefix ? strlen(prefix) : 0)),
2718 30), /* doesn't have to be 30 */
2719 990, /* 998 is the SMTP limit */
2720 NULL, 0, wrapflags);
2725 * if not flowed, remove trailing whitespace to reduce
2726 * confusion, since we're sending out as flowed if we
2727 * can. At some future point, we might try
2728 * plugging in a user-option-controlled heuristic
2729 * flowing filter
2731 * We also want to fold "> " quotes so we get the
2732 * attributions correct.
2734 if(flow_res && prefix && !strucmp(prefix, "> "))
2735 *(prefix_p = prefix + 1) = '\0';
2737 if(!(wrapflags & GFW_FLOWED)
2738 && flow_res){
2739 filters[filtcnt].filter = gf_line_test;
2740 filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL);
2742 filters[filtcnt].filter = gf_line_test;
2743 filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL);
2746 else if(body->subtype){
2747 int plain_opt = 1;
2749 if(strucmp(body->subtype,"richtext") == 0){
2750 filters[filtcnt].filter = gf_rich2plain;
2751 filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt);
2753 else if(strucmp(body->subtype,"enriched") == 0){
2754 filters[filtcnt].filter = gf_enriched2plain;
2755 filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt);
2757 else if(strucmp(body->subtype,"html") == 0){
2758 if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){
2759 filters[filtcnt].filter = gf_html2plain;
2760 filters[filtcnt++].data = gf_html2plain_opt(NULL,
2761 ps_global->ttyo->screen_cols,
2762 non_messageview_margin(),
2763 NULL, NULL, GFHP_STRIPPED);
2768 if(prefix){
2769 if(ps_global->full_header != 2
2770 && (F_ON(F_ENABLE_SIGDASHES, ps_global)
2771 || F_ON(F_ENABLE_STRIP_SIGDASHES, ps_global))){
2772 dashdata = 0;
2773 filters[filtcnt].filter = gf_line_test;
2774 filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata);
2777 filters[filtcnt].filter = gf_prefix;
2778 filters[filtcnt++].data = gf_prefix_opt(prefix);
2780 if(wrapflags & GFW_FLOWED || flow_res){
2781 filters[filtcnt].filter = gf_line_test;
2782 filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL);
2786 if(flags & GBPT_DELQUOTES){
2787 memset(&dq, 0, sizeof(dq));
2788 dq.lines = Q_DEL_ALL;
2789 dq.is_flowed = 0;
2790 dq.indent_length = 0;
2791 dq.saved_line = &free_this;
2792 dq.handlesp = NULL;
2793 dq.do_color = 0;
2794 dq.delete_all = 1;
2796 filters[filtcnt].filter = gf_line_test;
2797 filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq);
2800 err = detach(stream, msg_no, part_no, partial, &len, pc,
2801 filters[0].filter ? filters : NULL,
2802 ((flags & GBPT_PEEK) ? FT_PEEK : 0)
2803 | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0));
2805 if(free_this)
2806 fs_give((void **) &free_this);
2808 if(prefix_p)
2809 *prefix_p = ' ';
2811 if (err != (char *) NULL)
2812 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2813 q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld",
2814 err, (void *) msg_no);
2816 if(we_cancel)
2817 cancel_busy_cue(-1);
2819 return((int) len);
2824 quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local)
2826 char *p;
2828 if(*line == '>'){
2829 for(p = line; *p; p++){
2830 if(isspace((unsigned char) *p)){
2831 if(*(p+1) == '>')
2832 ins = gf_line_test_new_ins(ins, p, "", -1);
2834 else if(*p != '>')
2835 break;
2839 return(0);
2844 twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2846 char *p, *ws = NULL;
2848 for(p = line; *p; p++){
2849 /* don't strip trailing space on signature line */
2850 if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))
2851 break;
2853 if(isspace((unsigned char) *p)){
2854 if(!ws)
2855 ws = p;
2857 else
2858 ws = NULL;
2861 if(ws)
2862 ins = gf_line_test_new_ins(ins, ws, "", -(p - ws));
2864 return(0);
2868 post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local)
2870 char *p;
2872 for(p = line; *p; p++)
2873 if(*p != '>'){
2874 if(p != line && *p != ' ')
2875 ins = gf_line_test_new_ins(ins, p, " ", 1);
2877 break;
2880 return(0);
2885 sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local)
2887 if(*((int *)local)
2888 || (*line == '-' && *(line+1) == '-'
2889 && *(line+2) == ' ' && !*(line+3))){
2890 *((int *) local) = 1;
2891 return(2); /* skip this line! */
2894 return(0);
2898 /*----------------------------------------------------------------------
2899 return the c-client reference name for the given end_body part
2900 ----*/
2901 char *
2902 body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body)
2904 BODY *body;
2906 (void) pine_mail_fetchstructure(stream, msgno, &body);
2907 return(partno(body, end_body));
2911 /*----------------------------------------------------------------------
2912 return the c-client reference name for the given end_body part
2913 ----*/
2914 char *
2915 partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body)
2917 PART *part;
2918 int num = 0;
2919 char tmp[64], *p = NULL;
2921 if(body && body->type == TYPEMULTIPART) {
2922 part = body->nested.part; /* first body part */
2924 do { /* for each part */
2925 num++;
2926 if(&part->body == end_body || (p = partno(&part->body, end_body))){
2927 snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "",
2928 sizeof(tmp)-10, (p) ? p : "");
2929 tmp[sizeof(tmp)-1] = '\0';
2930 if(p)
2931 fs_give((void **)&p);
2933 return(cpystr(tmp));
2935 } while ((part = part->next) != NULL); /* until done */
2937 return(NULL);
2939 else if(body && body->type == TYPEMESSAGE && body->subtype
2940 && !strucmp(body->subtype, "rfc822")){
2941 return(partno(body->nested.msg->body, end_body));
2944 return((body == end_body) ? cpystr("1") : NULL);
2948 /*----------------------------------------------------------------------
2949 Fill in the contents of each body part
2951 Args: stream -- Stream the message is on
2952 msgno -- Message number the body structure is for
2953 section -- body section associated with body pointer
2954 body -- Body pointer to fill in
2956 Result: 1 if all went OK, 0 if there was a problem
2958 This function copies the contents from an original message/body to
2959 a new message/body. It recurses down all multipart levels.
2961 If one or more part (but not all) can't be fetched, a status message
2962 will be queued.
2963 ----*/
2965 fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body)
2967 char *tp;
2968 int got_one = 0;
2970 if(!body->id)
2971 body->id = generate_message_id();
2973 if(body->type == TYPEMULTIPART){
2974 char subsection[256], *subp;
2975 int n, last_one = 10; /* remember worst case */
2976 PART *part = body->nested.part;
2978 if(!(part = body->nested.part))
2979 return(0);
2981 subp = subsection;
2982 if(section && *section){
2983 for(n = 0;
2984 n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++)
2987 *subp++ = '.';
2990 n = 1;
2991 do {
2992 snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++);
2993 subsection[sizeof(subsection)-1] = '\0';
2994 got_one = fetch_contents(stream, msgno, subsection, &part->body);
2995 last_one = MIN(last_one, got_one);
2997 while((part = part->next) != NULL);
2999 return(last_one);
3002 if(body->contents.text.data)
3003 return(1); /* already taken care of... */
3005 if(body->type == TYPEMESSAGE){
3006 if(body->subtype && strucmp(body->subtype,"external-body")){
3008 * the idea here is to fetch everything into storage objects
3010 body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,
3011 EDIT_ACCESS);
3012 if(body->contents.text.data
3013 && (tp = pine_mail_fetch_body(stream, msgno, section,
3014 &body->size.bytes, NIL))){
3015 so_truncate((STORE_S *)body->contents.text.data,
3016 body->size.bytes + 2048);
3017 so_nputs((STORE_S *)body->contents.text.data, tp,
3018 body->size.bytes);
3019 got_one = 1;
3021 else
3022 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3023 _("Error fetching part %s"), section);
3024 } else {
3025 got_one = 1;
3027 } else {
3029 * the idea here is to fetch everything into storage objects
3030 * so, grab one, then fetch the body part
3032 body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS);
3033 if(body->contents.text.data
3034 && (tp=pine_mail_fetch_body(stream, msgno, section,
3035 &body->size.bytes, NIL))){
3036 so_truncate((STORE_S *)body->contents.text.data,
3037 body->size.bytes + 2048);
3038 so_nputs((STORE_S *)body->contents.text.data, tp,
3039 body->size.bytes);
3040 got_one = 1;
3042 else
3043 q_status_message1(SM_ORDER | SM_DING, 3, 3,
3044 _("Error fetching part %s"), section);
3047 return(got_one);
3051 /*----------------------------------------------------------------------
3052 Copy the body structure
3054 Args: new_body -- Pointer to already allocated body, or NULL, if none
3055 old_body -- The Body to copy
3058 This is traverses the body structure recursively copying all elements.
3059 The new_body parameter can be NULL in which case a new body is
3060 allocated. Alternatively it can point to an already allocated body
3061 structure. This is used when copying body parts since a PART includes a
3062 BODY. The contents fields are *not* filled in.
3063 ----*/
3065 BODY *
3066 copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body)
3068 if(old_body == NULL)
3069 return(NULL);
3071 if(new_body == NULL)
3072 new_body = mail_newbody();
3074 new_body->type = old_body->type;
3075 new_body->encoding = old_body->encoding;
3077 if(old_body->subtype)
3078 new_body->subtype = cpystr(old_body->subtype);
3080 new_body->parameter = copy_parameters(old_body->parameter);
3082 if(old_body->id)
3083 new_body->id = cpystr(old_body->id);
3085 if(old_body->description)
3086 new_body->description = cpystr(old_body->description);
3088 if(old_body->disposition.type)
3089 new_body->disposition.type = cpystr(old_body->disposition.type);
3091 new_body->disposition.parameter
3092 = copy_parameters(old_body->disposition.parameter);
3094 new_body->size = old_body->size;
3096 if(new_body->type == TYPEMESSAGE
3097 && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){
3098 new_body->nested.msg = mail_newmsg();
3099 new_body->nested.msg->body
3100 = copy_body(NULL, old_body->nested.msg->body);
3102 else if(new_body->type == TYPEMULTIPART) {
3103 PART **new_partp, *old_part;
3105 new_partp = &new_body->nested.part;
3106 for(old_part = old_body->nested.part;
3107 old_part != NULL;
3108 old_part = old_part->next){
3109 *new_partp = mail_newbody_part();
3110 copy_body(&(*new_partp)->body, &old_part->body);
3111 new_partp = &(*new_partp)->next;
3115 return(new_body);
3119 /*----------------------------------------------------------------------
3120 Copy the MIME parameter list
3122 Allocates storage for new part, and returns pointer to new paramter
3123 list. If old_p is NULL, NULL is returned.
3124 ----*/
3125 PARAMETER *
3126 copy_parameters(PARAMETER *old_p)
3128 PARAMETER *new_p, *p1, *p2;
3130 if(old_p == NULL)
3131 return((PARAMETER *)NULL);
3133 new_p = p2 = NULL;
3134 for(p1 = old_p; p1 != NULL; p1 = p1->next){
3135 set_parameter(&p2, p1->attribute, p1->value);
3136 if(new_p == NULL)
3137 new_p = p2;
3140 return(new_p);
3144 /*----------------------------------------------------------------------
3145 Make a complete copy of an envelope and all it's fields
3147 Args: e -- the envelope to copy
3149 Result: returns the new envelope, or NULL, if the given envelope was NULL
3151 ----*/
3153 ENVELOPE *
3154 copy_envelope(register ENVELOPE *e)
3156 register ENVELOPE *e2;
3158 if(!e)
3159 return(NULL);
3161 e2 = mail_newenvelope();
3162 e2->remail = e->remail ? cpystr(e->remail) : NULL;
3163 e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL;
3164 e2->date = e->date ? (unsigned char *)cpystr((char *) e->date)
3165 : NULL;
3166 e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL;
3167 e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL;
3168 e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL;
3169 e2->subject = e->subject ? cpystr(e->subject) : NULL;
3170 e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL;
3171 e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL;
3172 e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL;
3173 e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL;
3174 e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL;
3175 e2->message_id = e->message_id ? cpystr(e->message_id) : NULL;
3176 e2->references = e->references ? cpystr(e->references) : NULL;
3177 e2->followup_to = e->followup_to ? cpystr(e->references) : NULL;
3178 return(e2);
3182 /*----------------------------------------------------------------------
3183 Generate the "In-reply-to" text from message header
3185 Args: message -- Envelope of original message
3187 Result: returns an alloc'd string or NULL if there is a problem
3188 ----*/
3189 char *
3190 reply_in_reply_to(ENVELOPE *env)
3192 return((env && env->message_id) ? cpystr(env->message_id) : NULL);
3196 /*----------------------------------------------------------------------
3197 Generate a unique message id string.
3199 Args: ps -- The usual pine structure
3201 Result: Alloc'd unique string is returned
3203 Uniqueness is gaurenteed by using the host name, process id, date to the
3204 second and a single unique character
3205 *----------------------------------------------------------------------*/
3206 char *
3207 generate_message_id(void)
3209 static short osec = 0, cnt = 0;
3210 char idbuf[128];
3211 char *id;
3212 time_t now;
3213 struct tm *now_x;
3214 char *hostpart = NULL;
3216 now = time((time_t *)0);
3217 now_x = localtime(&now);
3219 if(now_x->tm_sec == osec)
3220 cnt++;
3221 else{
3222 cnt = 0;
3223 osec = now_x->tm_sec;
3226 hostpart = F_ON(F_ROT13_MESSAGE_ID, ps_global)
3227 ? rot13(ps_global->hostname)
3228 : cpystr(ps_global->hostname);
3230 if(!hostpart)
3231 hostpart = cpystr("huh");
3233 snprintf(idbuf, sizeof(idbuf), "<alpine.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
3234 SYSTYPE, ALPINE_VERSION, (now_x->tm_year) % 100, now_x->tm_mon + 1,
3235 now_x->tm_mday, now_x->tm_hour, now_x->tm_min, now_x->tm_sec,
3236 cnt, getpid(), hostpart);
3237 idbuf[sizeof(idbuf)-1] = '\0';
3239 id = cpystr(idbuf);
3241 if(hostpart)
3242 fs_give((void **) &hostpart);
3244 return(id);
3248 char *
3249 generate_user_agent(void)
3251 char buf[128];
3252 char rev[128];
3254 if(F_ON(F_QUELL_USERAGENT, ps_global))
3255 return(NULL);
3257 snprintf(buf, sizeof(buf),
3258 "%sAlpine %s (%s %s)",
3259 (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "",
3260 ALPINE_VERSION, SYSTYPE,
3261 get_alpine_revision_string(rev, sizeof(rev)));
3263 return(cpystr(buf));
3267 char *
3268 rot13(char *src)
3270 char byte, cap, *p, *ret = NULL;
3272 if(src && *src){
3273 ret = (char *) fs_get((strlen(src)+1) * sizeof(char));
3274 p = ret;
3275 while((byte = *src++) != '\0'){
3276 cap = byte & 32;
3277 byte &= ~cap;
3278 *p++ = ((byte >= 'A') && (byte <= 'Z')
3279 ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap;
3282 *p = '\0';
3285 return(ret);
3289 /*----------------------------------------------------------------------
3290 Return the first true address pointer (modulo group syntax allowance)
3292 Args: addr -- Address list
3294 Result: First real address pointer, or NULL
3295 ----------------------------------------------------------------------*/
3296 ADDRESS *
3297 first_addr(struct mail_address *addr)
3299 while(addr && !addr->host)
3300 addr = addr->next;
3302 return(addr);
3306 /*----------------------------------------------------------------------
3307 lit -- this is the source
3308 prenewlines -- prefix the file contents with this many newlines
3309 postnewlines -- postfix the file contents with this many newlines
3310 is_sig -- this is a signature (not a template)
3311 decode_constants -- change C-style constants into their values
3312 ----*/
3313 char *
3314 get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants)
3316 char *sig = NULL;
3319 * Should make this smart enough not to do the copying and double
3320 * allocation of space.
3322 if(lit){
3323 char *tmplit = NULL, *p, *q, *d, save;
3324 size_t len;
3326 if(decode_constants){
3327 tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char));
3328 tmplit[0] = '\0';
3329 cstring_to_string(lit, tmplit);
3331 else
3332 tmplit = cpystr(lit);
3334 len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE);
3335 sig = (char *) fs_get((len+1) * sizeof(char));
3336 memset(sig, 0, len+1);
3337 d = sig;
3338 while(prenewlines--)
3339 sstrncpy(&d, NEWLINE, len-(d-sig));
3341 if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) &&
3342 !sigdashes_are_present(tmplit)){
3343 sstrncpy(&d, SIGDASHES, len-(d-sig));
3344 sstrncpy(&d, NEWLINE, len-(d-sig));
3347 sig[len] = '\0';
3349 p = tmplit;
3350 while(*p){
3351 /* get a line */
3352 q = strpbrk(p, "\n\r");
3353 if(q){
3354 save = *q;
3355 *q = '\0';
3359 * Strip trailing space if we are doing a signature and
3360 * this line is not sigdashes.
3362 if(is_sig && strcmp(p, SIGDASHES))
3363 removing_trailing_white_space(p);
3365 while((d-sig) <= len && (*d = *p++) != '\0')
3366 d++;
3368 if(q){
3369 if((d-sig) <= len)
3370 *d++ = save;
3372 p = q+1;
3374 else
3375 break;
3378 while(postnewlines--)
3379 sstrncpy(&d, NEWLINE, len-(d-sig));
3381 sig[len] = '\0';
3383 if((d-sig) <= len)
3384 *d = '\0';
3386 if(tmplit)
3387 fs_give((void **) &tmplit);
3390 return(sig);
3395 sigdashes_are_present(char *sig)
3397 char *p;
3399 p = srchstr(sig, SIGDASHES);
3400 while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) &&
3401 (p[3] == '\0' || p[3] == '\n' || p[3] == '\r')))
3402 p = srchstr(p+1, SIGDASHES);
3404 return(p ? 1 : 0);
3408 /*----------------------------------------------------------------------
3409 Acquire the pinerc defined signature file pathname
3411 ----*/
3412 char *
3413 signature_path(char *sname, char *sbuf, size_t len)
3415 *sbuf = '\0';
3416 if(sname && *sname){
3417 size_t spl = strlen(sname);
3418 if(IS_REMOTE(sname)){
3419 if(spl < len - 1)
3420 strncpy(sbuf, sname, len-1);
3422 else if(is_absolute_path(sname)){
3423 strncpy(sbuf, sname, len-1);
3424 sbuf[len-1] = '\0';
3425 fnexpand(sbuf, len);
3427 else if(ps_global->VAR_OPER_DIR){
3428 if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1)
3429 build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len);
3431 else{
3432 char *lc = last_cmpnt(ps_global->pinerc);
3434 sbuf[0] = '\0';
3435 if(lc != NULL){
3436 strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc));
3437 sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0';
3440 strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0));
3441 sbuf[len-1] = '\0';
3445 return(*sbuf ? sbuf : NULL);
3449 char *
3450 simple_read_remote_file(char *name, char *subtype)
3452 int try_cache;
3453 REMDATA_S *rd;
3454 char *file = NULL;
3457 dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?"));
3460 * We could parse the name here to find what type it is. So far we
3461 * only have type RemImap.
3463 rd = rd_create_remote(RemImap, name, subtype,
3464 NULL, _("Error: "), _("Can't fetch remote configuration."));
3465 if(!rd)
3466 goto bail_out;
3468 try_cache = rd_read_metadata(rd);
3470 if(rd->access == MaybeRorW){
3471 if(rd->read_status == 'R')
3472 rd->access = ReadOnly;
3473 else
3474 rd->access = ReadWrite;
3477 if(rd->access != NoExists){
3479 rd_check_remvalid(rd, 1L);
3482 * If the cached info says it is readonly but
3483 * it looks like it's been fixed now, change it to readwrite.
3485 if(rd->read_status == 'R'){
3487 * We go to this trouble since readonly sigfiles
3488 * are likely a mistake. They are usually supposed to be
3489 * readwrite so we open it and check if it's been fixed.
3491 rd_check_readonly_access(rd);
3492 if(rd->read_status == 'W'){
3493 rd->access = ReadWrite;
3494 rd->flags |= REM_OUTOFDATE;
3496 else
3497 rd->access = ReadOnly;
3500 if(rd->flags & REM_OUTOFDATE){
3501 if(rd_update_local(rd) != 0){
3503 dprint((1,
3504 "simple_read_remote_file: rd_update_local failed\n"));
3506 * Don't give up altogether. We still may be
3507 * able to use a cached copy.
3510 else{
3511 dprint((7,
3512 "%s: copied remote to local (%ld)\n",
3513 rd->rn ? rd->rn : "?", (long)rd->last_use));
3517 if(rd->access == ReadWrite)
3518 rd->flags |= DO_REMTRIM;
3521 /* If we couldn't get to remote folder, try using the cached copy */
3522 if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){
3523 if(try_cache){
3524 rd->access = ReadOnly;
3525 rd->flags |= USE_OLD_CACHE;
3526 q_status_message(SM_ORDER, 3, 4,
3527 "Can't contact remote server, using cached copy");
3528 dprint((2,
3529 "Can't open remote file %s, using local cached copy %s readonly\n",
3530 rd->rn ? rd->rn : "?",
3531 rd->lf ? rd->lf : "?"));
3533 else{
3534 rd->flags &= ~DO_REMTRIM;
3535 goto bail_out;
3539 file = read_file(rd->lf, READ_FROM_LOCALE);
3541 bail_out:
3542 if(rd)
3543 rd_close_remdata(&rd);
3545 return(file);
3549 /*----------------------------------------------------------------------
3550 Build the body for the multipart/alternative part
3552 Args:
3554 Result:
3556 ----------------------------------------------------------------------*/
3557 BODY *
3558 forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body,
3559 long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags)
3561 BODY *body = NULL;
3562 PART *part = NULL, *bestpart = NULL;
3563 char tmp_buf[256];
3564 char *new_charset = NULL;
3565 int partnum, bestpartnum;
3567 if(ps_global->force_prefer_plain
3568 || (!ps_global->force_no_prefer_plain
3569 && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){
3570 for(part = orig_body->nested.part, partnum = 1;
3571 part;
3572 part = part->next, partnum++)
3573 if((!part->body.type || part->body.type == TYPETEXT)
3574 && (!part->body.subtype
3575 || !strucmp(part->body.subtype, "plain")))
3576 break;
3580 * Else choose last alternative among plain or html parts.
3581 * Perhaps we should really be using mime_show() to make this
3582 * potentially more general than just plain or html.
3584 if(!part){
3585 for(part = orig_body->nested.part, partnum = 1;
3586 part;
3587 part = part->next, partnum++){
3588 if((!part->body.type || part->body.type == TYPETEXT)
3589 && ((!part->body.subtype || !strucmp(part->body.subtype, "plain"))
3591 (part->body.subtype && !strucmp(part->body.subtype, "html")))){
3592 bestpart = part;
3593 bestpartnum = partnum;
3597 part = bestpart;
3598 partnum = bestpartnum;
3602 * IF something's interesting insert it
3603 * AND forget the rest of the multipart
3605 if(part){
3606 body = mail_newbody();
3607 body->type = TYPETEXT;
3608 body->contents.text.data = msgtext;
3610 /* record character set, flowing, etc */
3611 body->parameter = copy_parameters(part->body.parameter);
3612 body->size.bytes = part->body.size.bytes;
3614 if(!(flags & FWD_ANON)){
3615 forward_delimiter(pc);
3616 reply_forward_header(stream, msgno, sect_prefix, env, pc, "");
3619 snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d",
3620 sizeof(tmp_buf)/2, sect_prefix ? sect_prefix : "",
3621 sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "",
3622 partnum);
3623 tmp_buf[sizeof(tmp_buf)-1] = '\0';
3624 get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc,
3625 NULL, &new_charset, GBPT_NONE);
3628 * get_body_part_text translated the data to a new charset.
3629 * We need to record that fact in body.
3631 if(new_charset)
3632 set_parameter(&body->parameter, "charset", new_charset);
3634 else
3635 q_status_message(SM_ORDER | SM_DING, 3, 3,
3636 "No suitable part found. Forwarding as attachment");
3638 return(body);
3642 void
3643 reply_append_addr(struct mail_address **dest, struct mail_address *src)
3645 for( ; *dest; dest = &(*dest)->next)
3648 *dest = src;