2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/reply.h"
17 #include "../pith/send.h"
18 #include "../pith/init.h"
19 #include "../pith/state.h"
20 #include "../pith/conf.h"
21 #include "../pith/remote.h"
22 #include "../pith/status.h"
23 #include "../pith/mailview.h"
24 #include "../pith/filter.h"
25 #include "../pith/newmail.h"
26 #include "../pith/bldaddr.h"
27 #include "../pith/mailindx.h"
28 #include "../pith/mimedesc.h"
29 #include "../pith/detach.h"
30 #include "../pith/help.h"
31 #include "../pith/pipe.h"
32 #include "../pith/addrstring.h"
33 #include "../pith/news.h"
34 #include "../pith/util.h"
35 #include "../pith/pattern.h"
36 #include "../pith/detoken.h"
37 #include "../pith/stream.h"
38 #include "../pith/busy.h"
39 #include "../pith/readfile.h"
40 #include "../pith/text.h"
41 #include "../pith/list.h"
42 #include "../pith/ablookup.h"
43 #include "../pith/mailcmd.h"
44 #include "../pith/margin.h"
45 #include "../pith/smime.h"
51 void bounce_mask_header(char **, char *);
54 int (*pith_opt_replyto_prompt
)(void);
55 int (*pith_opt_reply_to_all_prompt
)(int *);
59 * standard type of storage object used for body parts...
61 #define PART_SO_TYPE CharStar
64 char *(*pith_opt_user_agent_prefix
)(void);
66 /* compare two subjects and return if they are the same.
67 We compare stripped subjects, that is, those that do
68 not have re/fwd. Before we compare the subjects, we
69 decode them in case they were encoded.
70 Return value: 0 - not the same subject, 1 - same subject.
74 same_subject(char *s
, char *t
)
78 char *s1
, *s2
; /* holds decoded subjects from s and t */
81 if (s
== NULL
|| t
== NULL
)
82 return s
== t
? 1 : 0;
84 i
= strlen(s
); j
= strlen(t
);
88 s1
= (char *) rfc1522_decode_to_utf8((unsigned char *) u
, 6*len
+ 1, s
);
89 s2
= (char *) rfc1522_decode_to_utf8((unsigned char *) v
, 6*len
+ 1, t
);
90 mail_strip_subject(s1
, &u
);
91 mail_strip_subject(s2
, &v
);
95 fs_give((void **) &u
);
96 fs_give((void **) &v
);
103 * Returns: 1 if addresses successfully copied
104 * 0 on user cancel or error
108 * RSF_QUERY_REPLY_ALL
109 * RSF_FORCE_REPLY_ALL
112 * RSF_FORCE_REPLY_ALL
116 reply_harvest(struct pine
*ps
, long int msgno
, char *section
, ENVELOPE
*env
,
117 struct mail_address
**saved_from
, struct mail_address
**saved_to
,
118 struct mail_address
**saved_cc
, struct mail_address
**saved_resent
,
121 ADDRESS
*ap
, *ap2
, *rep_address
;
122 int ret
= 0, sniff_resent
= 0;
126 * If Reply-To is same as From just treat it like it was From.
127 * Otherwise, always use the reply-to if we're replying to more
128 * than one msg or say ok to using it, even if it's us.
129 * If there's no reply-to or it's the same as the from, assume
130 * that the user doesn't want to reply to himself, unless there's
133 if(env
->reply_to
&& !addr_lists_same(env
->reply_to
, env
->from
)
134 && (F_ON(F_AUTO_REPLY_TO
, ps
)
135 || ((*flags
) & RSF_FORCE_REPLY_TO
)
136 || (pith_opt_replyto_prompt
&& (*pith_opt_replyto_prompt
)() == 'y'))){
137 rep_field
= "reply-to";
138 rep_address
= env
->reply_to
;
142 rep_address
= env
->from
;
145 ap
= reply_cp_addr(ps
, msgno
, section
, rep_field
, *saved_from
,
146 (ADDRESS
*) NULL
, rep_address
, RCA_NOT_US
);
149 cmd_cancelled("Reply");
153 reply_append_addr(saved_from
, ap
);
155 /*--------- check for other recipients ---------*/
156 if(((*flags
) & (RSF_FORCE_REPLY_ALL
| RSF_QUERY_REPLY_ALL
))){
158 if((ap
= reply_cp_addr(ps
, msgno
, section
, "To", *saved_to
,
159 *saved_from
, env
->to
, RCA_NOT_US
)) != NULL
)
160 reply_append_addr(saved_to
, ap
);
162 if((ap
= reply_cp_addr(ps
, msgno
, section
, "Cc", *saved_cc
,
163 *saved_from
, env
->cc
, RCA_NOT_US
)) != NULL
)
164 reply_append_addr(saved_cc
, ap
);
167 * In these cases, we either need to look at the resent headers
168 * to include in the reply-to-all, or to decide whether or not
169 * we need to ask the reply-to-all question.
171 if(((*flags
) & RSF_FORCE_REPLY_ALL
)
172 || (((*flags
) & RSF_QUERY_REPLY_ALL
)
173 && ((!(*saved_from
) && !(*saved_cc
))
174 || (*saved_from
&& !(*saved_to
) && !(*saved_cc
))))){
177 if((ap2
= reply_resent(ps
, msgno
, section
)) != NULL
){
179 * look for bogus addr entries and replace
181 if((ap
= reply_cp_addr(ps
, 0, NULL
, NULL
, *saved_resent
,
182 *saved_from
, ap2
, RCA_NOT_US
)) != NULL
)
184 reply_append_addr(saved_resent
, ap
);
186 mail_free_address(&ap2
);
191 * It makes sense to ask reply-to-all now.
193 if(((*flags
) & RSF_QUERY_REPLY_ALL
)
194 && ((*saved_from
&& (*saved_to
|| *saved_cc
|| *saved_resent
))
195 || (*saved_cc
|| *saved_resent
))){
196 *flags
&= ~RSF_QUERY_REPLY_ALL
;
197 if(pith_opt_reply_to_all_prompt
198 && (*pith_opt_reply_to_all_prompt
)(flags
) < 0){
199 cmd_cancelled("Reply");
205 * If we just answered yes to the reply-to-all question and
206 * we still haven't collected the resent headers, do so now.
208 if(((*flags
) & RSF_FORCE_REPLY_ALL
) && !sniff_resent
209 && (ap2
= reply_resent(ps
, msgno
, section
))){
211 * look for bogus addr entries and replace
213 if((ap
= reply_cp_addr(ps
, 0, NULL
, NULL
, *saved_resent
,
214 *saved_from
, ap2
, RCA_NOT_US
)) != NULL
)
215 reply_append_addr(saved_resent
, ap
);
217 mail_free_address(&ap2
);
225 /*----------------------------------------------------------------------
226 Return a pointer to a copy of the given address list
227 filtering out those already in the "mask" lists and ourself.
229 Args: mask1 -- Don't copy if in this list
230 mask2 -- or if in this list
231 source -- List to be copied
232 us_too -- Don't filter out ourself.
233 flags -- RCA_NOT_US copy all addrs except for our own
234 RCA_ALL copy all addrs, including our own
235 RCA_ONLY_US copy only addrs that are our own
239 reply_cp_addr(struct pine
*ps
, long int msgno
, char *section
, char *field
,
240 struct mail_address
*mask1
, struct mail_address
*mask2
,
241 struct mail_address
*source
, int flags
)
243 ADDRESS
*tmp1
, *tmp2
, *ret
= NULL
, **ret_tail
;
245 /* can only choose one of these flags values */
246 assert(!((flags
& RCA_ALL
&& flags
& RCA_ONLY_US
)
247 || (flags
& RCA_ALL
&& flags
& RCA_NOT_US
)
248 || (flags
& RCA_ONLY_US
&& flags
& RCA_NOT_US
)));
250 for(tmp1
= source
; msgno
&& tmp1
; tmp1
= tmp1
->next
)
251 if(tmp1
->host
&& tmp1
->host
[0] == '.'){
256 if((h
= pine_fetchheader_lines(ps
? ps
->mail_stream
: NULL
,
257 msgno
, section
, fields
)) != NULL
){
263 strncpy(fname
, field
, sizeof(fname
)-2);
264 fname
[sizeof(fname
)-2] = '\0';
265 strncat(fname
, ":", sizeof(fname
)-strlen(fname
)-1);
266 fname
[sizeof(fname
)-1] = '\0';
268 for(p
= h
; (p
= strstr(p
, fname
)) != NULL
; )
269 rplstr(p
, q
-(p
-h
), strlen(fname
), ""); /* strip field strings */
271 sqznewlines(h
); /* blat out CR's & LF's */
272 for(p
= h
; (p
= strchr(p
, TAB
)) != NULL
; )
273 *p
++ = ' '; /* turn TABs to whitespace */
279 ret
= (ADDRESS
*) fs_get(sizeof(ADDRESS
));
280 memset(ret
, 0, sizeof(ADDRESS
));
282 /* get rid of leading white space */
283 for(p
= h
; *p
== SPACE
; p
++)
287 memmove(h
, p
, l
= strlen(p
));
291 /* base64 armor plate the gunk to protect against
292 * c-client quoting in output.
294 p
= (char *) rfc822_binary(h
, strlen(h
),
295 (unsigned long *) &l
);
297 fs_give((void **) &h
);
299 * Seems like the 4 ought to be a 2, but I'll leave it
300 * to be safe, in case something else adds 2 chars later.
303 ret
->mailbox
= (char *) fs_get(ll
* sizeof(char));
304 snprintf(ret
->mailbox
, ll
, "&%s", p
);
305 ret
->mailbox
[ll
-1] = '\0';
306 fs_give((void **) &p
);
307 ret
->host
= cpystr(RAWFIELD
);
315 for(source
= first_addr(source
); source
; source
= source
->next
){
316 for(tmp1
= first_addr(mask1
); tmp1
; tmp1
= tmp1
->next
)
317 if(address_is_same(source
, tmp1
)) /* it is in mask1, skip it */
320 for(tmp2
= first_addr(mask2
); !tmp1
&& tmp2
; tmp2
= tmp2
->next
)
321 if(address_is_same(source
, tmp2
)) /* it is in mask2, skip it */
325 * If there's no match in masks and this address satisfies the
326 * flags requirement, copy it.
328 if(!tmp1
&& !tmp2
/* no mask match */
329 && ((flags
& RCA_ALL
) /* including everybody */
330 || (flags
& RCA_ONLY_US
&& address_is_us(source
, ps
))
331 || (flags
& RCA_NOT_US
&& !address_is_us(source
, ps
)))){
333 source
->next
= NULL
; /* only copy one addr! */
334 *ret_tail
= rfc822_cpy_adr(source
);
335 ret_tail
= &(*ret_tail
)->next
;
336 source
->next
= tmp1
; /* restore rest of list */
345 set_role_from_msg(struct pine
*ps
, long int rflags
, long int msgno
, char *section
)
347 ACTION_S
*role
= NULL
;
349 SEARCHSET
*ss
= NULL
;
352 if(!nonempty_patterns(rflags
, &pstate
))
356 ss
= mail_newsearchset();
357 ss
->first
= ss
->last
= (unsigned long)msgno
;
360 /* Go through the possible roles one at a time until we get a match. */
361 pat
= first_pattern(&pstate
);
363 /* calculate this message's score if needed */
364 if(ss
&& pat
&& scores_are_used(SCOREUSE_GET
) & SCOREUSE_ROLES
&&
365 get_msg_score(ps
->mail_stream
, msgno
) == SCORE_UNDEF
)
366 (void)calculate_some_scores(ps
->mail_stream
, ss
, 0);
369 if(match_pattern(pat
->patgrp
, ps
->mail_stream
, ss
, section
,
370 get_msg_score
, SE_NOSERVER
|SE_NOPREFETCH
)){
371 if(!pat
->action
|| pat
->action
->bogus
)
377 pat
= next_pattern(&pstate
);
381 mail_free_searchset(&ss
);
388 * reply_seed - fill in reply header
392 reply_seed(struct pine
*ps
, ENVELOPE
*outgoing
, ENVELOPE
*env
,
393 struct mail_address
*saved_from
, struct mail_address
*saved_to
,
394 struct mail_address
*saved_cc
, struct mail_address
*saved_resent
,
395 char **fcc
, int replytoall
, char **errmsg
)
397 ADDRESS
**to_tail
, **cc_tail
;
399 to_tail
= &outgoing
->to
;
400 cc_tail
= &outgoing
->cc
;
403 /* Put Reply-To or From in To. */
404 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
405 (ADDRESS
*) NULL
, saved_from
, RCA_ALL
);
407 if(ps
->reply
.preserve_fields
){
409 to_tail
= &(*to_tail
)->next
;
411 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
412 (ADDRESS
*) NULL
, saved_to
, RCA_ALL
);
415 to_tail
= &(*to_tail
)->next
;
417 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
418 outgoing
->to
, saved_resent
, RCA_ALL
);
420 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
421 outgoing
->to
, saved_cc
, RCA_ALL
);
423 else{ /* and the rest in cc */
424 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
425 outgoing
->to
, saved_to
, RCA_ALL
);
426 while(*cc_tail
) /* stay on last address */
427 cc_tail
= &(*cc_tail
)->next
;
429 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
430 outgoing
->to
, saved_cc
, RCA_ALL
);
432 cc_tail
= &(*cc_tail
)->next
;
434 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
435 outgoing
->to
, saved_resent
, RCA_ALL
);
440 /* No From (maybe from us), put To in To. */
441 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
442 (ADDRESS
*)NULL
, saved_to
, RCA_ALL
);
443 /* and the rest in cc */
445 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
446 outgoing
->to
, saved_cc
, RCA_ALL
);
448 cc_tail
= &(*cc_tail
)->next
;
450 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
451 outgoing
->to
, saved_resent
, RCA_ALL
);
455 /* No From or To, put everything else in To if replytoall, */
457 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
458 (ADDRESS
*) NULL
, saved_cc
, RCA_ALL
);
460 to_tail
= &(*to_tail
)->next
;
462 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
463 (ADDRESS
*) NULL
, saved_resent
, RCA_ALL
);
465 /* else, reply to original From which must be us */
468 * Put self in To if in original From.
470 if(!outgoing
->newsgroups
)
471 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
472 (ADDRESS
*) NULL
, env
->from
, RCA_ALL
);
476 /* add any missing personal data */
477 reply_fish_personal(outgoing
, env
);
480 if(fcc
&& outgoing
->to
&& outgoing
->to
->host
[0] != '.'){
481 *fcc
= get_fcc_based_on_to(outgoing
->to
);
483 else if(fcc
&& outgoing
->newsgroups
){
484 char *newsgroups_returned
= NULL
;
487 rv
= news_grouper(outgoing
->newsgroups
, &newsgroups_returned
, errmsg
, fcc
, NULL
);
489 strcmp(outgoing
->newsgroups
, newsgroups_returned
)){
490 fs_give((void **)&outgoing
->newsgroups
);
491 outgoing
->newsgroups
= newsgroups_returned
;
494 fs_give((void **) &newsgroups_returned
);
499 /*----------------------------------------------------------------------
500 Test the given address lists for equivalence
502 Args: x -- First address list for comparison
503 y -- Second address for comparison
507 addr_lists_same(struct mail_address
*x
, struct mail_address
*y
)
509 for(x
= first_addr(x
), y
= first_addr(y
);
511 x
= first_addr(x
->next
), y
= first_addr(y
->next
)){
512 if(!address_is_same(x
, y
))
516 return(!x
&& !y
); /* true if ran off both lists */
520 /*----------------------------------------------------------------------
521 Test the given address against those in the given envelope's to, cc
523 Args: addr -- address for comparison
524 env -- envelope to compare against
528 addr_in_env(struct mail_address
*addr
, ENVELOPE
*env
)
532 for(ap
= env
? env
->to
: NULL
; ap
; ap
= ap
->next
)
533 if(address_is_same(addr
, ap
))
536 for(ap
= env
? env
->cc
: NULL
; ap
; ap
= ap
->next
)
537 if(address_is_same(addr
, ap
))
540 return(0); /* not found! */
544 /*----------------------------------------------------------------------
545 Add missing personal info dest from src envelope
547 Args: dest -- envelope to add personal info to
548 src -- envelope to get personal info from
550 NOTE: This is just kind of a courtesy function. It's really not adding
551 anything needed to get the mail thru, but it is nice for the user
552 under some odd circumstances.
555 reply_fish_personal(ENVELOPE
*dest
, ENVELOPE
*src
)
559 for(da
= dest
? dest
->to
: NULL
; da
; da
= da
->next
){
560 if(da
->personal
&& !da
->personal
[0])
561 fs_give((void **)&da
->personal
);
563 for(sa
= src
? src
->to
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
564 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
565 da
->personal
= cpystr(sa
->personal
);
567 for(sa
= src
? src
->cc
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
568 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
569 da
->personal
= cpystr(sa
->personal
);
572 for(da
= dest
? dest
->cc
: NULL
; da
; da
= da
->next
){
573 if(da
->personal
&& !da
->personal
[0])
574 fs_give((void **)&da
->personal
);
576 for(sa
= src
? src
->to
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
577 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
578 da
->personal
= cpystr(sa
->personal
);
580 for(sa
= src
? src
->cc
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
581 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
582 da
->personal
= cpystr(sa
->personal
);
587 /*----------------------------------------------------------------------
588 Given a header field and envelope, build "References: " header data
595 reply_build_refs(ENVELOPE
*env
)
597 int len
, id_len
, first_ref_len
= 0, foldslop
;
598 char *p
, *refs
= NULL
, *h
= env
->references
;
599 char *first_ref
= NULL
, *tail_refs
= NULL
;
602 if(!(env
->message_id
&& (id_len
= strlen(env
->message_id
))))
607 * The length we have to work with doesn't seem to appear in any
608 * standards. Steve Jones says that in comp.news discussions he
609 * has seen 1024 as the longest length of a header value.
610 * In the inn news source we find MAXHEADERSIZE = 1024. It appears
611 * that is the maximum length of the header value, including
612 * newlines for folded lines (that is, the newlines are counted).
613 * We'll be conservative and figure every reference will take up a
614 * line of its own when we fold. We'll also count 2 for CRLF instead
615 * of just one for LF just to be safe. hubert 2001-jan
616 * J.B. Moreno <planb@newsreaders.com> says "The server limit is
617 * more commonly encountered at 999/1000 bytes [...]". So we'll
618 * back off to 999 instead of 1024.
620 #define MAXHEADERSIZE (999)
622 /* count the total number of potential folds, max of 2 bytes each */
623 for(foldslop
= 2, p
= h
; (p
= strstr(p
+1, "> <")); )
626 if((len
=strlen(h
)) + 1+id_len
+ foldslop
>= MAXHEADERSIZE
627 && (p
= strstr(h
, "> <"))){
629 * If the references line is so long that we are going to have
630 * to delete some of the references, delete the 2nd, 3rd, ...
631 * We don't want to delete the first message in the thread.
634 first_ref
= cpystr(h
);
635 first_ref_len
= strlen(first_ref
)+1; /* len includes space */
638 /* get rid of 2nd, 3rd, ... until it fits */
639 while((len
=strlen(tail_refs
)) + first_ref_len
+ 1+id_len
+
640 foldslop
>= MAXHEADERSIZE
641 && (p
= strstr(tail_refs
, "> <"))){
647 * If the individual references are seriously long, somebody
648 * is messing with us and we don't care if it works right.
650 if((len
=strlen(tail_refs
)) + first_ref_len
+ 1+id_len
+
651 foldslop
>= MAXHEADERSIZE
){
652 first_ref_len
= len
= 0;
655 fs_give((void **)&first_ref
);
661 refs
= (char *)fs_get((first_ref_len
+ 1+id_len
+ len
+ 1) *
663 snprintf(refs
, first_ref_len
+ 1+id_len
+ len
+ 1, "%s%s%s%s%s",
664 first_ref
? first_ref
: "",
665 first_ref
? " " : "",
666 tail_refs
? tail_refs
: "",
667 tail_refs
? " " : "",
669 refs
[first_ref_len
+ 1+id_len
+ len
] = '\0';
673 refs
= cpystr(env
->message_id
);
676 fs_give((void **)&first_ref
);
683 /*----------------------------------------------------------------------
684 Snoop for any Resent-* headers, and return an ADDRESS list
689 Returns: either NULL if no Resent-* or parsed ADDRESS struct list
692 reply_resent(struct pine
*pine_state
, long int msgno
, char *section
)
697 ADDRESS
*rlist
= NULL
, **a
, **b
;
698 char *hdrs
, *values
[RESENTCC
+1];
700 static char *fields
[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL
};
701 static char *fakedomain
= "@";
703 if((hdrs
= pine_fetchheader_lines(pine_state
->mail_stream
,
704 msgno
, section
, fields
)) != NULL
){
705 memset(values
, 0, (RESENTCC
+1) * sizeof(char *));
706 simple_header_parse(hdrs
, fields
, values
);
707 for(i
= RESENTFROM
; i
<= RESENTCC
; i
++)
708 rfc822_parse_adrlist(&rlist
, values
[i
],
709 (F_ON(F_COMPOSE_REJECTS_UNQUAL
, pine_state
))
710 ? fakedomain
: pine_state
->maildomain
);
713 for(a
= &rlist
; *a
; a
= &(*a
)->next
) /* compare every address */
714 for(b
= &(*a
)->next
; *b
; ) /* to the others */
715 if(address_is_same(*a
, *b
)){
718 if(!(*a
)->personal
){ /* preserve personal name */
719 (*a
)->personal
= (*b
)->personal
;
720 (*b
)->personal
= NULL
;
725 mail_free_address(&t
);
732 fs_give((void **) &hdrs
);
738 /*----------------------------------------------------------------------
739 Format and return subject suitable for the reply command
741 Args: subject -- subject to build reply subject for
742 buf -- buffer to use for writing. If non supplied, alloc one.
743 buflen -- length of buf if supplied, else ignored
745 Returns: with either "Re:" prepended or not, if already there.
746 returned string is allocated.
749 reply_subject(char *subject
, char *buf
, size_t buflen
)
751 size_t l
= (subject
&& *subject
) ? 4*strlen(subject
) : 10;
752 char *tmp
= fs_get(l
+ 1), *decoded
, *p
;
756 buf
= fs_get(buflen
);
759 /* decode any 8bit into tmp buffer */
760 decoded
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp
, l
+1, subject
);
763 if(decoded
/* already "re:" ? */
764 && (decoded
[0] == 'R' || decoded
[0] == 'r')
765 && (decoded
[1] == 'E' || decoded
[1] == 'e')){
767 if(decoded
[2] == ':')
768 snprintf(buf
, buflen
, "%.*s", (int)(buflen
-1), subject
);
769 else if((decoded
[2] == '[') && (p
= strchr(decoded
, ']'))){
771 while(*p
&& isspace((unsigned char)*p
)) p
++;
773 snprintf(buf
, buflen
, "%.*s", (int)(buflen
-1), subject
);
778 snprintf(buf
, buflen
, "Re: %.*s", (int)(buflen
-1),
779 (subject
&& *subject
) ? subject
: "your mail");
781 buf
[buflen
-1] = '\0';
783 fs_give((void **) &tmp
);
788 /*----------------------------------------------------------------------
789 return initials for the given personal name
791 Args: name -- Personal name to extract initials from
793 Returns: pointer to name overwritten with initials
796 reply_quote_initials(char *name
)
804 cbuf
.cbuf
[i
= 0] = '\0';
805 cbuf
.cbufp
= cbuf
.cbuf
;
806 cbuf
.cbufend
= cbuf
.cbuf
;
808 /* while there are still characters to look at */
810 /* skip to next initial */
811 while(*s
&& (unsigned char) *s
== ' ')
814 if(!utf8_to_ucs4_oneatatime((unsigned char) *s
++ & 0xff, &cbuf
, &ucs
, NULL
)){
819 /* skip over non-alpha stuff */
820 if(i
== 0 && !isalnum((unsigned char) cbuf
.cbuf
[0]))
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
!= ' ')
831 cbuf
.cbuf
[i
= 0] = '\0';
832 cbuf
.cbufp
= cbuf
.cbuf
;
833 cbuf
.cbufend
= cbuf
.cbuf
;
843 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
844 * tokens being substituted for (only in the size of buf below).
846 #define MAX_SUBSTITUTION 6
847 #define MAX_PREFIX 63
848 static char *from_token
= "_FROM_";
849 static char *nick_token
= "_NICK_";
850 static char *init_token
= "_INIT_";
852 /*----------------------------------------------------------------------
853 return a quoting string, "> " by default, for replied text
855 Args: env -- envelope of message being replied to
857 Returns: malloc'd array containing quoting string, freed by caller
860 reply_quote_str(ENVELOPE
*env
)
862 char *prefix
, *repl
, *p
, buf
[MAX_PREFIX
+1], pbf
[MAX_SUBSTITUTION
+1];
864 strncpy(buf
, ps_global
->VAR_REPLY_STRING
, sizeof(buf
)-1);
865 buf
[sizeof(buf
)-1] = '\0';
867 /* set up the prefix to quote included text */
868 if((p
= strstr(buf
, from_token
)) != NULL
){
869 repl
= (env
&& env
->from
&& env
->from
->mailbox
) ? env
->from
->mailbox
871 strncpy(pbf
, repl
, sizeof(pbf
)-1);
872 pbf
[sizeof(pbf
)-1] = '\0';;
873 rplstr(p
, sizeof(buf
)-(p
-buf
), strlen(from_token
), pbf
);
876 if((p
= strstr(buf
, nick_token
)) != NULL
){
880 get_nickname_from_addr(env
->from
, tmp_20k_buf
, 1000))
882 strncpy(pbf
, repl
, sizeof(pbf
)-1);
883 pbf
[sizeof(pbf
)-1] = '\0';;
884 rplstr(p
, sizeof(buf
)-(p
-buf
), strlen(nick_token
), pbf
);
887 if((p
= strstr(buf
, init_token
)) != NULL
){
889 char buftmp
[MAILTMPLEN
];
891 snprintf(buftmp
, sizeof(buftmp
), "%.200s",
892 (env
&& env
->from
&& env
->from
->personal
) ? env
->from
->personal
: "");
893 buftmp
[sizeof(buftmp
)-1] = '\0';
895 repl
= (env
&& env
->from
&& env
->from
->personal
)
896 ? reply_quote_initials(q
= cpystr((char *)rfc1522_decode_to_utf8(
897 (unsigned char *)tmp_20k_buf
,
898 SIZEOF_20KBUF
, buftmp
)))
901 istrncpy(pbf
, repl
, sizeof(pbf
)-1);
902 pbf
[sizeof(pbf
)-1] = '\0';;
903 rplstr(p
, sizeof(buf
)-(p
-buf
), strlen(init_token
), pbf
);
905 fs_give((void **)&q
);
908 prefix
= removing_quotes(cpystr(buf
));
914 reply_quote_str_contains_tokens(void)
916 return(ps_global
->VAR_REPLY_STRING
&& ps_global
->VAR_REPLY_STRING
[0] &&
917 (strstr(ps_global
->VAR_REPLY_STRING
, from_token
) ||
918 strstr(ps_global
->VAR_REPLY_STRING
, nick_token
) ||
919 strstr(ps_global
->VAR_REPLY_STRING
, init_token
)));
923 /*----------------------------------------------------------------------
924 Build the body for the message number/part being replied to
928 Result: BODY structure suitable for sending
930 ----------------------------------------------------------------------*/
932 reply_body(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
933 long int msgno
, char *sect_prefix
, void *msgtext
, char *prefix
,
934 int plustext
, ACTION_S
*role
, int toplevel
, REDRAFT_POS_S
**redraft_pos
)
936 #define SECTBUFLEN 256
937 char *p
, *sig
= NULL
, *section
, sect_buf
[SECTBUFLEN
];
938 BODY
*body
= NULL
, *tmp_body
= NULL
;
941 int impl
, template_len
= 0, leave_cursor_at_top
= 0, reply_raw_body
= 0;
943 if(sect_prefix
) /* SECTBUFLEN = sizeof(sect_buf) */
944 snprintf(section
= sect_buf
, sizeof(sect_buf
), "%.*s.1", SECTBUFLEN
-3, sect_prefix
);
948 sect_buf
[sizeof(sect_buf
)-1] = '\0';
950 if(ps_global
->full_header
== 2
951 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT
, ps_global
))
954 gf_set_so_writec(&pc
, (STORE_S
*) msgtext
);
960 filtered
= detoken(role
, env
, 0,
961 ps_global
->reply
.signature_bottom
,
962 0, redraft_pos
, &impl
);
965 so_puts((STORE_S
*)msgtext
, filtered
);
967 template_len
= strlen(filtered
);
969 leave_cursor_at_top
++;
972 fs_give((void **)&filtered
);
981 (sig
= reply_signature(role
, env
, redraft_pos
, &impl
)) &&
982 !ps_global
->reply
.signature_bottom
){
985 * If CURSORPOS was set explicitly in sig_file, and there was a
986 * template file before that, we need to adjust the offset by the
987 * length of the template file. However, if the template had
988 * a set CURSORPOS in it then impl was 2 before getting to the
989 * signature, so offset wouldn't have been reset by the signature
990 * CURSORPOS and offset would already be correct. That case will
991 * be ok here because template_len will be 0 and adding it does
992 * nothing. If template
993 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
994 * by the CURSORPOS in the sig. In that case we have to adjust the
995 * offset. That's what the next line does. It adjusts it if
996 * template_len is nonzero and if CURSORPOS was set in sig_file.
999 (*redraft_pos
)->offset
+= template_len
;
1002 so_puts((STORE_S
*)msgtext
, sig
);
1005 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
1006 * is set, we won't have used it yet and want it to be non-NULL.
1008 fs_give((void **)&sig
);
1012 * Only put cursor in sig if there is a cursorpos there but not
1013 * one in the template, and sig-at-bottom.
1015 if(!(sig
&& impl
== 2 && !leave_cursor_at_top
))
1016 leave_cursor_at_top
++;
1020 || orig_body
->type
== TYPETEXT
1022 || !ps_global
->reply
.keep_attach
){
1023 char *charset
= NULL
;
1025 /*------ Simple text-only message ----*/
1026 body
= mail_newbody();
1027 body
->type
= TYPETEXT
;
1028 body
->contents
.text
.data
= msgtext
;
1029 reply_delimiter(env
, role
, pc
);
1030 if(ps_global
->reply
.include_header
)
1031 reply_forward_header(stream
, msgno
, sect_prefix
,
1034 if(!orig_body
|| reply_raw_body
|| reply_body_text(orig_body
, &tmp_body
)){
1037 bodyp
= reply_raw_body
? NULL
: tmp_body
;
1040 * We set the charset in the outgoing message to the same
1041 * as the one in the message we're replying to unless it
1042 * is the unknown charset. We do that in order to attempt
1043 * to preserve the same charset in the reply if possible.
1044 * It may be safer to just set it to whatever we want instead
1045 * but then the receiver may not be able to read it.
1048 && (charset
= parameter_val(bodyp
->parameter
, "charset"))
1049 && strucmp(charset
, UNKNOWN_CHARSET
))
1050 set_parameter(&body
->parameter
, "charset", charset
);
1053 fs_give((void **) &charset
);
1055 get_body_part_text(stream
, bodyp
, msgno
,
1056 bodyp
? (p
= body_partno(stream
, msgno
, bodyp
))
1058 0L, pc
, prefix
, NULL
, GBPT_NONE
);
1060 fs_give((void **) &p
);
1063 gf_puts(NEWLINE
, pc
);
1064 gf_puts(" [NON-Text Body part not included]", pc
);
1065 gf_puts(NEWLINE
, pc
);
1068 else if(orig_body
->type
== TYPEMULTIPART
){
1069 /*------ Message is Multipart ------*/
1070 if(orig_body
->subtype
1071 && (!strucmp(orig_body
->subtype
, "signed")
1073 || !strucmp(orig_body
->subtype
, OUR_PKCS7_ENCLOSURE_SUBTYPE
)
1076 && orig_body
->nested
.part
){
1077 /* operate only on the signed part */
1078 body
= reply_body(stream
, env
,
1079 &orig_body
->nested
.part
->body
,
1080 msgno
, section
, msgtext
, prefix
,
1081 plustext
, role
, 0, redraft_pos
);
1083 else if(orig_body
->subtype
1084 && !strucmp(orig_body
->subtype
, "mixed")
1085 && orig_body
->nested
.part
1086 && orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
1087 && orig_body
->nested
.part
->body
.subtype
1088 && (!strucmp(orig_body
->nested
.part
->body
.subtype
, "signed")
1090 || !strucmp(orig_body
->nested
.part
->body
.subtype
, OUR_PKCS7_ENCLOSURE_SUBTYPE
)
1093 /* we can call reply_body as in the call above with section
1094 * equal to "1", but that adds the multipart text to the
1095 * list of attachments. We do not want that, so we redo this
1098 body
= copy_body(NULL
, orig_body
);
1101 * whatever subtype it is, demote it
1102 * to plain old MIXED.
1105 fs_give((void **) &body
->subtype
);
1107 body
->subtype
= cpystr("Mixed");
1109 if(reply_body_text(&orig_body
->nested
.part
->body
.nested
.part
->body
,
1113 reply_delimiter(env
, role
, pc
);
1114 if(ps_global
->reply
.include_header
)
1115 reply_forward_header(stream
, msgno
, sect_prefix
,
1117 /* SECTBUFLEN = sizeof(sect_buf) */
1118 snprintf(sect_buf
, sizeof(sect_buf
), "%.*s%s%.*s",
1120 sect_prefix
? sect_prefix
: "",
1121 sect_prefix
? "." : "",
1123 p
= partno(orig_body
, tmp_body
));
1124 sect_buf
[sizeof(sect_buf
)-1] = '\0';
1125 fs_give((void **) &p
);
1126 get_body_part_text(stream
, tmp_body
, msgno
,
1127 sect_buf
, 0L, pc
, prefix
,
1130 part
= body
->nested
.part
->next
;
1131 body
->nested
.part
->next
= NULL
;
1132 mail_free_body_part(&body
->nested
.part
);
1133 body
->nested
.part
= mail_newbody_part();
1134 body
->nested
.part
->body
.type
= TYPETEXT
;
1135 body
->nested
.part
->body
.subtype
= cpystr("Plain");
1136 body
->nested
.part
->body
.contents
.text
.data
= msgtext
;
1137 body
->nested
.part
->next
= part
;
1138 /* SECTBUFLEN = sizeof(sect_buf) */
1139 for(partnum
= 2; part
!= NULL
; part
= part
->next
){
1140 snprintf(sect_buf
, sizeof(sect_buf
), "%.*s%s%d",
1142 sect_prefix
? sect_prefix
: "",
1143 sect_prefix
? "." : "", partnum
++);
1144 sect_buf
[sizeof(sect_buf
)-1] = '\0';
1146 if(!fetch_contents(stream
, msgno
,
1147 sect_buf
, &part
->body
)){
1153 /*--- Fetch the original pieces ---*/
1154 if(!fetch_contents(stream
, msgno
, sect_prefix
, body
))
1155 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1156 _("Error including all message parts"));
1158 /*--- No text part, create a blank one ---*/
1159 part
= mail_newbody_part();
1160 part
->next
= body
->nested
.part
;
1161 body
->nested
.part
= part
;
1162 part
->body
.contents
.text
.data
= msgtext
;
1165 else if(orig_body
->subtype
1166 && !strucmp(orig_body
->subtype
, "alternative")
1167 && orig_body
->nested
.part
1168 && orig_body
->nested
.part
->next
1169 && orig_body
->nested
.part
->next
->body
.type
== TYPEMULTIPART
1170 && orig_body
->nested
.part
->next
->body
.subtype
1171 && !strucmp(orig_body
->nested
.part
->next
->body
.subtype
, "MIXED"))
1172 body
= reply_body(stream
, env
,
1173 &orig_body
->nested
.part
->next
->body
,
1174 msgno
, "2", msgtext
, prefix
,
1175 plustext
, role
, 0, redraft_pos
);
1176 else if(orig_body
->subtype
1177 && !strucmp(orig_body
->subtype
, "alternative")){
1178 /* Set up the simple text reply */
1179 body
= mail_newbody();
1180 body
->type
= TYPETEXT
;
1181 body
->contents
.text
.data
= msgtext
;
1183 if(reply_body_text(orig_body
, &tmp_body
)){
1184 reply_delimiter(env
, role
, pc
);
1185 if(ps_global
->reply
.include_header
)
1186 reply_forward_header(stream
, msgno
, sect_prefix
,
1189 get_body_part_text(stream
, tmp_body
, msgno
,
1190 p
= body_partno(stream
,msgno
,tmp_body
),
1191 0L, pc
, prefix
, NULL
, GBPT_NONE
);
1193 fs_give((void **) &p
);
1196 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
1197 "No suitable multipart text found for inclusion!");
1200 body
= copy_body(NULL
, orig_body
);
1203 * whatever subtype it is, demote it
1204 * to plain old MIXED.
1207 fs_give((void **) &body
->subtype
);
1209 body
->subtype
= cpystr("Mixed");
1211 if(body
->nested
.part
&&
1212 body
->nested
.part
->body
.type
== TYPETEXT
) {
1213 char *new_charset
= NULL
;
1215 /*---- First part of the message is text -----*/
1216 body
->nested
.part
->body
.contents
.text
.data
= msgtext
;
1217 if(body
->nested
.part
->body
.subtype
&&
1218 strucmp(body
->nested
.part
->body
.subtype
, "Plain")){
1219 fs_give((void **)&body
->nested
.part
->body
.subtype
);
1220 body
->nested
.part
->body
.subtype
= cpystr("Plain");
1222 reply_delimiter(env
, role
, pc
);
1223 if(ps_global
->reply
.include_header
)
1224 reply_forward_header(stream
, msgno
, sect_prefix
,
1227 if(!(get_body_part_text(stream
,
1228 &orig_body
->nested
.part
->body
,
1229 msgno
, section
, 0L, pc
, prefix
,
1230 &new_charset
, GBPT_NONE
)
1231 && fetch_contents(stream
, msgno
, sect_prefix
, body
)))
1232 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1233 _("Error including all message parts"));
1234 else if(new_charset
)
1235 set_parameter(&body
->nested
.part
->body
.parameter
, "charset", new_charset
);
1237 else if(orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
1238 && orig_body
->nested
.part
->body
.subtype
1239 && !strucmp(orig_body
->nested
.part
->body
.subtype
,
1241 && reply_body_text(&orig_body
->nested
.part
->body
,
1245 reply_delimiter(env
, role
, pc
);
1246 if(ps_global
->reply
.include_header
)
1247 reply_forward_header(stream
, msgno
, sect_prefix
,
1249 /* SECTBUFLEN = sizeof(sect_buf) */
1250 snprintf(sect_buf
, sizeof(sect_buf
), "%.*s%s%.*s",
1252 sect_prefix
? sect_prefix
: "",
1253 sect_prefix
? "." : "",
1255 p
= partno(orig_body
, tmp_body
));
1256 sect_buf
[sizeof(sect_buf
)-1] = '\0';
1257 fs_give((void **) &p
);
1258 get_body_part_text(stream
, tmp_body
, msgno
,
1259 sect_buf
, 0L, pc
, prefix
,
1262 part
= body
->nested
.part
->next
;
1263 body
->nested
.part
->next
= NULL
;
1264 mail_free_body_part(&body
->nested
.part
);
1265 body
->nested
.part
= mail_newbody_part();
1266 body
->nested
.part
->body
.type
= TYPETEXT
;
1267 body
->nested
.part
->body
.subtype
= cpystr("Plain");
1268 body
->nested
.part
->body
.contents
.text
.data
= msgtext
;
1269 body
->nested
.part
->next
= part
;
1270 /* SECTBUFLEN = sizeof(sect_buf) */
1271 for(partnum
= 2; part
!= NULL
; part
= part
->next
){
1272 snprintf(sect_buf
, sizeof(sect_buf
), "%.*s%s%d",
1274 sect_prefix
? sect_prefix
: "",
1275 sect_prefix
? "." : "", partnum
++);
1276 sect_buf
[sizeof(sect_buf
)-1] = '\0';
1278 if(!fetch_contents(stream
, msgno
,
1279 sect_buf
, &part
->body
)){
1285 /*--- Fetch the original pieces ---*/
1286 if(!fetch_contents(stream
, msgno
, sect_prefix
, body
))
1287 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1288 _("Error including all message parts"));
1290 /*--- No text part, create a blank one ---*/
1291 part
= mail_newbody_part();
1292 part
->next
= body
->nested
.part
;
1293 body
->nested
.part
= part
;
1294 part
->body
.contents
.text
.data
= msgtext
;
1299 /*---- Single non-text message of some sort ----*/
1300 body
= mail_newbody();
1301 body
->type
= TYPEMULTIPART
;
1302 part
= mail_newbody_part();
1303 body
->nested
.part
= part
;
1305 /*--- The first part, a blank text part to be edited ---*/
1306 part
->body
.type
= TYPETEXT
;
1307 part
->body
.contents
.text
.data
= msgtext
;
1309 /*--- The second part, what ever it is ---*/
1310 part
->next
= mail_newbody_part();
1312 part
->body
.id
= generate_message_id(NULL
);
1313 copy_body(&(part
->body
), orig_body
);
1316 * the idea here is to fetch part into storage object
1318 if((part
->body
.contents
.text
.data
= (void *) so_get(PART_SO_TYPE
,
1319 NULL
,EDIT_ACCESS
)) != NULL
){
1320 if((p
= pine_mail_fetch_body(stream
, msgno
, section
,
1321 &part
->body
.size
.bytes
, NIL
)) != NULL
){
1322 so_nputs((STORE_S
*)part
->body
.contents
.text
.data
,
1323 p
, part
->body
.size
.bytes
);
1326 mail_free_body(&body
);
1329 mail_free_body(&body
);
1333 /*--------- No text included --------*/
1334 body
= mail_newbody();
1335 body
->type
= TYPETEXT
;
1336 body
->contents
.text
.data
= msgtext
;
1339 if(!leave_cursor_at_top
){
1343 /* rewind and count chars to start of sig file */
1344 so_seek((STORE_S
*)msgtext
, 0L, 0);
1345 while(so_readc(&c
, (STORE_S
*)msgtext
))
1349 *redraft_pos
= (REDRAFT_POS_S
*)fs_get(sizeof(**redraft_pos
));
1350 memset((void *)*redraft_pos
, 0,sizeof(**redraft_pos
));
1351 (*redraft_pos
)->hdrname
= cpystr(":");
1355 * If explicit cursor positioning in sig file,
1356 * add offset to start of sig file plus offset into sig file.
1357 * Else, just offset to start of sig file.
1359 (*redraft_pos
)->offset
+= cnt
;
1364 so_puts((STORE_S
*)msgtext
, sig
);
1366 fs_give((void **)&sig
);
1369 gf_clear_so_writec((STORE_S
*) msgtext
);
1376 * reply_part - first replyable multipart of a multipart.
1379 reply_body_text(struct mail_bodystruct
*body
, struct mail_bodystruct
**new_body
)
1387 case TYPEMULTIPART
:
1388 if(body
->subtype
&& !strucmp(body
->subtype
, "alternative")){
1392 if((part
= body
->nested
.part
) != NULL
1393 && part
->body
.type
== TYPEMULTIPART
)
1394 return reply_body_text(&body
->nested
.part
->body
, new_body
);
1396 if(ps_global
->force_prefer_plain
1397 || (!ps_global
->force_no_prefer_plain
1398 && F_ON(F_PREFER_PLAIN_TEXT
, ps_global
))){
1399 for(part
= body
->nested
.part
; part
; part
= part
->next
)
1400 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
1401 && (!part
->body
.subtype
1402 || !strucmp(part
->body
.subtype
, "plain"))){
1403 *new_body
= &part
->body
;
1409 * Else choose last alternative among plain or html parts.
1410 * Perhaps we should really be using mime_show() to make this
1411 * potentially more general than just plain or html.
1413 for(part
= body
->nested
.part
; part
; part
= part
->next
){
1414 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
1415 && ((!part
->body
.subtype
|| !strucmp(part
->body
.subtype
, "plain"))
1417 (part
->body
.subtype
&& !strucmp(part
->body
.subtype
, "html")))){
1419 *new_body
= &part
->body
;
1426 else if(body
->nested
.part
)
1427 /* NOTE: we're only interested in "first" part of mixed */
1428 return(reply_body_text(&body
->nested
.part
->body
, new_body
));
1442 reply_signature(ACTION_S
*role
, ENVELOPE
*env
, REDRAFT_POS_S
**redraft_pos
, int *impl
)
1447 sig
= detoken(role
, env
,
1448 2, ps_global
->reply
.signature_bottom
? 0 : 1, 1,
1451 if(!ps_global
->reply
.signature_bottom
&& (!sig
|| !*sig
)){
1453 fs_give((void **)&sig
);
1455 l
= 2 * strlen(NEWLINE
);
1456 sig
= (char *)fs_get((l
+1) * sizeof(char));
1457 strncpy(sig
, NEWLINE
, l
);
1459 strncat(sig
, NEWLINE
, l
+1-1-strlen(sig
));
1469 * Buf is at least size maxlen+1
1472 get_addr_data(ENVELOPE
*env
, IndexColType type
, char *buf
, size_t maxlen
)
1474 ADDRESS
*addr
= NULL
;
1475 ADDRESS
*last_to
= NULL
;
1476 ADDRESS
*first_addr
= NULL
, *second_addr
= NULL
;
1477 ADDRESS
*third_addr
= NULL
, *fourth_addr
= NULL
;
1486 addr
= env
? env
->from
: NULL
;
1490 addr
= env
? env
->to
: NULL
;
1494 addr
= env
? env
->cc
: NULL
;
1498 addr
= env
? env
->sender
: NULL
;
1502 * Recips is To and Cc together. We hook the two adrlists together
1506 addr
= env
? env
->to
: NULL
;
1507 /* Find end of To list */
1508 for(last_to
= addr
; last_to
&& last_to
->next
; last_to
= last_to
->next
)
1511 /* Make the end of To list point to cc list */
1513 last_to
->next
= (env
? env
->cc
: NULL
);
1521 if(env
&& env
->from
&& env
->from
->personal
){
1522 char *name
, *initials
= NULL
;
1524 name
= (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
1525 SIZEOF_20KBUF
, env
->from
->personal
);
1526 if(name
== env
->from
->personal
){
1527 strncpy(tmp_20k_buf
, name
, SIZEOF_20KBUF
-1);
1528 tmp_20k_buf
[SIZEOF_20KBUF
- 1] = '\0';
1533 initials
= reply_quote_initials(name
);
1534 iutf8ncpy(buf
, initials
, maxlen
);
1545 orig_maxlen
= maxlen
;
1548 /* skip over rest of c-client group addr */
1549 if(first_addr
&& first_addr
->mailbox
&& !first_addr
->host
){
1550 for(second_addr
= first_addr
->next
;
1551 second_addr
&& second_addr
->host
;
1552 second_addr
= second_addr
->next
)
1555 if(second_addr
&& !second_addr
->host
)
1556 second_addr
= second_addr
->next
;
1558 else if(!(first_addr
&& first_addr
->host
&& first_addr
->host
[0] == '.'))
1559 second_addr
= first_addr
? first_addr
->next
: NULL
;
1561 if(second_addr
&& second_addr
->mailbox
&& !second_addr
->host
){
1562 for(third_addr
= second_addr
->next
;
1563 third_addr
&& third_addr
->host
;
1564 third_addr
= third_addr
->next
)
1567 if(third_addr
&& !third_addr
->host
)
1568 third_addr
= third_addr
->next
;
1570 else if(!(second_addr
&& second_addr
->host
&& second_addr
->host
[0] == '.'))
1571 third_addr
= second_addr
? second_addr
->next
: NULL
;
1573 if(third_addr
&& third_addr
->mailbox
&& !third_addr
->host
){
1574 for(fourth_addr
= third_addr
->next
;
1575 fourth_addr
&& fourth_addr
->host
;
1576 fourth_addr
= fourth_addr
->next
)
1579 if(fourth_addr
&& !fourth_addr
->host
)
1580 fourth_addr
= fourth_addr
->next
;
1582 else if(!(third_addr
&& third_addr
->host
&& third_addr
->host
[0] == '.'))
1583 fourth_addr
= third_addr
? third_addr
->next
: NULL
;
1585 /* Just attempting to make a nice display */
1586 if(first_addr
&& ((first_addr
->personal
&& first_addr
->personal
[0]) ||
1587 (first_addr
->mailbox
&& first_addr
->mailbox
[0]))){
1589 if((second_addr
->personal
&& second_addr
->personal
[0]) ||
1590 (second_addr
->mailbox
&& second_addr
->mailbox
[0])){
1592 if((third_addr
->personal
&& third_addr
->personal
[0]) ||
1593 (third_addr
->mailbox
&& third_addr
->mailbox
[0])){
1616 a_little_addr_string(first_addr
, p
, maxlen
);
1617 else if(cntaddr
== 2){
1618 a_little_addr_string(first_addr
, p
, maxlen
);
1619 maxlen
-= (l
=strlen(p
));
1622 strncpy(p
, " and ", maxlen
);
1625 a_little_addr_string(second_addr
, p
, maxlen
);
1628 else if(cntaddr
== 3){
1629 a_little_addr_string(first_addr
, p
, maxlen
);
1630 maxlen
-= (l
=strlen(p
));
1633 strncpy(p
, ", ", maxlen
);
1636 a_little_addr_string(second_addr
, p
, maxlen
);
1637 maxlen
-= (l
=strlen(p
));
1640 strncpy(p
, ", and ", maxlen
);
1643 a_little_addr_string(third_addr
, p
, maxlen
);
1647 else if(cntaddr
> 3){
1648 a_little_addr_string(first_addr
, p
, maxlen
);
1649 maxlen
-= (l
=strlen(p
));
1652 strncpy(p
, ", ", maxlen
);
1655 a_little_addr_string(second_addr
, p
, maxlen
);
1656 maxlen
-= (l
=strlen(p
));
1659 strncpy(p
, ", ", maxlen
);
1662 a_little_addr_string(third_addr
, p
, maxlen
);
1663 maxlen
-= (l
=strlen(p
));
1666 strncpy(p
, ", and others", maxlen
);
1667 else if(maxlen
>= 3)
1668 strncpy(p
, "...", maxlen
);
1675 a_string
= addr_list_string(addr
, NULL
, 0);
1676 iutf8ncpy(buf
, a_string
, maxlen
);
1678 fs_give((void **)&a_string
);
1682 last_to
->next
= NULL
;
1684 buf
[orig_maxlen
] = '\0';
1689 * Buf is at least size maxlen+1
1692 get_news_data(ENVELOPE
*env
, IndexColType type
, char *buf
, size_t maxlen
)
1694 int cntnews
= 0, orig_maxlen
;
1695 char *news
= NULL
, *p
, *q
;
1701 case iNewsAndRecips
:
1702 case iRecipsAndNews
:
1703 news
= env
? env
->newsgroups
: NULL
;
1707 if(ps_global
->mail_stream
&& IS_NEWS(ps_global
->mail_stream
))
1708 news
= ps_global
->cur_folder
;
1716 orig_maxlen
= maxlen
;
1720 while(isspace((unsigned char)*q
))
1726 while((q
= strindex(q
, ',')) != NULL
){
1728 while(isspace((unsigned char)*q
))
1739 istrncpy(buf
, news
, maxlen
);
1741 removing_leading_and_trailing_white_space(buf
);
1743 else if(cntnews
== 2){
1746 while(isspace((unsigned char)*q
))
1749 while(maxlen
> 0 && *q
&& !isspace((unsigned char)*q
) && *q
!= ','){
1755 strncpy(p
, " and ", maxlen
);
1760 while(isspace((unsigned char)*q
) || *q
== ',')
1763 while(maxlen
> 0 && *q
&& !isspace((unsigned char)*q
) && *q
!= ','){
1770 istrncpy(tmp_20k_buf
, buf
, 10000);
1771 strncpy(buf
, tmp_20k_buf
, orig_maxlen
);
1773 else if(cntnews
> 2){
1778 while(isspace((unsigned char)*q
))
1781 while(maxlen
> 0 && *q
&& !isspace((unsigned char)*q
) && *q
!= ','){
1787 snprintf(b
, sizeof(b
), " and %d other newsgroups", cntnews
-1);
1788 b
[sizeof(b
)-1] = '\0';
1789 if(maxlen
>= strlen(b
))
1790 strncpy(p
, b
, maxlen
);
1791 else if(maxlen
>= 3)
1792 strncpy(p
, "...", maxlen
);
1794 buf
[orig_maxlen
] = '\0';
1796 istrncpy(tmp_20k_buf
, buf
, 10000);
1797 tmp_20k_buf
[10000-1] = '\0';
1798 strncpy(buf
, tmp_20k_buf
, orig_maxlen
);
1801 buf
[orig_maxlen
] = '\0';
1805 shorten_subject(char *origsubj
)
1810 if(origsubj
== NULL
|| *origsubj
== '\0')
1813 for(t
=s
=origsubj
; *s
; s
++){
1815 /* this transforms "A [B [C] D" into "A D" should this be
1818 case '[' : if((u
= strchr(s
+1,']')) != NULL
){
1825 case ' ' : if(endlist
== 0) *t
++ = *s
; break;
1826 default : endlist
= 0; *t
++ = *s
; break;
1833 * Buf is at least size maxlen+1
1836 get_reply_data(ENVELOPE
*env
, ACTION_S
*role
, IndexColType type
, char *buf
, size_t maxlen
)
1839 IndexColType addrtype
;
1844 case iRDate
: case iSDate
: case iSTime
: case iSTime24
:
1845 case iS1Date
: case iS2Date
: case iS3Date
: case iS4Date
:
1846 case iSDateIso
: case iSDateIsoS
:
1847 case iSDateS1
: case iSDateS2
: case iSDateS3
: case iSDateS4
:
1849 case iSDateTimeIso
: case iSDateTimeIsoS
:
1850 case iSDateTimeS1
: case iSDateTimeS2
: case iSDateTimeS3
: case iSDateTimeS4
:
1852 case iSDateTimeIso24
: case iSDateTimeIsoS24
:
1853 case iSDateTimeS124
: case iSDateTimeS224
: case iSDateTimeS324
: case iSDateTimeS424
:
1854 case iDateIso
: case iDateIsoS
: case iTime24
: case iTime12
:
1855 case iDay
: case iDayOrdinal
: case iDay2Digit
:
1856 case iMonAbb
: case iMonLong
: case iMon
: case iMon2Digit
:
1857 case iYear
: case iYear2Digit
:
1858 case iDate
: case iLDate
:
1859 case iTimezone
: case iDayOfWeekAbb
: case iDayOfWeek
:
1860 case iPrefDate
: case iPrefTime
: case iPrefDateTime
:
1861 if(env
&& env
->date
&& env
->date
[0] && maxlen
>= 20)
1862 date_str((char *) env
->date
, type
, 1, buf
, maxlen
+1, 0);
1874 case iCurDayOfWeekAbb
:
1880 case iCurYear2Digit
:
1882 case iCurPrefDateTime
:
1889 case iLstMonYear2Digit
:
1891 case iLstYear2Digit
:
1893 date_str(NULL
, type
, 1, buf
, maxlen
+1, 0);
1903 get_addr_data(env
, type
, buf
, maxlen
);
1907 if(role
&& role
->nick
){
1908 strncpy(buf
, role
->nick
, maxlen
);
1914 if(maxlen
>= strlen(NEWLINE
)){
1915 strncpy(buf
, NEWLINE
, maxlen
);
1922 if(env
&& env
->from
&& env
->from
->mailbox
&& env
->from
->mailbox
[0] &&
1923 strlen(env
->from
->mailbox
) <= maxlen
){
1924 strncpy(buf
, env
->from
->mailbox
, maxlen
);
1926 if(type
== iAddress
&&
1928 env
->from
->host
[0] &&
1929 env
->from
->host
[0] != '.' &&
1930 strlen(buf
) + strlen(env
->from
->host
) + 1 <= maxlen
){
1931 strncat(buf
, "@", maxlen
+1-1-strlen(buf
));
1933 strncat(buf
, env
->from
->host
, maxlen
+1-1-strlen(buf
));
1942 get_news_data(env
, type
, buf
, maxlen
);
1947 case iRecipsAndNews
:
1948 case iNewsAndRecips
:
1949 if(type
== iToAndNews
|| type
== iNewsAndTo
)
1954 if(env
&& env
->newsgroups
){
1955 space
= (char *)fs_get((maxlen
+1) * sizeof(char));
1956 get_news_data(env
, type
, space
, maxlen
);
1959 get_addr_data(env
, addrtype
, buf
, maxlen
);
1961 if(space
&& *space
&& *buf
){
1962 if(strlen(space
) + strlen(buf
) + 5 > maxlen
){
1963 if(strlen(space
) > maxlen
/2)
1964 get_news_data(env
, type
, space
, maxlen
- strlen(buf
) - 5);
1966 get_addr_data(env
, addrtype
, buf
, maxlen
- strlen(space
) - 5);
1969 if(type
== iToAndNews
|| type
== iRecipsAndNews
)
1970 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s and %s", buf
, space
);
1972 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s and %s", space
, buf
);
1974 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
1976 strncpy(buf
, tmp_20k_buf
, maxlen
);
1979 else if(space
&& *space
){
1980 strncpy(buf
, space
, maxlen
);
1985 fs_give((void **)&space
);
1991 if(env
&& env
->subject
){
1993 unsigned char *p
, *tmp
= NULL
;
1995 if((n
= 4*strlen(env
->subject
)) > SIZEOF_20KBUF
-1){
1997 p
= tmp
= (unsigned char *)fs_get(len
* sizeof(char));
2000 len
= SIZEOF_20KBUF
;
2001 p
= (unsigned char *)tmp_20k_buf
;
2004 istrncpy(buf
, (char *)rfc1522_decode_to_utf8(p
, len
, env
->subject
), maxlen
);
2009 fs_give((void **)&tmp
);
2011 if(type
== iShortSubject
)
2012 shorten_subject(buf
);
2018 if(env
&& env
->message_id
){
2019 strncpy(buf
, env
->message_id
, maxlen
);
2035 * reply_delimiter - output formatted reply delimiter for given envelope
2036 * with supplied character writing function.
2039 reply_delimiter(ENVELOPE
*env
, ACTION_S
*role
, gf_io_t pc
)
2041 #define MAX_DELIM 2000
2042 char buf
[MAX_DELIM
+1];
2044 char *filtered
= NULL
;
2045 int contains_newline_token
= 0;
2051 strncpy(buf
, ps_global
->VAR_REPLY_INTRO
, MAX_DELIM
);
2052 buf
[MAX_DELIM
] = '\0';
2053 /* preserve exact default behavior from before */
2054 if(!strcmp(buf
, DEFAULT_REPLY_INTRO
)){
2058 parse_date((char *) env
->date
, &d
);
2059 include_date
= !(d
.day
== -1 || d
.month
== -1 || d
.year
== -1);
2061 gf_puts("On ", pc
); /* All delims have... */
2062 if(d
.wkday
!= -1){ /* "On day, date month year" */
2063 gf_puts(day_abbrev(d
.wkday
), pc
); /* in common */
2067 gf_puts(int2string(d
.day
), pc
);
2069 gf_puts(month_abbrev(d
.month
), pc
);
2071 gf_puts(int2string(d
.year
), pc
);
2075 && ((env
->from
->personal
&& env
->from
->personal
[0])
2076 || (env
->from
->mailbox
&& env
->from
->mailbox
[0]))){
2077 char buftmp
[MAILTMPLEN
];
2079 a_little_addr_string(env
->from
, buftmp
, sizeof(buftmp
)-1);
2083 gf_puts(buftmp
, pc
);
2084 gf_puts(" wrote:", pc
);
2088 gf_puts(", it was written", pc
);
2090 gf_puts("It was written", pc
);
2096 * This is here for backwards compatibility. There didn't used
2097 * to be a _NEWLINE_ token. The user would enter text that should
2098 * all fit on one line and then that was followed by two newlines.
2099 * Also, truncation occurs if it is long.
2100 * Now, if _NEWLINE_ is not in the text, same thing still works
2101 * the same. However, if _NEWLINE_ is in there, then all bets are
2102 * off and the user is on his or her own. No automatic newlines
2103 * are added, only those that come from the tokens. No truncation
2104 * is done, the user is trusted to get it right. Newlines may be
2105 * embedded so that the leadin is multi-line.
2107 contains_newline_token
= (strstr(buf
, "_NEWLINE_") != NULL
);
2108 filtered
= detoken_src(buf
, FOR_REPLY_INTRO
, env
, role
,
2111 /* try to truncate if too long */
2112 if(!contains_newline_token
&& filtered
&& utf8_width(filtered
) > 80){
2113 int ended_with_colon
= 0;
2114 int ended_with_quote
= 0;
2115 int ended_with_quote_colon
= 0;
2118 l
= strlen(filtered
);
2120 if(filtered
[l
-1] == ':'){
2121 ended_with_colon
= ':';
2122 if(filtered
[l
-2] == QUOTE
|| filtered
[l
-2] == '\'')
2123 ended_with_quote_colon
= filtered
[l
-2];
2125 else if(filtered
[l
-1] == QUOTE
|| filtered
[l
-1] == '\'')
2126 ended_with_quote
= filtered
[l
-1];
2128 /* try to find space to break at */
2129 for(p
= &filtered
[75]; p
> &filtered
[60] &&
2130 !isspace((unsigned char)*p
); p
--)
2133 if(!isspace((unsigned char)*p
))
2139 if(ended_with_quote_colon
){
2140 *p
++ = ended_with_quote_colon
;
2143 else if(ended_with_colon
)
2144 *p
++ = ended_with_colon
;
2145 else if(ended_with_quote
)
2146 *p
++ = ended_with_quote
;
2151 if(filtered
&& *filtered
)
2152 gf_puts(filtered
, pc
);
2155 /* and end with two newlines unless no leadin at all */
2156 if(!contains_newline_token
){
2157 if(!strcmp(buf
, DEFAULT_REPLY_INTRO
) || (filtered
&& *filtered
)){
2158 gf_puts(NEWLINE
, pc
);
2159 gf_puts(NEWLINE
, pc
);
2164 fs_give((void **)&filtered
);
2169 free_redraft_pos(REDRAFT_POS_S
**redraft_pos
)
2171 if(redraft_pos
&& *redraft_pos
){
2172 if((*redraft_pos
)->hdrname
)
2173 fs_give((void **)&(*redraft_pos
)->hdrname
);
2175 fs_give((void **)redraft_pos
);
2180 /*----------------------------------------------------------------------
2181 Build the body for the message number/part being forwarded as ATTACHMENT
2185 Result: PARTS suitably attached to body
2187 ----------------------------------------------------------------------*/
2189 forward_mime_msg(MAILSTREAM
*stream
, long int msgno
, char *section
, ENVELOPE
*env
, struct mail_body_part
**partp
, void *msgtext
)
2195 *partp
= mail_newbody_part();
2196 b
= &(*partp
)->body
;
2197 b
->type
= TYPEMESSAGE
;
2198 b
->id
= generate_message_id(NULL
);
2199 b
->description
= cpystr("Forwarded Message");
2200 b
->nested
.msg
= mail_newmsg();
2201 b
->disposition
.type
= cpystr("inline");
2203 /*---- Package each message in a storage object ----*/
2204 if((b
->contents
.text
.data
= (void *) so_get(PART_SO_TYPE
,NULL
,EDIT_ACCESS
))
2205 && (tmp_text
= mail_fetch_header(stream
,msgno
,section
,NIL
,NIL
,FT_PEEK
))
2207 so_puts((STORE_S
*) b
->contents
.text
.data
, tmp_text
);
2209 b
->size
.bytes
= strlen(tmp_text
);
2210 so_puts((STORE_S
*) b
->contents
.text
.data
, "\015\012");
2211 if((tmp_text
= pine_mail_fetch_text (stream
,msgno
,section
,&len
,NIL
)) != NULL
){
2212 so_nputs((STORE_S
*)b
->contents
.text
.data
,tmp_text
,(long) len
);
2213 b
->size
.bytes
+= len
;
2223 * forward_delimiter - return delimiter for forwarded text
2226 forward_delimiter(gf_io_t pc
)
2228 gf_puts(NEWLINE
, pc
);
2229 /* TRANSLATORS: When a message is forwarded by the user this is the
2230 text that shows where the forwarded part of the message begins. */
2231 gf_puts(_("---------- Forwarded message ----------"), pc
);
2232 gf_puts(NEWLINE
, pc
);
2236 /*----------------------------------------------------------------------
2237 Wrapper for header formatting tool
2245 Result: header suitable for reply/forward text written using "pc"
2247 ----------------------------------------------------------------------*/
2249 reply_forward_header(MAILSTREAM
*stream
, long int msgno
, char *part
, ENVELOPE
*env
,
2250 gf_io_t pc
, char *prefix
)
2254 char **list
, **new_list
= NULL
;
2256 list
= ps_global
->VAR_VIEW_HEADERS
;
2259 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2260 * the user doesn't inadvertently forward the BCC header.
2262 if(list
&& list
[0]){
2269 p
= new_list
= (char **) fs_get((cnt
+1) * sizeof(char *));
2271 for(i
=0; list
[i
]; i
++)
2272 if(strucmp(list
[i
], "bcc"))
2273 *p
++ = cpystr(list
[i
]);
2277 if(new_list
&& new_list
[0])
2282 HD_INIT(&h
, list
, ps_global
->view_all_except
, FE_DEFAULT
& ~FE_BCC
);
2283 if((rv
= format_header(stream
, msgno
, part
, env
, &h
,
2284 prefix
, NULL
, FM_NOINDENT
, NULL
, pc
)) != 0){
2286 gf_puts(" [Error fetching message header data]", pc
);
2289 gf_puts(NEWLINE
, pc
); /* write header delimiter */
2292 free_list_array(&new_list
);
2296 /*----------------------------------------------------------------------
2297 Build the subject for the message number being forwarded
2299 Args: pine_state -- The usual pine structure
2300 msgno -- The message number to build subject for
2302 Result: malloc'd string containing new subject or NULL on error
2304 ----------------------------------------------------------------------*/
2306 forward_subject(ENVELOPE
*env
, int flags
)
2309 char *p
, buftmp
[MAILTMPLEN
];
2314 dprint((9, "checking subject: \"%s\"\n",
2315 env
->subject
? env
->subject
: "NULL"));
2317 if(env
->subject
&& env
->subject
[0]){ /* add (fwd)? */
2318 snprintf(buftmp
, sizeof(buftmp
), "%s", env
->subject
);
2319 buftmp
[sizeof(buftmp
)-1] = '\0';
2320 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2321 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf
,
2322 SIZEOF_20KBUF
, buftmp
) == (unsigned char *) buftmp
)
2323 strncpy(tmp_20k_buf
, buftmp
, SIZEOF_20KBUF
);
2325 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2327 removing_trailing_white_space(tmp_20k_buf
);
2328 l
= strlen(tmp_20k_buf
);
2329 if(l
< 5 || strcmp(tmp_20k_buf
+l
-5,"(fwd)")){
2330 char *s
= cpystr(tmp_20k_buf
);
2331 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%.1000s (fwd)", s
);
2332 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2333 fs_give((void **) &s
);
2337 * HACK: composer can't handle embedded double quotes in attachment
2338 * comments so we substitute two single quotes.
2340 if(flags
& FS_CONVERT_QUOTES
)
2341 while((p
= strchr(tmp_20k_buf
, QUOTE
)) != NULL
)
2342 (void)rplstr(p
, SIZEOF_20KBUF
-(p
-tmp_20k_buf
), 1, "''");
2344 return(cpystr(tmp_20k_buf
));
2348 return(cpystr("Forwarded mail...."));
2352 /*----------------------------------------------------------------------
2353 Build the body for the message number/part being forwarded
2357 Result: BODY structure suitable for sending
2359 ----------------------------------------------------------------------*/
2361 forward_body(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
2362 long int msgno
, char *sect_prefix
, void *msgtext
, int flags
)
2364 BODY
*body
= NULL
, *text_body
, *tmp_body
;
2367 char *tmp_text
, *section
, sect_buf
[256];
2368 int forward_raw_body
= 0;
2371 * Check to see if messages got expunged out from underneath us. This
2372 * could have happened during the prompt to the user asking whether to
2373 * include the message as an attachment. Either the message is gone or
2374 * it might be at a different sequence number. We'd better bail.
2376 if(ps_global
->full_header
== 2
2377 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT
, ps_global
))
2378 forward_raw_body
= 1;
2379 if(sp_expunge_count(stream
))
2382 if(sect_prefix
&& forward_raw_body
== 0)
2383 snprintf(section
= sect_buf
, sizeof(sect_buf
), "%s.1", sect_prefix
);
2384 else if(sect_prefix
&& forward_raw_body
)
2385 section
= sect_prefix
;
2386 else if(!sect_prefix
&& forward_raw_body
)
2391 sect_buf
[sizeof(sect_buf
)-1] = '\0';
2393 gf_set_so_writec(&pc
, (STORE_S
*) msgtext
);
2394 if(!orig_body
|| orig_body
->type
== TYPETEXT
|| forward_raw_body
) {
2395 char *charset
= NULL
;
2397 /*---- Message has a single text part -----*/
2398 body
= mail_newbody();
2399 body
->type
= TYPETEXT
;
2400 body
->contents
.text
.data
= msgtext
;
2402 && (charset
= parameter_val(orig_body
->parameter
, "charset")))
2403 set_parameter(&body
->parameter
, "charset", charset
);
2406 fs_give((void **) &charset
);
2408 if(!(flags
& FWD_ANON
)){
2409 forward_delimiter(pc
);
2410 reply_forward_header(stream
, msgno
, sect_prefix
, env
, pc
, "");
2413 if(!get_body_part_text(stream
, forward_raw_body
? NULL
: orig_body
,
2414 msgno
, section
, 0L, pc
, NULL
, NULL
, GBPT_NONE
)){
2415 mail_free_body(&body
);
2419 else if(orig_body
->type
== TYPEMULTIPART
) {
2420 if(orig_body
->subtype
2421 && ((!strucmp(orig_body
->subtype
, "signed") && orig_body
->nested
.part
)
2423 || (!strucmp(orig_body
->subtype
, "mixed")
2424 && orig_body
->nested
.part
2425 && orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
2426 && orig_body
->nested
.part
->body
.subtype
2427 && (!strucmp(orig_body
->nested
.part
->body
.subtype
, OUR_PKCS7_ENCLOSURE_SUBTYPE
)
2428 || !strucmp(orig_body
->nested
.part
->body
.subtype
, "signed")))
2429 || !strucmp(orig_body
->subtype
, OUR_PKCS7_ENCLOSURE_SUBTYPE
)
2432 /* only operate on the signed data (not the signature) */
2433 body
= forward_body(stream
, env
, &orig_body
->nested
.part
->body
,
2435 orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
2436 ? section
: sect_prefix
, msgtext
, flags
);
2438 /*---- Message is multipart ----*/
2439 else if(!(orig_body
->subtype
&& !strucmp(orig_body
->subtype
,
2441 && (body
= forward_multi_alt(stream
, env
, orig_body
, msgno
,
2442 sect_prefix
, msgtext
,
2444 /*--- Copy the body and entire structure ---*/
2445 body
= copy_body(NULL
, orig_body
);
2448 * whatever subtype it is, demote it
2449 * to plain old MIXED.
2452 fs_give((void **) &body
->subtype
);
2454 body
->subtype
= cpystr("Mixed");
2456 /*--- The text part of the message ---*/
2457 if(!body
->nested
.part
){
2458 q_status_message(SM_ORDER
| SM_DING
, 3, 6,
2459 "Error referencing body part 1");
2460 mail_free_body(&body
);
2462 else if(body
->nested
.part
->body
.type
== TYPETEXT
) {
2463 char *new_charset
= NULL
;
2465 /*--- The first part is text ----*/
2466 text_body
= &body
->nested
.part
->body
;
2467 text_body
->contents
.text
.data
= msgtext
;
2468 if(text_body
->subtype
&& strucmp(text_body
->subtype
, "Plain")){
2469 /* this text is going to the composer, it should be Plain */
2470 fs_give((void **)&text_body
->subtype
);
2471 text_body
->subtype
= cpystr("PLAIN");
2473 if(!(flags
& FWD_ANON
)){
2474 forward_delimiter(pc
);
2475 reply_forward_header(stream
, msgno
,
2476 sect_prefix
, env
, pc
, "");
2479 if(!(get_body_part_text(stream
, &orig_body
->nested
.part
->body
,
2480 msgno
, section
, 0L, pc
,
2481 NULL
, &new_charset
, GBPT_NONE
)
2482 && fetch_contents(stream
, msgno
, sect_prefix
, body
)))
2483 mail_free_body(&body
);
2484 else if(new_charset
)
2485 set_parameter(&text_body
->parameter
, "charset", new_charset
);
2487 /* BUG: ? matter that we're not setting body.size.bytes */
2489 else if(body
->nested
.part
->body
.type
== TYPEMULTIPART
2490 && body
->nested
.part
->body
.subtype
2491 && !strucmp(body
->nested
.part
->body
.subtype
, "alternative")
2492 && (tmp_body
= forward_multi_alt(stream
, env
,
2493 &body
->nested
.part
->body
,
2496 flags
| FWD_NESTED
))){
2497 /* for the forward_multi_alt call above, we want to pass
2498 * sect_prefix instead of section so we can obtain the header.
2499 * Set the FWD_NESTED flag so we fetch the right body_part.
2503 part
= body
->nested
.part
->next
;
2504 body
->nested
.part
->next
= NULL
;
2505 mail_free_body_part(&body
->nested
.part
);
2506 body
->nested
.part
= mail_newbody_part();
2507 body
->nested
.part
->body
= *tmp_body
;
2508 body
->nested
.part
->next
= part
;
2510 for(partnum
= 2; part
!= NULL
; part
= part
->next
){
2511 snprintf(sect_buf
, sizeof(sect_buf
), "%s%s%d",
2512 sect_prefix
? sect_prefix
: "",
2513 sect_prefix
? "." : "", partnum
++);
2514 sect_buf
[sizeof(sect_buf
)-1] = '\0';
2516 if(!fetch_contents(stream
, msgno
, sect_buf
, &part
->body
)){
2517 mail_free_body(&body
);
2523 if(fetch_contents(stream
, msgno
, sect_prefix
, body
)){
2524 /*--- Create a new blank text part ---*/
2525 part
= mail_newbody_part();
2526 part
->next
= body
->nested
.part
;
2527 body
->nested
.part
= part
;
2528 part
->body
.contents
.text
.data
= msgtext
;
2531 mail_free_body(&body
);
2536 /*---- A single part message, not of type text ----*/
2537 body
= mail_newbody();
2538 body
->type
= TYPEMULTIPART
;
2539 part
= mail_newbody_part();
2540 body
->nested
.part
= part
;
2542 /*--- The first part, a blank text part to be edited ---*/
2543 part
->body
.type
= TYPETEXT
;
2544 part
->body
.contents
.text
.data
= msgtext
;
2546 /*--- The second part, what ever it is ---*/
2547 part
->next
= mail_newbody_part();
2549 part
->body
.id
= generate_message_id(NULL
);
2550 copy_body(&(part
->body
), orig_body
);
2553 * the idea here is to fetch part into storage object
2555 if((part
->body
.contents
.text
.data
= (void *) so_get(PART_SO_TYPE
, NULL
,
2556 EDIT_ACCESS
)) != NULL
){
2557 if((tmp_text
= pine_mail_fetch_body(stream
, msgno
, section
,
2558 &part
->body
.size
.bytes
, NIL
)) != NULL
)
2559 so_nputs((STORE_S
*)part
->body
.contents
.text
.data
, tmp_text
,
2560 part
->body
.size
.bytes
);
2562 mail_free_body(&body
);
2565 mail_free_body(&body
);
2568 gf_clear_so_writec((STORE_S
*) msgtext
);
2576 * bounce_msg_body - build body from specified message suitable
2577 * for sending as bounced message
2580 bounce_msg_body(MAILSTREAM
*stream
,
2585 ENVELOPE
**outgoingp
,
2589 char *h
, *p
, *errstr
= NULL
;
2594 *outgoingp
= mail_newenvelope();
2595 (*outgoingp
)->subject
= cpystr(subject
? subject
: "Resent mail....");
2598 * Fill in destination if we were given one. If so, note that we
2599 * call p_s_s() below such that it won't prompt...
2602 static char *fakedomain
= "@";
2605 /* rfc822_parse_adrlist feels free to destroy input so copy */
2606 tmp_a_string
= cpystr(*to
);
2607 rfc822_parse_adrlist(&(*outgoingp
)->to
, tmp_a_string
,
2608 (F_ON(F_COMPOSE_REJECTS_UNQUAL
, ps_global
))
2609 ? fakedomain
: ps_global
->maildomain
);
2610 fs_give((void **) &tmp_a_string
);
2613 /* build remail'd header */
2614 if((h
= mail_fetch_header(stream
, rawno
, part
, NULL
, 0, FT_PEEK
)) != NULL
){
2615 for(p
= h
, i
= 0; (p
= strchr(p
, ':')) != NULL
; p
++)
2619 (*outgoingp
)->remail
= (char *) fs_get(strlen(h
) + (2 * i
) + 1);
2622 * copy it, "X-"ing out transport headers bothersome to
2623 * software but potentially useful to the human recipient...
2625 p
= (*outgoingp
)->remail
;
2626 bounce_mask_header(&p
, h
);
2628 if(*h
== '\015' && *(h
+1) == '\012'){
2629 *p
++ = *h
++; /* copy CR LF */
2631 bounce_mask_header(&p
, h
);
2633 while((*p
++ = *h
++) != '\0');
2635 /* BUG: else complain? */
2637 /* NOT bound for the composer, so no need for PicoText */
2638 if(!(msgtext
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2639 mail_free_envelope(outgoingp
);
2640 return(_("Error allocating message text"));
2643 /* mark object for special handling */
2644 so_attr(msgtext
, "rawbody", "1");
2647 * Build a fake body description. It's ignored by pine_rfc822_header,
2648 * but we need to set it to something that makes set_mime_types
2649 * not sniff it and pine_rfc822_output_body not re-encode it.
2650 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2651 * problems unless something tries to access body_encodings[] using
2652 * it without proper precautions. We don't want to use ENCOTHER
2653 * cause that tells set_mime_types to sniff it, and we don't want to
2654 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2655 * it. When there's time, it'd be nice to clean this interaction
2658 *bodyp
= mail_newbody();
2659 (*bodyp
)->type
= TYPETEXT
;
2660 (*bodyp
)->encoding
= ENCMAX
+ 1;
2661 (*bodyp
)->subtype
= cpystr("Plain");
2662 (*bodyp
)->contents
.text
.data
= (void *) msgtext
;
2663 gf_set_so_writec(&pc
, msgtext
);
2665 if(seenp
&& rawno
> 0L && stream
&& rawno
<= stream
->nmsgs
){
2668 if((mc
= mail_elt(stream
, rawno
)) != NULL
)
2672 /* pass NULL body to force mail_fetchtext */
2673 if(!get_body_part_text(stream
, NULL
, rawno
, part
, 0L, pc
, NULL
, NULL
, GBPT_NONE
))
2674 errstr
= _("Error fetching message contents. Can't Bounce message");
2676 gf_clear_so_writec(msgtext
);
2683 /*----------------------------------------------------------------------
2684 Mask off any header entries we don't want xport software to see
2686 Args: d -- destination string pointer pointer
2687 s -- source string pointer pointer
2689 Postfix uses Delivered-To to detect loops.
2690 Received line counting is also used to detect loops in places.
2694 bounce_mask_header(char **d
, char *s
)
2696 if(((*s
== 'R' || *s
== 'r')
2697 && (!struncmp(s
+1, "esent-", 6) || !struncmp(s
+1, "eceived:", 8)
2698 || !struncmp(s
+1, "eturn-Path", 10)))
2699 || !struncmp(s
, "Delivered-To:", 13)){
2700 *(*d
)++ = 'X'; /* write mask */
2706 /*----------------------------------------------------------------------
2707 Fetch and format text for forwarding
2709 Args: stream -- Mail stream to fetch text from
2710 body -- Body structure of message being forwarded
2711 msg_no -- Message number of text for forward
2712 part_no -- Part number of text to forward
2713 partial -- If this is > 0 a partial fetch will be done and it will
2714 be done using FT_PEEK so the message will remain unseen.
2715 pc -- Function to write to
2716 prefix -- Prefix for each line
2717 ret_charset -- If we translate to another charset return that
2720 Returns: true if OK, false if problem occurred while filtering
2722 If the text is richtext, it will be converted to plain text, since there's
2723 no rich text editing capabilities in Pine (yet).
2725 It's up to calling routines to plug in signature appropriately
2727 As with all internal text, NVT end-of-line conventions are observed.
2728 DOESN'T sanity check the prefix given!!!
2731 get_body_part_text(MAILSTREAM
*stream
, struct mail_bodystruct
*body
,
2732 long int msg_no
, char *part_no
, long partial
, gf_io_t pc
,
2733 char *prefix
, char **ret_charset
, unsigned flags
)
2735 int we_cancel
= 0, dashdata
, wrapflags
= GFW_FORCOMPOSE
, flow_res
= 0;
2736 FILTLIST_S filters
[12];
2738 char *err
, *charset
, *prefix_p
= NULL
;
2740 char *free_this
= NULL
;
2743 memset(filters
, 0, sizeof(filters
));
2745 *ret_charset
= NULL
;
2747 if(!pc_is_picotext(pc
))
2748 we_cancel
= busy_cue(NULL
, NULL
, 1);
2750 /* if null body, we must be talking to a non-IMAP2bis server.
2751 * No MIME parsing provided, so we just grab the message text...
2754 char *text
, *decode_error
;
2756 SourceType src
= CharStar
;
2759 (void) pine_mail_fetchstructure(stream
, msg_no
, NULL
);
2761 if((text
= pine_mail_fetch_text(stream
, msg_no
, part_no
, NULL
, 0)) != NULL
){
2762 gf_set_readc(&gc
, text
, (unsigned long)strlen(text
), src
, 0);
2764 gf_filter_init(); /* no filters needed */
2766 gf_link_filter(gf_prefix
, gf_prefix_opt(prefix
));
2767 if((decode_error
= gf_pipe(gc
, pc
)) != NULL
){
2768 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s%s [Formatting error: %s]%s",
2770 decode_error
, NEWLINE
);
2771 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2772 gf_puts(tmp_20k_buf
, pc
);
2777 gf_puts(NEWLINE
, pc
);
2778 gf_puts(_(" [ERROR fetching text of message]"), pc
);
2779 gf_puts(NEWLINE
, pc
);
2780 gf_puts(NEWLINE
, pc
);
2785 cancel_busy_cue(-1);
2790 charset
= parameter_val(body
->parameter
, "charset");
2792 if(charset
&& strucmp(charset
, "utf-8") && strucmp(charset
, "us-ascii")){
2794 *ret_charset
= "UTF-8";
2798 fs_give((void **) &charset
);
2801 * just use detach, but add an auxiliary filter to insert prefix,
2802 * and, perhaps, digest richtext
2804 if(ps_global
->full_header
!= 2
2805 && !ps_global
->postpone_no_flow
2806 && (!body
->subtype
|| !strucmp(body
->subtype
, "plain"))){
2809 flow_res
= (F_OFF(F_QUELL_FLOWED_TEXT
, ps_global
)
2810 && F_OFF(F_STRIP_WS_BEFORE_SEND
, ps_global
)
2811 && (!prefix
|| (strucmp(prefix
,"> ") == 0)
2812 || strucmp(prefix
, ">") == 0));
2813 if((parmval
= parameter_val(body
->parameter
,
2814 "format")) != NULL
){
2815 if(!strucmp(parmval
, "flowed")){
2816 wrapflags
|= GFW_FLOWED
;
2818 fs_give((void **) &parmval
);
2819 if((parmval
= parameter_val(body
->parameter
, "delsp")) != NULL
){
2820 if(!strucmp(parmval
, "yes")){
2821 filters
[filtcnt
++].filter
= gf_preflow
;
2822 wrapflags
|= GFW_DELSP
;
2825 fs_give((void **) &parmval
);
2829 * if there's no prefix we're forwarding text
2830 * otherwise it's reply text. unless the user's
2831 * tied our hands, alter the prefix to continue flowed
2834 if(flow_res
&& ps_global
->reply
.use_flowed
)
2835 wrapflags
|= GFW_FLOW_RESULT
;
2837 filters
[filtcnt
].filter
= gf_wrap
;
2839 * The 80 will cause longer lines than what is likely
2840 * set by composer_fillcol, so we'll have longer
2841 * quoted and forwarded lines than the lines we type.
2842 * We're fine with that since the alternative is the
2843 * possible wrapping of lines we shouldn't have, which
2844 * is mitigated by this higher 80 limit.
2846 * If we were to go back to using composer_fillcol,
2847 * the correct value is composer_fillcol + 1, pine
2848 * is off-by-one from pico.
2850 filters
[filtcnt
++].data
= gf_wrap_filter_opt(
2851 MAX(MIN(ps_global
->ttyo
->screen_cols
2852 - (prefix
? strlen(prefix
) : 0),
2853 80 - (prefix
? strlen(prefix
) : 0)),
2854 30), /* doesn't have to be 30 */
2855 990, /* 998 is the SMTP limit */
2856 NULL
, 0, wrapflags
);
2861 * if not flowed, remove trailing whitespace to reduce
2862 * confusion, since we're sending out as flowed if we
2863 * can. At some future point, we might try
2864 * plugging in a user-option-controlled heuristic
2867 * We also want to fold "> " quotes so we get the
2868 * attributions correct.
2870 if(flow_res
&& ps_global
->reply
.use_flowed
&& prefix
&& !strucmp(prefix
, "> "))
2871 *(prefix_p
= prefix
+ 1) = '\0';
2872 ps_global
->reply
.use_flowed
= 1; /* reset for next call */
2873 if(!(wrapflags
& GFW_FLOWED
)
2875 filters
[filtcnt
].filter
= gf_line_test
;
2876 filters
[filtcnt
++].data
= gf_line_test_opt(twsp_strip
, NULL
);
2878 filters
[filtcnt
].filter
= gf_line_test
;
2879 filters
[filtcnt
++].data
= gf_line_test_opt(quote_fold
, NULL
);
2882 else if(body
->subtype
){
2885 if(strucmp(body
->subtype
,"richtext") == 0){
2886 filters
[filtcnt
].filter
= gf_rich2plain
;
2887 filters
[filtcnt
++].data
= gf_rich2plain_opt(&plain_opt
);
2889 else if(strucmp(body
->subtype
,"enriched") == 0){
2890 filters
[filtcnt
].filter
= gf_enriched2plain
;
2891 filters
[filtcnt
++].data
= gf_enriched2plain_opt(&plain_opt
);
2893 else if(strucmp(body
->subtype
,"html") == 0){
2894 if((flags
& GBPT_HTML_OK
) != GBPT_HTML_OK
){
2895 filters
[filtcnt
].filter
= gf_html2plain
;
2896 filters
[filtcnt
++].data
= gf_html2plain_opt(NULL
,
2897 ps_global
->ttyo
->screen_cols
,
2898 non_messageview_margin(),
2899 NULL
, NULL
, GFHP_STRIPPED
);
2905 if(ps_global
->reply
.strip_signature
){
2907 filters
[filtcnt
].filter
= gf_line_test
;
2908 filters
[filtcnt
++].data
= gf_line_test_opt(sigdash_strip
, &dashdata
);
2911 filters
[filtcnt
].filter
= gf_prefix
;
2912 filters
[filtcnt
++].data
= gf_prefix_opt(prefix
);
2914 if(wrapflags
& GFW_FLOWED
|| flow_res
){
2915 filters
[filtcnt
].filter
= gf_line_test
;
2916 filters
[filtcnt
++].data
= gf_line_test_opt(post_quote_space
, NULL
);
2920 if(flags
& GBPT_DELQUOTES
){
2921 memset(&dq
, 0, sizeof(dq
));
2922 dq
.lines
= Q_DEL_ALL
;
2924 dq
.indent_length
= 0;
2925 dq
.saved_line
= &free_this
;
2930 filters
[filtcnt
].filter
= gf_line_test
;
2931 filters
[filtcnt
++].data
= gf_line_test_opt(delete_quotes
, &dq
);
2934 err
= detach(stream
, msg_no
, part_no
, partial
, &len
, pc
,
2935 filters
[0].filter
? filters
: NULL
,
2936 ((flags
& GBPT_PEEK
) ? FT_PEEK
: 0)
2937 | ((flags
& GBPT_NOINTR
) ? DT_NOINTR
: 0));
2940 fs_give((void **) &free_this
);
2945 if (err
!= (char *) NULL
)
2946 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2947 q_status_message2(SM_ORDER
, 3, 4, "%s: message number %ld",
2948 err
, (void *) msg_no
);
2951 cancel_busy_cue(-1);
2958 quote_fold(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
2963 for(p
= line
; *p
; p
++){
2964 if(isspace((unsigned char) *p
)){
2966 ins
= gf_line_test_new_ins(ins
, p
, "", -1);
2978 twsp_strip(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
2980 char *p
, *ws
= NULL
;
2982 for(p
= line
; *p
; p
++){
2983 /* don't strip trailing space on signature line */
2984 if(*line
== '-' && *(line
+1) == '-' && *(line
+2) == ' ' && !*(line
+3))
2987 if(isspace((unsigned char) *p
)){
2996 ins
= gf_line_test_new_ins(ins
, ws
, "", -(p
- ws
));
3002 post_quote_space(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
3006 for(p
= line
; *p
; p
++)
3008 if(p
!= line
&& *p
!= ' ')
3009 ins
= gf_line_test_new_ins(ins
, p
, " ", 1);
3019 sigdash_strip(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
3022 || (*line
== '-' && *(line
+1) == '-'
3023 && *(line
+2) == ' ' && !*(line
+3))){
3024 *((int *) local
) = 1;
3025 return(2); /* skip this line! */
3032 /*----------------------------------------------------------------------
3033 return the c-client reference name for the given end_body part
3036 body_partno(MAILSTREAM
*stream
, long int msgno
, struct mail_bodystruct
*end_body
)
3040 (void) pine_mail_fetchstructure(stream
, msgno
, &body
);
3041 return(partno(body
, end_body
));
3045 /*----------------------------------------------------------------------
3046 return the c-client reference name for the given end_body part
3049 partno(struct mail_bodystruct
*body
, struct mail_bodystruct
*end_body
)
3051 #define PARTTMPLEN 64
3054 char tmp
[PARTTMPLEN
], *p
= NULL
;
3056 if(body
&& body
->type
== TYPEMULTIPART
) {
3057 part
= body
->nested
.part
; /* first body part */
3059 do { /* for each part */
3060 num
++; /* PARTTMPLEN = sizeof(tmp) */
3061 if(&part
->body
== end_body
|| (p
= partno(&part
->body
, end_body
))){
3062 snprintf(tmp
, sizeof(tmp
), "%d%s%.*s", num
, (p
) ? "." : "",
3063 PARTTMPLEN
-12, (p
) ? p
: "");
3064 tmp
[sizeof(tmp
)-1] = '\0';
3066 fs_give((void **)&p
);
3068 return(cpystr(tmp
));
3070 } while ((part
= part
->next
) != NULL
); /* until done */
3074 else if(body
&& body
->type
== TYPEMESSAGE
&& body
->subtype
3075 && !strucmp(body
->subtype
, "rfc822")){
3076 return(partno(body
->nested
.msg
->body
, end_body
));
3079 return((body
== end_body
) ? cpystr("1") : NULL
);
3083 /*----------------------------------------------------------------------
3084 Fill in the contents of each body part
3086 Args: stream -- Stream the message is on
3087 msgno -- Message number the body structure is for
3088 section -- body section associated with body pointer
3089 body -- Body pointer to fill in
3091 Result: 1 if all went OK, 0 if there was a problem
3093 This function copies the contents from an original message/body to
3094 a new message/body. It recurses down all multipart levels.
3096 If one or more part (but not all) can't be fetched, a status message
3100 fetch_contents(MAILSTREAM
*stream
, long int msgno
, char *section
, struct mail_bodystruct
*body
)
3106 body
->id
= generate_message_id(NULL
);
3108 if(body
->type
== TYPEMULTIPART
){
3109 char subsection
[256], *subp
;
3110 int n
, last_one
= 10; /* remember worst case */
3111 PART
*part
= body
->nested
.part
;
3113 if(!(part
= body
->nested
.part
))
3117 if(section
&& *section
){
3119 n
< sizeof(subsection
)-20 && (*subp
= section
[n
]); n
++, subp
++)
3127 snprintf(subp
, sizeof(subsection
)-(subp
-subsection
), "%d", n
++);
3128 subsection
[sizeof(subsection
)-1] = '\0';
3129 got_one
= fetch_contents(stream
, msgno
, subsection
, &part
->body
);
3130 last_one
= MIN(last_one
, got_one
);
3132 while((part
= part
->next
) != NULL
);
3137 if(body
->contents
.text
.data
)
3138 return(1); /* already taken care of... */
3140 if(body
->type
== TYPEMESSAGE
){
3141 if(body
->subtype
&& strucmp(body
->subtype
,"external-body")){
3143 * the idea here is to fetch everything into storage objects
3145 body
->contents
.text
.data
= (void *) so_get(PART_SO_TYPE
, NULL
,
3147 if(body
->contents
.text
.data
3148 && (tp
= pine_mail_fetch_body(stream
, msgno
, section
,
3149 &body
->size
.bytes
, NIL
))){
3150 so_truncate((STORE_S
*)body
->contents
.text
.data
,
3151 body
->size
.bytes
+ 2048);
3152 so_nputs((STORE_S
*)body
->contents
.text
.data
, tp
,
3157 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
3158 _("Error fetching part %s"), section
);
3164 * the idea here is to fetch everything into storage objects
3165 * so, grab one, then fetch the body part
3167 body
->contents
.text
.data
= (void *)so_get(PART_SO_TYPE
,NULL
,EDIT_ACCESS
);
3168 if(body
->contents
.text
.data
3169 && (tp
=pine_mail_fetch_body(stream
, msgno
, section
,
3170 &body
->size
.bytes
, NIL
))){
3171 so_truncate((STORE_S
*)body
->contents
.text
.data
,
3172 body
->size
.bytes
+ 2048);
3173 so_nputs((STORE_S
*)body
->contents
.text
.data
, tp
,
3178 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
3179 _("Error fetching part %s"), section
);
3186 /*----------------------------------------------------------------------
3187 Copy the body structure
3189 Args: new_body -- Pointer to already allocated body, or NULL, if none
3190 old_body -- The Body to copy
3193 This is traverses the body structure recursively copying all elements.
3194 The new_body parameter can be NULL in which case a new body is
3195 allocated. Alternatively it can point to an already allocated body
3196 structure. This is used when copying body parts since a PART includes a
3197 BODY. The contents fields are *not* filled in.
3201 copy_body(struct mail_bodystruct
*new_body
, struct mail_bodystruct
*old_body
)
3203 if(old_body
== NULL
)
3206 if(new_body
== NULL
)
3207 new_body
= mail_newbody();
3209 new_body
->type
= old_body
->type
;
3210 new_body
->encoding
= old_body
->encoding
;
3212 if(old_body
->subtype
)
3213 new_body
->subtype
= cpystr(old_body
->subtype
);
3215 new_body
->parameter
= copy_parameters(old_body
->parameter
);
3218 new_body
->id
= cpystr(old_body
->id
);
3220 if(old_body
->description
)
3221 new_body
->description
= cpystr(old_body
->description
);
3223 if(old_body
->disposition
.type
)
3224 new_body
->disposition
.type
= cpystr(old_body
->disposition
.type
);
3226 new_body
->disposition
.parameter
3227 = copy_parameters(old_body
->disposition
.parameter
);
3229 new_body
->size
= old_body
->size
;
3231 if(new_body
->type
== TYPEMESSAGE
3232 && new_body
->subtype
&& !strucmp(new_body
->subtype
, "rfc822")){
3233 new_body
->nested
.msg
= mail_newmsg();
3234 new_body
->nested
.msg
->body
3235 = copy_body(NULL
, old_body
->nested
.msg
->body
);
3237 else if(new_body
->type
== TYPEMULTIPART
) {
3238 PART
**new_partp
, *old_part
;
3240 new_partp
= &new_body
->nested
.part
;
3241 for(old_part
= old_body
->nested
.part
;
3243 old_part
= old_part
->next
){
3244 *new_partp
= mail_newbody_part();
3245 copy_body(&(*new_partp
)->body
, &old_part
->body
);
3246 new_partp
= &(*new_partp
)->next
;
3254 /*----------------------------------------------------------------------
3255 Copy the MIME parameter list
3257 Allocates storage for new part, and returns pointer to new parameter
3258 list. If old_p is NULL, NULL is returned.
3261 copy_parameters(PARAMETER
*old_p
)
3263 PARAMETER
*new_p
, *p1
, *p2
;
3266 return((PARAMETER
*)NULL
);
3269 for(p1
= old_p
; p1
!= NULL
; p1
= p1
->next
){
3270 set_parameter(&p2
, p1
->attribute
, p1
->value
);
3279 /*----------------------------------------------------------------------
3280 Make a complete copy of an envelope and all it's fields
3282 Args: e -- the envelope to copy
3284 Result: returns the new envelope, or NULL, if the given envelope was NULL
3289 copy_envelope(register ENVELOPE
*e
)
3291 register ENVELOPE
*e2
;
3296 e2
= mail_newenvelope();
3297 e2
->remail
= e
->remail
? cpystr(e
->remail
) : NULL
;
3298 e2
->return_path
= e
->return_path
? rfc822_cpy_adr(e
->return_path
) : NULL
;
3299 e2
->date
= e
->date
? (unsigned char *)cpystr((char *) e
->date
)
3301 e2
->from
= e
->from
? rfc822_cpy_adr(e
->from
) : NULL
;
3302 e2
->sender
= e
->sender
? rfc822_cpy_adr(e
->sender
) : NULL
;
3303 e2
->reply_to
= e
->reply_to
? rfc822_cpy_adr(e
->reply_to
) : NULL
;
3304 e2
->subject
= e
->subject
? cpystr(e
->subject
) : NULL
;
3305 e2
->to
= e
->to
? rfc822_cpy_adr(e
->to
) : NULL
;
3306 e2
->cc
= e
->cc
? rfc822_cpy_adr(e
->cc
) : NULL
;
3307 e2
->bcc
= e
->bcc
? rfc822_cpy_adr(e
->bcc
) : NULL
;
3308 e2
->in_reply_to
= e
->in_reply_to
? cpystr(e
->in_reply_to
) : NULL
;
3309 e2
->newsgroups
= e
->newsgroups
? cpystr(e
->newsgroups
) : NULL
;
3310 e2
->message_id
= e
->message_id
? cpystr(e
->message_id
) : NULL
;
3311 e2
->references
= e
->references
? cpystr(e
->references
) : NULL
;
3312 e2
->followup_to
= e
->followup_to
? cpystr(e
->references
) : NULL
;
3317 /*----------------------------------------------------------------------
3318 Generate the "In-reply-to" text from message header
3320 Args: message -- Envelope of original message
3322 Result: returns an alloc'd string or NULL if there is a problem
3325 reply_in_reply_to(ENVELOPE
*env
)
3327 return((env
&& env
->message_id
) ? cpystr(env
->message_id
) : NULL
);
3331 /*----------------------------------------------------------------------
3332 Generate a unique message id string.
3334 Args: ps -- The usual pine structure
3336 Result: Alloc'd unique string is returned
3338 Uniqueness is guaranteed by using the host name, process id, date to the
3339 second and a single unique character
3340 *----------------------------------------------------------------------*/
3342 generate_message_id(ACTION_S
*role
)
3344 char *id
, *leftpart
, *hostpart
, *prehostpart
;
3346 if(role
&& role
->from
)
3347 prehostpart
= cpystr(role
->from
->host
? role
->from
->host
: "huh");
3348 else if(ps_global
->maildomain
) /* as in generate_from() */
3349 prehostpart
= cpystr(ps_global
->maildomain
);
3351 prehostpart
= cpystr(ps_global
->hostname
);
3353 if(F_ON(F_ROT13_MESSAGE_ID
, ps_global
)){
3354 hostpart
= rot13(prehostpart
);
3355 leftpart
= rot13(oauth2_generate_state());
3357 hostpart
= prehostpart
;
3358 leftpart
= oauth2_generate_state();
3361 id
= fs_get(strlen(leftpart
) + strlen(hostpart
) + 4);
3362 sprintf(id
, "<%s@%s>", leftpart
, hostpart
);
3364 fs_give((void **) &hostpart
);
3365 fs_give((void **) &leftpart
);
3373 generate_user_agent(void)
3378 if(F_ON(F_QUELL_USERAGENT
, ps_global
))
3381 snprintf(buf
, sizeof(buf
),
3382 "%sAlpine %s (%s %s)",
3383 (pith_opt_user_agent_prefix
) ? (*pith_opt_user_agent_prefix
)() : "",
3384 ALPINE_VERSION
, SYSTYPE
,
3385 get_alpine_revision_string(rev
, sizeof(rev
)));
3387 return(cpystr(buf
));
3394 char byte
, cap
, *p
, *ret
= NULL
;
3397 ret
= (char *) fs_get((strlen(src
)+1) * sizeof(char));
3399 while((byte
= *src
++) != '\0'){
3402 *p
++ = ((byte
>= 'A') && (byte
<= 'Z')
3403 ? ((byte
- 'A' + 13) % 26 + 'A') : byte
) | cap
;
3415 char byte
, *p
, *ret
= NULL
;
3418 ret
= (char *) fs_get((strlen(src
)+1) * sizeof(char));
3420 while((byte
= *src
++) != '\0')
3421 *p
++ = ((byte
>= '0') && (byte
<= '9')
3422 ? ((byte
- '0' + 5) % 10 + '0') : byte
);
3430 /*----------------------------------------------------------------------
3431 Return the first true address pointer (modulo group syntax allowance)
3433 Args: addr -- Address list
3435 Result: First real address pointer, or NULL
3436 ----------------------------------------------------------------------*/
3438 first_addr(struct mail_address
*addr
)
3440 while(addr
&& !addr
->host
)
3447 /*----------------------------------------------------------------------
3448 lit -- this is the source
3449 prenewlines -- prefix the file contents with this many newlines
3450 postnewlines -- postfix the file contents with this many newlines
3451 is_sig -- this is a signature (not a template)
3452 decode_constants -- change C-style constants into their values
3455 get_signature_lit(char *lit
, int prenewlines
, int postnewlines
, int is_sig
, int decode_constants
)
3460 * Should make this smart enough not to do the copying and double
3461 * allocation of space.
3464 char *tmplit
= NULL
, *p
, *q
, *d
, save
;
3467 if(decode_constants
){
3468 tmplit
= (char *) fs_get((strlen(lit
)+1) * sizeof(char));
3470 cstring_to_string(lit
, tmplit
);
3473 tmplit
= cpystr(lit
);
3475 len
= strlen(tmplit
) + 5 + (prenewlines
+postnewlines
) * strlen(NEWLINE
);
3476 sig
= (char *) fs_get((len
+1) * sizeof(char));
3477 memset(sig
, 0, len
+1);
3479 while(prenewlines
--)
3480 sstrncpy(&d
, NEWLINE
, len
-(d
-sig
));
3482 if(is_sig
&& F_ON(F_ENABLE_SIGDASHES
, ps_global
) &&
3483 !sigdashes_are_present(tmplit
)){
3484 sstrncpy(&d
, SIGDASHES
, len
-(d
-sig
));
3485 sstrncpy(&d
, NEWLINE
, len
-(d
-sig
));
3493 q
= strpbrk(p
, "\n\r");
3500 * Strip trailing space if we are doing a signature and
3501 * this line is not sigdashes.
3503 if(is_sig
&& strcmp(p
, SIGDASHES
))
3504 removing_trailing_white_space(p
);
3506 while((d
-sig
) <= len
&& (*d
= *p
++) != '\0')
3519 while(postnewlines
--)
3520 sstrncpy(&d
, NEWLINE
, len
-(d
-sig
));
3528 fs_give((void **) &tmplit
);
3536 sigdashes_are_present(char *sig
)
3540 p
= srchstr(sig
, SIGDASHES
);
3541 while(p
&& !((p
== sig
|| (p
[-1] == '\n' || p
[-1] == '\r')) &&
3542 (p
[3] == '\0' || p
[3] == '\n' || p
[3] == '\r')))
3543 p
= srchstr(p
+1, SIGDASHES
);
3549 /*----------------------------------------------------------------------
3550 Acquire the pinerc defined signature file pathname
3554 signature_path(char *sname
, char *sbuf
, size_t len
)
3557 if(sname
&& *sname
){
3558 size_t spl
= strlen(sname
);
3559 if(IS_REMOTE(sname
)){
3561 strncpy(sbuf
, sname
, len
-1);
3563 else if(is_absolute_path(sname
)){
3564 strncpy(sbuf
, sname
, len
-1);
3566 fnexpand(sbuf
, len
);
3568 else if(ps_global
->VAR_OPER_DIR
){
3569 if(strlen(ps_global
->VAR_OPER_DIR
) + spl
< len
- 1)
3570 build_path(sbuf
, ps_global
->VAR_OPER_DIR
, sname
, len
);
3573 char *lc
= last_cmpnt(ps_global
->pinerc
);
3577 strncpy(sbuf
,ps_global
->pinerc
,MIN(len
-1,lc
-ps_global
->pinerc
));
3578 sbuf
[MIN(len
-1,lc
-ps_global
->pinerc
)] = '\0';
3581 strncat(sbuf
, sname
, MAX(len
-1-strlen(sbuf
), 0));
3586 return(*sbuf
? sbuf
: NULL
);
3591 simple_read_remote_file(char *name
, char *subtype
)
3598 dprint((7, "simple_read_remote_file(%s, %s)\n", name
? name
: "?", subtype
? subtype
: "?"));
3601 * We could parse the name here to find what type it is. So far we
3602 * only have type RemImap.
3604 rd
= rd_create_remote(RemImap
, name
, subtype
,
3605 NULL
, _("Error: "), _("Can't fetch remote configuration."));
3609 try_cache
= rd_read_metadata(rd
);
3611 if(rd
->access
== MaybeRorW
){
3612 if(rd
->read_status
== 'R')
3613 rd
->access
= ReadOnly
;
3615 rd
->access
= ReadWrite
;
3618 if(rd
->access
!= NoExists
){
3620 rd_check_remvalid(rd
, 1L);
3623 * If the cached info says it is readonly but
3624 * it looks like it's been fixed now, change it to readwrite.
3626 if(rd
->read_status
== 'R'){
3628 * We go to this trouble since readonly sigfiles
3629 * are likely a mistake. They are usually supposed to be
3630 * readwrite so we open it and check if it's been fixed.
3632 rd_check_readonly_access(rd
);
3633 if(rd
->read_status
== 'W'){
3634 rd
->access
= ReadWrite
;
3635 rd
->flags
|= REM_OUTOFDATE
;
3638 rd
->access
= ReadOnly
;
3641 if(rd
->flags
& REM_OUTOFDATE
){
3642 if(rd_update_local(rd
) != 0){
3645 "simple_read_remote_file: rd_update_local failed\n"));
3647 * Don't give up altogether. We still may be
3648 * able to use a cached copy.
3653 "%s: copied remote to local (%ld)\n",
3654 rd
->rn
? rd
->rn
: "?", (long)rd
->last_use
));
3658 if(rd
->access
== ReadWrite
)
3659 rd
->flags
|= DO_REMTRIM
;
3662 /* If we couldn't get to remote folder, try using the cached copy */
3663 if(rd
->access
== NoExists
|| rd
->flags
& REM_OUTOFDATE
){
3665 rd
->access
= ReadOnly
;
3666 rd
->flags
|= USE_OLD_CACHE
;
3667 q_status_message(SM_ORDER
, 3, 4,
3668 "Can't contact remote server, using cached copy");
3670 "Can't open remote file %s, using local cached copy %s readonly\n",
3671 rd
->rn
? rd
->rn
: "?",
3672 rd
->lf
? rd
->lf
: "?"));
3675 rd
->flags
&= ~DO_REMTRIM
;
3680 file
= read_file(rd
->lf
, READ_FROM_LOCALE
);
3684 rd_close_remdata(&rd
);
3689 /* special handling for messages that contain a mixed part in the
3690 * multipart alternative section.
3693 forward_multi_alt_mixed(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
3694 long int msgno
, char *sect_prefix
, void *msgtext
, gf_io_t pc
, int flags
)
3696 #define FWDTMPLEN 256
3697 BODY
*body
= NULL
, *text_body
= NULL
;
3699 char prefix_buf
[FWDTMPLEN
];
3701 char *section
, sect_buf
[256];
3702 int forward_raw_body
= 0;
3705 && orig_body
->type
== TYPEMULTIPART
3706 && orig_body
->subtype
3707 && !strucmp(orig_body
->subtype
, "alternative"))
3708 for(part
= orig_body
->nested
.part
, partnum
= 1;
3710 part
= part
->next
, partnum
++)
3711 if(part
->body
.type
== TYPEMULTIPART
3712 && part
->body
.subtype
3713 && !strucmp(part
->body
.subtype
, "MIXED"))
3716 if(part
== NULL
) return NULL
;
3718 snprintf(prefix_buf
, sizeof(prefix_buf
), "%.*s%s%s%d",
3719 FWDTMPLEN
/2, sect_prefix
? sect_prefix
: "",
3720 sect_prefix
? "." : "", flags
& FWD_NESTED
? "1." : "",
3722 prefix_buf
[sizeof(prefix_buf
)-1] = '\0';
3724 if(ps_global
->full_header
== 2
3725 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT
, ps_global
))
3726 forward_raw_body
= 1;
3727 if(sp_expunge_count(stream
))
3730 if(sect_prefix
&& forward_raw_body
== 0)
3731 snprintf(section
= sect_buf
, sizeof(sect_buf
), "%s.1", sect_prefix
);
3732 else if(sect_prefix
&& forward_raw_body
)
3733 section
= sect_prefix
;
3734 else if(!sect_prefix
&& forward_raw_body
)
3738 sect_buf
[sizeof(sect_buf
)-1] = '\0';
3740 body
= copy_body(NULL
, &part
->body
);
3742 /*--- The text part of the message ---*/
3743 if(!body
->nested
.part
){
3744 q_status_message(SM_ORDER
| SM_DING
, 3, 6,
3745 "Error referencing body part 1");
3746 mail_free_body(&body
);
3748 else if(body
->nested
.part
->body
.type
== TYPETEXT
) {
3749 char *new_charset
= NULL
;
3751 /*--- The first part is text ----*/
3752 text_body
= &body
->nested
.part
->body
;
3753 text_body
->contents
.text
.data
= msgtext
;
3754 if(text_body
->subtype
&& strucmp(text_body
->subtype
, "Plain")){
3755 /* this text is going to the composer, it should be Plain */
3756 fs_give((void **)&text_body
->subtype
);
3757 text_body
->subtype
= cpystr("PLAIN");
3759 if(!(flags
& FWD_ANON
)){
3760 forward_delimiter(pc
);
3761 reply_forward_header(stream
, msgno
,
3762 sect_prefix
, env
, pc
, "");
3765 if(!(get_body_part_text(stream
, &part
->body
,
3766 msgno
, section
, 0L, pc
,
3767 NULL
, &new_charset
, GBPT_NONE
)
3768 && fetch_contents(stream
, msgno
, prefix_buf
, body
)))
3769 mail_free_body(&body
);
3770 else if(new_charset
)
3771 set_parameter(&text_body
->parameter
, "charset", new_charset
);
3777 /*----------------------------------------------------------------------
3778 Build the body for the multipart/alternative part
3784 ----------------------------------------------------------------------*/
3786 forward_multi_alt(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
3787 long int msgno
, char *sect_prefix
, void *msgtext
, gf_io_t pc
, int flags
)
3789 #define FWDTMPLEN 256
3791 PART
*part
= NULL
, *bestpart
= NULL
;
3792 char tmp_buf
[FWDTMPLEN
];
3793 char *new_charset
= NULL
;
3794 int partnum
, bestpartnum
;
3796 /* try multipart mixed first */
3797 if((body
= forward_multi_alt_mixed(stream
, env
, orig_body
,
3798 msgno
, sect_prefix
, msgtext
, pc
, flags
)) != NULL
)
3801 if(ps_global
->force_prefer_plain
3802 || (!ps_global
->force_no_prefer_plain
3803 && F_ON(F_PREFER_PLAIN_TEXT
, ps_global
))){
3804 for(part
= orig_body
->nested
.part
, partnum
= 1;
3806 part
= part
->next
, partnum
++)
3807 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
3808 && (!part
->body
.subtype
3809 || !strucmp(part
->body
.subtype
, "plain")))
3814 * Else choose last alternative among plain or html parts.
3815 * Perhaps we should really be using mime_show() to make this
3816 * potentially more general than just plain or html.
3819 for(part
= orig_body
->nested
.part
, partnum
= 1;
3821 part
= part
->next
, partnum
++){
3822 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
3823 && ((!part
->body
.subtype
|| !strucmp(part
->body
.subtype
, "plain"))
3825 (part
->body
.subtype
&& !strucmp(part
->body
.subtype
, "html")))){
3827 bestpartnum
= partnum
;
3832 partnum
= bestpartnum
;
3836 * IF something's interesting insert it
3837 * AND forget the rest of the multipart
3840 body
= mail_newbody();
3841 body
->type
= TYPETEXT
;
3842 body
->contents
.text
.data
= msgtext
;
3844 /* record character set, flowing, etc */
3845 body
->parameter
= copy_parameters(part
->body
.parameter
);
3846 body
->size
.bytes
= part
->body
.size
.bytes
;
3848 if(!(flags
& FWD_ANON
)){
3849 forward_delimiter(pc
);
3850 reply_forward_header(stream
, msgno
, sect_prefix
, env
, pc
, "");
3852 /* FWDTMPLEN = sizeof(tmp_buf) */
3853 snprintf(tmp_buf
, sizeof(tmp_buf
), "%.*s%s%s%d",
3854 FWDTMPLEN
/2, sect_prefix
? sect_prefix
: "",
3855 sect_prefix
? "." : "", flags
& FWD_NESTED
? "1." : "",
3857 tmp_buf
[sizeof(tmp_buf
)-1] = '\0';
3858 get_body_part_text(stream
, &part
->body
, msgno
, tmp_buf
, 0L, pc
,
3859 NULL
, &new_charset
, GBPT_NONE
);
3862 * get_body_part_text translated the data to a new charset.
3863 * We need to record that fact in body.
3866 set_parameter(&body
->parameter
, "charset", new_charset
);
3869 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3870 "No suitable part found. Forwarding as attachment");
3877 reply_append_addr(struct mail_address
**dest
, struct mail_address
*src
)
3879 for( ; *dest
; dest
= &(*dest
)->next
)