1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2016 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/reply.h"
21 #include "../pith/send.h"
22 #include "../pith/init.h"
23 #include "../pith/state.h"
24 #include "../pith/conf.h"
25 #include "../pith/remote.h"
26 #include "../pith/status.h"
27 #include "../pith/mailview.h"
28 #include "../pith/filter.h"
29 #include "../pith/newmail.h"
30 #include "../pith/bldaddr.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/mimedesc.h"
33 #include "../pith/detach.h"
34 #include "../pith/help.h"
35 #include "../pith/pipe.h"
36 #include "../pith/addrstring.h"
37 #include "../pith/news.h"
38 #include "../pith/util.h"
39 #include "../pith/pattern.h"
40 #include "../pith/detoken.h"
41 #include "../pith/stream.h"
42 #include "../pith/busy.h"
43 #include "../pith/readfile.h"
44 #include "../pith/text.h"
45 #include "../pith/list.h"
46 #include "../pith/ablookup.h"
47 #include "../pith/mailcmd.h"
48 #include "../pith/margin.h"
49 #include "../pith/smime.h"
55 void bounce_mask_header(char **, char *);
58 int (*pith_opt_replyto_prompt
)(void);
59 int (*pith_opt_reply_to_all_prompt
)(int *);
63 * standard type of storage object used for body parts...
65 #define PART_SO_TYPE CharStar
68 char *(*pith_opt_user_agent_prefix
)(void);
70 /* compare two subjects and return if they are the same.
71 We compare stripped subjects, that is, those that do
72 not have re/fwd. Before we compare the subjects, we
73 decode them in case they were encoded.
74 Return value: 0 - not the same subject, 1 - same subject.
78 same_subject(char *s
, char *t
)
82 char *s1
, *s2
; /* holds decoded subjects from s and t */
85 if (s
== NULL
|| t
== NULL
)
86 return s
== t
? 1 : 0;
88 i
= strlen(s
); j
= strlen(t
);
92 s1
= (char *) rfc1522_decode_to_utf8((unsigned char *) u
, 6*len
+ 1, s
);
93 s2
= (char *) rfc1522_decode_to_utf8((unsigned char *) v
, 6*len
+ 1, t
);
94 mail_strip_subject(s1
, &u
);
95 mail_strip_subject(s2
, &v
);
99 fs_give((void **) &u
);
100 fs_give((void **) &v
);
107 * Returns: 1 if addresses successfully copied
108 * 0 on user cancel or error
112 * RSF_QUERY_REPLY_ALL
113 * RSF_FORCE_REPLY_ALL
116 * RSF_FORCE_REPLY_ALL
120 reply_harvest(struct pine
*ps
, long int msgno
, char *section
, ENVELOPE
*env
,
121 struct mail_address
**saved_from
, struct mail_address
**saved_to
,
122 struct mail_address
**saved_cc
, struct mail_address
**saved_resent
,
125 ADDRESS
*ap
, *ap2
, *rep_address
;
126 int ret
= 0, sniff_resent
= 0;
130 * If Reply-To is same as From just treat it like it was From.
131 * Otherwise, always use the reply-to if we're replying to more
132 * than one msg or say ok to using it, even if it's us.
133 * If there's no reply-to or it's the same as the from, assume
134 * that the user doesn't want to reply to himself, unless there's
137 if(env
->reply_to
&& !addr_lists_same(env
->reply_to
, env
->from
)
138 && (F_ON(F_AUTO_REPLY_TO
, ps
)
139 || ((*flags
) & RSF_FORCE_REPLY_TO
)
140 || (pith_opt_replyto_prompt
&& (*pith_opt_replyto_prompt
)() == 'y'))){
141 rep_field
= "reply-to";
142 rep_address
= env
->reply_to
;
146 rep_address
= env
->from
;
149 ap
= reply_cp_addr(ps
, msgno
, section
, rep_field
, *saved_from
,
150 (ADDRESS
*) NULL
, rep_address
, RCA_NOT_US
);
153 cmd_cancelled("Reply");
157 reply_append_addr(saved_from
, ap
);
159 /*--------- check for other recipients ---------*/
160 if(((*flags
) & (RSF_FORCE_REPLY_ALL
| RSF_QUERY_REPLY_ALL
))){
162 if((ap
= reply_cp_addr(ps
, msgno
, section
, "To", *saved_to
,
163 *saved_from
, env
->to
, RCA_NOT_US
)) != NULL
)
164 reply_append_addr(saved_to
, ap
);
166 if((ap
= reply_cp_addr(ps
, msgno
, section
, "Cc", *saved_cc
,
167 *saved_from
, env
->cc
, RCA_NOT_US
)) != NULL
)
168 reply_append_addr(saved_cc
, ap
);
171 * In these cases, we either need to look at the resent headers
172 * to include in the reply-to-all, or to decide whether or not
173 * we need to ask the reply-to-all question.
175 if(((*flags
) & RSF_FORCE_REPLY_ALL
)
176 || (((*flags
) & RSF_QUERY_REPLY_ALL
)
177 && ((!(*saved_from
) && !(*saved_cc
))
178 || (*saved_from
&& !(*saved_to
) && !(*saved_cc
))))){
181 if((ap2
= reply_resent(ps
, msgno
, section
)) != NULL
){
183 * look for bogus addr entries and replace
185 if((ap
= reply_cp_addr(ps
, 0, NULL
, NULL
, *saved_resent
,
186 *saved_from
, ap2
, RCA_NOT_US
)) != NULL
)
188 reply_append_addr(saved_resent
, ap
);
190 mail_free_address(&ap2
);
195 * It makes sense to ask reply-to-all now.
197 if(((*flags
) & RSF_QUERY_REPLY_ALL
)
198 && ((*saved_from
&& (*saved_to
|| *saved_cc
|| *saved_resent
))
199 || (*saved_cc
|| *saved_resent
))){
200 *flags
&= ~RSF_QUERY_REPLY_ALL
;
201 if(pith_opt_reply_to_all_prompt
202 && (*pith_opt_reply_to_all_prompt
)(flags
) < 0){
203 cmd_cancelled("Reply");
209 * If we just answered yes to the reply-to-all question and
210 * we still haven't collected the resent headers, do so now.
212 if(((*flags
) & RSF_FORCE_REPLY_ALL
) && !sniff_resent
213 && (ap2
= reply_resent(ps
, msgno
, section
))){
215 * look for bogus addr entries and replace
217 if((ap
= reply_cp_addr(ps
, 0, NULL
, NULL
, *saved_resent
,
218 *saved_from
, ap2
, RCA_NOT_US
)) != NULL
)
219 reply_append_addr(saved_resent
, ap
);
221 mail_free_address(&ap2
);
229 /*----------------------------------------------------------------------
230 Return a pointer to a copy of the given address list
231 filtering out those already in the "mask" lists and ourself.
233 Args: mask1 -- Don't copy if in this list
234 mask2 -- or if in this list
235 source -- List to be copied
236 us_too -- Don't filter out ourself.
237 flags -- RCA_NOT_US copy all addrs except for our own
238 RCA_ALL copy all addrs, including our own
239 RCA_ONLY_US copy only addrs that are our own
243 reply_cp_addr(struct pine
*ps
, long int msgno
, char *section
, char *field
,
244 struct mail_address
*mask1
, struct mail_address
*mask2
,
245 struct mail_address
*source
, int flags
)
247 ADDRESS
*tmp1
, *tmp2
, *ret
= NULL
, **ret_tail
;
249 /* can only choose one of these flags values */
250 assert(!((flags
& RCA_ALL
&& flags
& RCA_ONLY_US
)
251 || (flags
& RCA_ALL
&& flags
& RCA_NOT_US
)
252 || (flags
& RCA_ONLY_US
&& flags
& RCA_NOT_US
)));
254 for(tmp1
= source
; msgno
&& tmp1
; tmp1
= tmp1
->next
)
255 if(tmp1
->host
&& tmp1
->host
[0] == '.'){
260 if((h
= pine_fetchheader_lines(ps
? ps
->mail_stream
: NULL
,
261 msgno
, section
, fields
)) != NULL
){
267 strncpy(fname
, field
, sizeof(fname
)-2);
268 fname
[sizeof(fname
)-2] = '\0';
269 strncat(fname
, ":", sizeof(fname
)-strlen(fname
)-1);
270 fname
[sizeof(fname
)-1] = '\0';
272 for(p
= h
; (p
= strstr(p
, fname
)) != NULL
; )
273 rplstr(p
, q
-(p
-h
), strlen(fname
), ""); /* strip field strings */
275 sqznewlines(h
); /* blat out CR's & LF's */
276 for(p
= h
; (p
= strchr(p
, TAB
)) != NULL
; )
277 *p
++ = ' '; /* turn TABs to whitespace */
283 ret
= (ADDRESS
*) fs_get(sizeof(ADDRESS
));
284 memset(ret
, 0, sizeof(ADDRESS
));
286 /* get rid of leading white space */
287 for(p
= h
; *p
== SPACE
; p
++)
291 memmove(h
, p
, l
= strlen(p
));
295 /* base64 armor plate the gunk to protect against
296 * c-client quoting in output.
298 p
= (char *) rfc822_binary(h
, strlen(h
),
299 (unsigned long *) &l
);
301 fs_give((void **) &h
);
303 * Seems like the 4 ought to be a 2, but I'll leave it
304 * to be safe, in case something else adds 2 chars later.
307 ret
->mailbox
= (char *) fs_get(ll
* sizeof(char));
308 snprintf(ret
->mailbox
, ll
, "&%s", p
);
309 ret
->mailbox
[ll
-1] = '\0';
310 fs_give((void **) &p
);
311 ret
->host
= cpystr(RAWFIELD
);
319 for(source
= first_addr(source
); source
; source
= source
->next
){
320 for(tmp1
= first_addr(mask1
); tmp1
; tmp1
= tmp1
->next
)
321 if(address_is_same(source
, tmp1
)) /* it is in mask1, skip it */
324 for(tmp2
= first_addr(mask2
); !tmp1
&& tmp2
; tmp2
= tmp2
->next
)
325 if(address_is_same(source
, tmp2
)) /* it is in mask2, skip it */
329 * If there's no match in masks and this address satisfies the
330 * flags requirement, copy it.
332 if(!tmp1
&& !tmp2
/* no mask match */
333 && ((flags
& RCA_ALL
) /* including everybody */
334 || (flags
& RCA_ONLY_US
&& address_is_us(source
, ps
))
335 || (flags
& RCA_NOT_US
&& !address_is_us(source
, ps
)))){
337 source
->next
= NULL
; /* only copy one addr! */
338 *ret_tail
= rfc822_cpy_adr(source
);
339 ret_tail
= &(*ret_tail
)->next
;
340 source
->next
= tmp1
; /* restore rest of list */
349 set_role_from_msg(struct pine
*ps
, long int rflags
, long int msgno
, char *section
)
351 ACTION_S
*role
= NULL
;
353 SEARCHSET
*ss
= NULL
;
356 if(!nonempty_patterns(rflags
, &pstate
))
360 ss
= mail_newsearchset();
361 ss
->first
= ss
->last
= (unsigned long)msgno
;
364 /* Go through the possible roles one at a time until we get a match. */
365 pat
= first_pattern(&pstate
);
367 /* calculate this message's score if needed */
368 if(ss
&& pat
&& scores_are_used(SCOREUSE_GET
) & SCOREUSE_ROLES
&&
369 get_msg_score(ps
->mail_stream
, msgno
) == SCORE_UNDEF
)
370 (void)calculate_some_scores(ps
->mail_stream
, ss
, 0);
373 if(match_pattern(pat
->patgrp
, ps
->mail_stream
, ss
, section
,
374 get_msg_score
, SE_NOSERVER
|SE_NOPREFETCH
)){
375 if(!pat
->action
|| pat
->action
->bogus
)
381 pat
= next_pattern(&pstate
);
385 mail_free_searchset(&ss
);
392 * reply_seed - fill in reply header
396 reply_seed(struct pine
*ps
, ENVELOPE
*outgoing
, ENVELOPE
*env
,
397 struct mail_address
*saved_from
, struct mail_address
*saved_to
,
398 struct mail_address
*saved_cc
, struct mail_address
*saved_resent
,
399 char **fcc
, int replytoall
, char **errmsg
)
401 ADDRESS
**to_tail
, **cc_tail
;
403 to_tail
= &outgoing
->to
;
404 cc_tail
= &outgoing
->cc
;
407 /* Put Reply-To or From in To. */
408 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
409 (ADDRESS
*) NULL
, saved_from
, RCA_ALL
);
411 if(ps
->reply
.preserve_fields
){
413 to_tail
= &(*to_tail
)->next
;
415 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
416 (ADDRESS
*) NULL
, saved_to
, RCA_ALL
);
419 to_tail
= &(*to_tail
)->next
;
421 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
422 outgoing
->to
, saved_resent
, RCA_ALL
);
424 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
425 outgoing
->to
, saved_cc
, RCA_ALL
);
427 else{ /* and the rest in cc */
428 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
429 outgoing
->to
, saved_to
, RCA_ALL
);
430 while(*cc_tail
) /* stay on last address */
431 cc_tail
= &(*cc_tail
)->next
;
433 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
434 outgoing
->to
, saved_cc
, RCA_ALL
);
436 cc_tail
= &(*cc_tail
)->next
;
438 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
439 outgoing
->to
, saved_resent
, RCA_ALL
);
444 /* No From (maybe from us), put To in To. */
445 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
446 (ADDRESS
*)NULL
, saved_to
, RCA_ALL
);
447 /* and the rest in cc */
449 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
450 outgoing
->to
, saved_cc
, RCA_ALL
);
452 cc_tail
= &(*cc_tail
)->next
;
454 *cc_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->cc
,
455 outgoing
->to
, saved_resent
, RCA_ALL
);
459 /* No From or To, put everything else in To if replytoall, */
461 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
462 (ADDRESS
*) NULL
, saved_cc
, RCA_ALL
);
464 to_tail
= &(*to_tail
)->next
;
466 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
467 (ADDRESS
*) NULL
, saved_resent
, RCA_ALL
);
469 /* else, reply to original From which must be us */
472 * Put self in To if in original From.
474 if(!outgoing
->newsgroups
)
475 *to_tail
= reply_cp_addr(ps
, 0, NULL
, NULL
, outgoing
->to
,
476 (ADDRESS
*) NULL
, env
->from
, RCA_ALL
);
480 /* add any missing personal data */
481 reply_fish_personal(outgoing
, env
);
484 if(fcc
&& outgoing
->to
&& outgoing
->to
->host
[0] != '.'){
485 *fcc
= get_fcc_based_on_to(outgoing
->to
);
487 else if(fcc
&& outgoing
->newsgroups
){
488 char *newsgroups_returned
= NULL
;
491 rv
= news_grouper(outgoing
->newsgroups
, &newsgroups_returned
, errmsg
, fcc
, NULL
);
493 strcmp(outgoing
->newsgroups
, newsgroups_returned
)){
494 fs_give((void **)&outgoing
->newsgroups
);
495 outgoing
->newsgroups
= newsgroups_returned
;
498 fs_give((void **) &newsgroups_returned
);
503 /*----------------------------------------------------------------------
504 Test the given address lists for equivalence
506 Args: x -- First address list for comparison
507 y -- Second address for comparison
511 addr_lists_same(struct mail_address
*x
, struct mail_address
*y
)
513 for(x
= first_addr(x
), y
= first_addr(y
);
515 x
= first_addr(x
->next
), y
= first_addr(y
->next
)){
516 if(!address_is_same(x
, y
))
520 return(!x
&& !y
); /* true if ran off both lists */
524 /*----------------------------------------------------------------------
525 Test the given address against those in the given envelope's to, cc
527 Args: addr -- address for comparison
528 env -- envelope to compare against
532 addr_in_env(struct mail_address
*addr
, ENVELOPE
*env
)
536 for(ap
= env
? env
->to
: NULL
; ap
; ap
= ap
->next
)
537 if(address_is_same(addr
, ap
))
540 for(ap
= env
? env
->cc
: NULL
; ap
; ap
= ap
->next
)
541 if(address_is_same(addr
, ap
))
544 return(0); /* not found! */
548 /*----------------------------------------------------------------------
549 Add missing personal info dest from src envelope
551 Args: dest -- envelope to add personal info to
552 src -- envelope to get personal info from
554 NOTE: This is just kind of a courtesy function. It's really not adding
555 anything needed to get the mail thru, but it is nice for the user
556 under some odd circumstances.
559 reply_fish_personal(ENVELOPE
*dest
, ENVELOPE
*src
)
563 for(da
= dest
? dest
->to
: NULL
; da
; da
= da
->next
){
564 if(da
->personal
&& !da
->personal
[0])
565 fs_give((void **)&da
->personal
);
567 for(sa
= src
? src
->to
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
568 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
569 da
->personal
= cpystr(sa
->personal
);
571 for(sa
= src
? src
->cc
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
572 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
573 da
->personal
= cpystr(sa
->personal
);
576 for(da
= dest
? dest
->cc
: NULL
; da
; da
= da
->next
){
577 if(da
->personal
&& !da
->personal
[0])
578 fs_give((void **)&da
->personal
);
580 for(sa
= src
? src
->to
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
581 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
582 da
->personal
= cpystr(sa
->personal
);
584 for(sa
= src
? src
->cc
: NULL
; sa
&& !da
->personal
; sa
= sa
->next
)
585 if(address_is_same(da
, sa
) && sa
->personal
&& sa
->personal
[0])
586 da
->personal
= cpystr(sa
->personal
);
591 /*----------------------------------------------------------------------
592 Given a header field and envelope, build "References: " header data
599 reply_build_refs(ENVELOPE
*env
)
601 int len
, id_len
, first_ref_len
= 0, foldslop
;
602 char *p
, *refs
= NULL
, *h
= env
->references
;
603 char *first_ref
= NULL
, *tail_refs
= NULL
;
606 if(!(env
->message_id
&& (id_len
= strlen(env
->message_id
))))
611 * The length we have to work with doesn't seem to appear in any
612 * standards. Steve Jones says that in comp.news discussions he
613 * has seen 1024 as the longest length of a header value.
614 * In the inn news source we find MAXHEADERSIZE = 1024. It appears
615 * that is the maximum length of the header value, including
616 * newlines for folded lines (that is, the newlines are counted).
617 * We'll be conservative and figure every reference will take up a
618 * line of its own when we fold. We'll also count 2 for CRLF instead
619 * of just one for LF just to be safe. hubert 2001-jan
620 * J.B. Moreno <planb@newsreaders.com> says "The server limit is
621 * more commonly encountered at 999/1000 bytes [...]". So we'll
622 * back off to 999 instead of 1024.
624 #define MAXHEADERSIZE (999)
626 /* count the total number of potential folds, max of 2 bytes each */
627 for(foldslop
= 2, p
= h
; (p
= strstr(p
+1, "> <")); )
630 if((len
=strlen(h
)) + 1+id_len
+ foldslop
>= MAXHEADERSIZE
631 && (p
= strstr(h
, "> <"))){
633 * If the references line is so long that we are going to have
634 * to delete some of the references, delete the 2nd, 3rd, ...
635 * We don't want to delete the first message in the thread.
638 first_ref
= cpystr(h
);
639 first_ref_len
= strlen(first_ref
)+1; /* len includes space */
642 /* get rid of 2nd, 3rd, ... until it fits */
643 while((len
=strlen(tail_refs
)) + first_ref_len
+ 1+id_len
+
644 foldslop
>= MAXHEADERSIZE
645 && (p
= strstr(tail_refs
, "> <"))){
651 * If the individual references are seriously long, somebody
652 * is messing with us and we don't care if it works right.
654 if((len
=strlen(tail_refs
)) + first_ref_len
+ 1+id_len
+
655 foldslop
>= MAXHEADERSIZE
){
656 first_ref_len
= len
= 0;
659 fs_give((void **)&first_ref
);
665 refs
= (char *)fs_get((first_ref_len
+ 1+id_len
+ len
+ 1) *
667 snprintf(refs
, first_ref_len
+ 1+id_len
+ len
+ 1, "%s%s%s%s%s",
668 first_ref
? first_ref
: "",
669 first_ref
? " " : "",
670 tail_refs
? tail_refs
: "",
671 tail_refs
? " " : "",
673 refs
[first_ref_len
+ 1+id_len
+ len
] = '\0';
677 refs
= cpystr(env
->message_id
);
680 fs_give((void **)&first_ref
);
687 /*----------------------------------------------------------------------
688 Snoop for any Resent-* headers, and return an ADDRESS list
693 Returns: either NULL if no Resent-* or parsed ADDRESS struct list
696 reply_resent(struct pine
*pine_state
, long int msgno
, char *section
)
701 ADDRESS
*rlist
= NULL
, **a
, **b
;
702 char *hdrs
, *values
[RESENTCC
+1];
704 static char *fields
[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL
};
705 static char *fakedomain
= "@";
707 if((hdrs
= pine_fetchheader_lines(pine_state
->mail_stream
,
708 msgno
, section
, fields
)) != NULL
){
709 memset(values
, 0, (RESENTCC
+1) * sizeof(char *));
710 simple_header_parse(hdrs
, fields
, values
);
711 for(i
= RESENTFROM
; i
<= RESENTCC
; i
++)
712 rfc822_parse_adrlist(&rlist
, values
[i
],
713 (F_ON(F_COMPOSE_REJECTS_UNQUAL
, pine_state
))
714 ? fakedomain
: pine_state
->maildomain
);
717 for(a
= &rlist
; *a
; a
= &(*a
)->next
) /* compare every address */
718 for(b
= &(*a
)->next
; *b
; ) /* to the others */
719 if(address_is_same(*a
, *b
)){
722 if(!(*a
)->personal
){ /* preserve personal name */
723 (*a
)->personal
= (*b
)->personal
;
724 (*b
)->personal
= NULL
;
729 mail_free_address(&t
);
736 fs_give((void **) &hdrs
);
742 /*----------------------------------------------------------------------
743 Format and return subject suitable for the reply command
745 Args: subject -- subject to build reply subject for
746 buf -- buffer to use for writing. If non supplied, alloc one.
747 buflen -- length of buf if supplied, else ignored
749 Returns: with either "Re:" prepended or not, if already there.
750 returned string is allocated.
753 reply_subject(char *subject
, char *buf
, size_t buflen
)
755 size_t l
= (subject
&& *subject
) ? 4*strlen(subject
) : 10;
756 char *tmp
= fs_get(l
+ 1), *decoded
, *p
;
760 buf
= fs_get(buflen
);
763 /* decode any 8bit into tmp buffer */
764 decoded
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp
, l
+1, subject
);
767 if(decoded
/* already "re:" ? */
768 && (decoded
[0] == 'R' || decoded
[0] == 'r')
769 && (decoded
[1] == 'E' || decoded
[1] == 'e')){
771 if(decoded
[2] == ':')
772 snprintf(buf
, buflen
, "%.*s", (int)(buflen
-1), subject
);
773 else if((decoded
[2] == '[') && (p
= strchr(decoded
, ']'))){
775 while(*p
&& isspace((unsigned char)*p
)) p
++;
777 snprintf(buf
, buflen
, "%.*s", (int)(buflen
-1), subject
);
782 snprintf(buf
, buflen
, "Re: %.*s", (int)(buflen
-1),
783 (subject
&& *subject
) ? subject
: "your mail");
785 buf
[buflen
-1] = '\0';
787 fs_give((void **) &tmp
);
792 /*----------------------------------------------------------------------
793 return initials for the given personal name
795 Args: name -- Personal name to extract initials from
797 Returns: pointer to name overwritten with initials
800 reply_quote_initials(char *name
)
808 cbuf
.cbuf
[i
= 0] = '\0';
809 cbuf
.cbufp
= cbuf
.cbuf
;
810 cbuf
.cbufend
= cbuf
.cbuf
;
812 /* while there are still characters to look at */
814 /* skip to next initial */
815 while(*s
&& (unsigned char) *s
== ' ')
818 if(!utf8_to_ucs4_oneatatime((unsigned char) *s
++ & 0xff, &cbuf
, &ucs
, NULL
)){
823 /* skip over non-alpha stuff */
824 if(i
== 0 && !isalnum((unsigned char) cbuf
.cbuf
[0]))
828 for(j
= 0; j
<= i
; j
++) *w
++ = cbuf
.cbuf
[j
];
830 /* skip to end of this piece of name */
831 while(*s
&& (unsigned char) *s
!= ' ')
835 cbuf
.cbuf
[i
= 0] = '\0';
836 cbuf
.cbufp
= cbuf
.cbuf
;
837 cbuf
.cbufend
= cbuf
.cbuf
;
847 * There is an assumption that MAX_SUBSTITUTION is <= the size of the
848 * tokens being substituted for (only in the size of buf below).
850 #define MAX_SUBSTITUTION 6
851 #define MAX_PREFIX 63
852 static char *from_token
= "_FROM_";
853 static char *nick_token
= "_NICK_";
854 static char *init_token
= "_INIT_";
856 /*----------------------------------------------------------------------
857 return a quoting string, "> " by default, for replied text
859 Args: env -- envelope of message being replied to
861 Returns: malloc'd array containing quoting string, freed by caller
864 reply_quote_str(ENVELOPE
*env
)
866 char *prefix
, *repl
, *p
, buf
[MAX_PREFIX
+1], pbf
[MAX_SUBSTITUTION
+1];
868 strncpy(buf
, ps_global
->VAR_REPLY_STRING
, sizeof(buf
)-1);
869 buf
[sizeof(buf
)-1] = '\0';
871 /* set up the prefix to quote included text */
872 if((p
= strstr(buf
, from_token
)) != NULL
){
873 repl
= (env
&& env
->from
&& env
->from
->mailbox
) ? env
->from
->mailbox
875 strncpy(pbf
, repl
, sizeof(pbf
)-1);
876 pbf
[sizeof(pbf
)-1] = '\0';;
877 rplstr(p
, sizeof(buf
)-(p
-buf
), strlen(from_token
), pbf
);
880 if((p
= strstr(buf
, nick_token
)) != NULL
){
884 get_nickname_from_addr(env
->from
, tmp_20k_buf
, 1000))
886 strncpy(pbf
, repl
, sizeof(pbf
)-1);
887 pbf
[sizeof(pbf
)-1] = '\0';;
888 rplstr(p
, sizeof(buf
)-(p
-buf
), strlen(nick_token
), pbf
);
891 if((p
= strstr(buf
, init_token
)) != NULL
){
893 char buftmp
[MAILTMPLEN
];
895 snprintf(buftmp
, sizeof(buftmp
), "%.200s",
896 (env
&& env
->from
&& env
->from
->personal
) ? env
->from
->personal
: "");
897 buftmp
[sizeof(buftmp
)-1] = '\0';
899 repl
= (env
&& env
->from
&& env
->from
->personal
)
900 ? reply_quote_initials(q
= cpystr((char *)rfc1522_decode_to_utf8(
901 (unsigned char *)tmp_20k_buf
,
902 SIZEOF_20KBUF
, buftmp
)))
905 istrncpy(pbf
, repl
, sizeof(pbf
)-1);
906 pbf
[sizeof(pbf
)-1] = '\0';;
907 rplstr(p
, sizeof(buf
)-(p
-buf
), strlen(init_token
), pbf
);
909 fs_give((void **)&q
);
912 prefix
= removing_quotes(cpystr(buf
));
918 reply_quote_str_contains_tokens(void)
920 return(ps_global
->VAR_REPLY_STRING
&& ps_global
->VAR_REPLY_STRING
[0] &&
921 (strstr(ps_global
->VAR_REPLY_STRING
, from_token
) ||
922 strstr(ps_global
->VAR_REPLY_STRING
, nick_token
) ||
923 strstr(ps_global
->VAR_REPLY_STRING
, init_token
)));
927 /*----------------------------------------------------------------------
928 Build the body for the message number/part being replied to
932 Result: BODY structure suitable for sending
934 ----------------------------------------------------------------------*/
936 reply_body(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
937 long int msgno
, char *sect_prefix
, void *msgtext
, char *prefix
,
938 int plustext
, ACTION_S
*role
, int toplevel
, REDRAFT_POS_S
**redraft_pos
)
940 #define SECTBUFLEN 256
941 char *p
, *sig
= NULL
, *section
, sect_buf
[SECTBUFLEN
];
942 BODY
*body
= NULL
, *tmp_body
= NULL
;
945 int impl
, template_len
= 0, leave_cursor_at_top
= 0, reply_raw_body
= 0;
947 if(sect_prefix
) /* SECTBUFLEN = sizeof(sect_buf) */
948 snprintf(section
= sect_buf
, sizeof(sect_buf
), "%.*s.1", SECTBUFLEN
-1, sect_prefix
);
952 sect_buf
[sizeof(sect_buf
)-1] = '\0';
954 if(ps_global
->full_header
== 2
955 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT
, ps_global
))
958 gf_set_so_writec(&pc
, (STORE_S
*) msgtext
);
964 filtered
= detoken(role
, env
, 0,
965 ps_global
->reply
.signature_bottom
,
966 0, redraft_pos
, &impl
);
969 so_puts((STORE_S
*)msgtext
, filtered
);
971 template_len
= strlen(filtered
);
973 leave_cursor_at_top
++;
976 fs_give((void **)&filtered
);
985 (sig
= reply_signature(role
, env
, redraft_pos
, &impl
)) &&
986 !ps_global
->reply
.signature_bottom
){
989 * If CURSORPOS was set explicitly in sig_file, and there was a
990 * template file before that, we need to adjust the offset by the
991 * length of the template file. However, if the template had
992 * a set CURSORPOS in it then impl was 2 before getting to the
993 * signature, so offset wouldn't have been reset by the signature
994 * CURSORPOS and offset would already be correct. That case will
995 * be ok here because template_len will be 0 and adding it does
996 * nothing. If template
997 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
998 * by the CURSORPOS in the sig. In that case we have to adjust the
999 * offset. That's what the next line does. It adjusts it if
1000 * template_len is nonzero and if CURSORPOS was set in sig_file.
1003 (*redraft_pos
)->offset
+= template_len
;
1006 so_puts((STORE_S
*)msgtext
, sig
);
1009 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
1010 * is set, we won't have used it yet and want it to be non-NULL.
1012 fs_give((void **)&sig
);
1016 * Only put cursor in sig if there is a cursorpos there but not
1017 * one in the template, and sig-at-bottom.
1019 if(!(sig
&& impl
== 2 && !leave_cursor_at_top
))
1020 leave_cursor_at_top
++;
1024 || orig_body
->type
== TYPETEXT
1026 || !ps_global
->reply
.keep_attach
){
1027 char *charset
= NULL
;
1029 /*------ Simple text-only message ----*/
1030 body
= mail_newbody();
1031 body
->type
= TYPETEXT
;
1032 body
->contents
.text
.data
= msgtext
;
1033 reply_delimiter(env
, role
, pc
);
1034 if(ps_global
->reply
.include_header
)
1035 reply_forward_header(stream
, msgno
, sect_prefix
,
1038 if(!orig_body
|| reply_raw_body
|| reply_body_text(orig_body
, &tmp_body
)){
1041 bodyp
= reply_raw_body
? NULL
: tmp_body
;
1044 * We set the charset in the outgoing message to the same
1045 * as the one in the message we're replying to unless it
1046 * is the unknown charset. We do that in order to attempt
1047 * to preserve the same charset in the reply if possible.
1048 * It may be safer to just set it to whatever we want instead
1049 * but then the receiver may not be able to read it.
1052 && (charset
= parameter_val(bodyp
->parameter
, "charset"))
1053 && strucmp(charset
, UNKNOWN_CHARSET
))
1054 set_parameter(&body
->parameter
, "charset", charset
);
1057 fs_give((void **) &charset
);
1059 get_body_part_text(stream
, bodyp
, msgno
,
1060 bodyp
? (p
= body_partno(stream
, msgno
, bodyp
))
1062 0L, pc
, prefix
, NULL
, GBPT_NONE
);
1064 fs_give((void **) &p
);
1067 gf_puts(NEWLINE
, pc
);
1068 gf_puts(" [NON-Text Body part not included]", pc
);
1069 gf_puts(NEWLINE
, pc
);
1072 else if(orig_body
->type
== TYPEMULTIPART
){
1073 /*------ Message is Multipart ------*/
1074 if(orig_body
->subtype
1075 && !strucmp(orig_body
->subtype
, "signed")
1076 && orig_body
->nested
.part
){
1077 /* operate only on the signed part */
1078 body
= reply_body(stream
, env
,
1079 &orig_body
->nested
.part
->body
,
1080 msgno
, section
, msgtext
, prefix
,
1081 plustext
, role
, 0, redraft_pos
);
1083 else if(orig_body
->subtype
1084 && !strucmp(orig_body
->subtype
, "alternative")){
1085 /* Set up the simple text reply */
1086 body
= mail_newbody();
1087 body
->type
= TYPETEXT
;
1088 body
->contents
.text
.data
= msgtext
;
1090 if(reply_body_text(orig_body
, &tmp_body
)){
1091 reply_delimiter(env
, role
, pc
);
1092 if(ps_global
->reply
.include_header
)
1093 reply_forward_header(stream
, msgno
, sect_prefix
,
1096 get_body_part_text(stream
, tmp_body
, msgno
,
1097 p
= body_partno(stream
,msgno
,tmp_body
),
1098 0L, pc
, prefix
, NULL
, GBPT_NONE
);
1100 fs_give((void **) &p
);
1103 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
1104 "No suitable multipart text found for inclusion!");
1107 body
= copy_body(NULL
, orig_body
);
1110 * whatever subtype it is, demote it
1111 * to plain old MIXED.
1114 fs_give((void **) &body
->subtype
);
1116 body
->subtype
= cpystr("Mixed");
1118 if(body
->nested
.part
&&
1119 body
->nested
.part
->body
.type
== TYPETEXT
) {
1120 char *new_charset
= NULL
;
1122 /*---- First part of the message is text -----*/
1123 body
->nested
.part
->body
.contents
.text
.data
= msgtext
;
1124 if(body
->nested
.part
->body
.subtype
&&
1125 strucmp(body
->nested
.part
->body
.subtype
, "Plain")){
1126 fs_give((void **)&body
->nested
.part
->body
.subtype
);
1127 body
->nested
.part
->body
.subtype
= cpystr("Plain");
1129 reply_delimiter(env
, role
, pc
);
1130 if(ps_global
->reply
.include_header
)
1131 reply_forward_header(stream
, msgno
, sect_prefix
,
1134 if(!(get_body_part_text(stream
,
1135 &orig_body
->nested
.part
->body
,
1136 msgno
, section
, 0L, pc
, prefix
,
1137 &new_charset
, GBPT_NONE
)
1138 && fetch_contents(stream
, msgno
, sect_prefix
, body
)))
1139 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1140 _("Error including all message parts"));
1141 else if(new_charset
)
1142 set_parameter(&body
->nested
.part
->body
.parameter
, "charset", new_charset
);
1144 else if(orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
1145 && orig_body
->nested
.part
->body
.subtype
1146 && !strucmp(orig_body
->nested
.part
->body
.subtype
,
1148 && reply_body_text(&orig_body
->nested
.part
->body
,
1152 reply_delimiter(env
, role
, pc
);
1153 if(ps_global
->reply
.include_header
)
1154 reply_forward_header(stream
, msgno
, sect_prefix
,
1156 /* SECTBUFLEN = sizeof(sect_buf) */
1157 snprintf(sect_buf
, sizeof(sect_buf
), "%.*s%s%.*s",
1159 sect_prefix
? sect_prefix
: "",
1160 sect_prefix
? "." : "",
1162 p
= partno(orig_body
, tmp_body
));
1163 sect_buf
[sizeof(sect_buf
)-1] = '\0';
1164 fs_give((void **) &p
);
1165 get_body_part_text(stream
, tmp_body
, msgno
,
1166 sect_buf
, 0L, pc
, prefix
,
1169 part
= body
->nested
.part
->next
;
1170 body
->nested
.part
->next
= NULL
;
1171 mail_free_body_part(&body
->nested
.part
);
1172 body
->nested
.part
= mail_newbody_part();
1173 body
->nested
.part
->body
.type
= TYPETEXT
;
1174 body
->nested
.part
->body
.subtype
= cpystr("Plain");
1175 body
->nested
.part
->body
.contents
.text
.data
= msgtext
;
1176 body
->nested
.part
->next
= part
;
1177 /* SECTBUFLEN = sizeof(sect_buf) */
1178 for(partnum
= 2; part
!= NULL
; part
= part
->next
){
1179 snprintf(sect_buf
, sizeof(sect_buf
), "%.*s%s%d",
1181 sect_prefix
? sect_prefix
: "",
1182 sect_prefix
? "." : "", partnum
++);
1183 sect_buf
[sizeof(sect_buf
)-1] = '\0';
1185 if(!fetch_contents(stream
, msgno
,
1186 sect_buf
, &part
->body
)){
1192 /*--- Fetch the original pieces ---*/
1193 if(!fetch_contents(stream
, msgno
, sect_prefix
, body
))
1194 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1195 _("Error including all message parts"));
1197 /*--- No text part, create a blank one ---*/
1198 part
= mail_newbody_part();
1199 part
->next
= body
->nested
.part
;
1200 body
->nested
.part
= part
;
1201 part
->body
.contents
.text
.data
= msgtext
;
1206 /*---- Single non-text message of some sort ----*/
1207 body
= mail_newbody();
1208 body
->type
= TYPEMULTIPART
;
1209 part
= mail_newbody_part();
1210 body
->nested
.part
= part
;
1212 /*--- The first part, a blank text part to be edited ---*/
1213 part
->body
.type
= TYPETEXT
;
1214 part
->body
.contents
.text
.data
= msgtext
;
1216 /*--- The second part, what ever it is ---*/
1217 part
->next
= mail_newbody_part();
1219 part
->body
.id
= generate_message_id();
1220 copy_body(&(part
->body
), orig_body
);
1223 * the idea here is to fetch part into storage object
1225 if((part
->body
.contents
.text
.data
= (void *) so_get(PART_SO_TYPE
,
1226 NULL
,EDIT_ACCESS
)) != NULL
){
1227 if((p
= pine_mail_fetch_body(stream
, msgno
, section
,
1228 &part
->body
.size
.bytes
, NIL
)) != NULL
){
1229 so_nputs((STORE_S
*)part
->body
.contents
.text
.data
,
1230 p
, part
->body
.size
.bytes
);
1233 mail_free_body(&body
);
1236 mail_free_body(&body
);
1240 /*--------- No text included --------*/
1241 body
= mail_newbody();
1242 body
->type
= TYPETEXT
;
1243 body
->contents
.text
.data
= msgtext
;
1246 if(!leave_cursor_at_top
){
1250 /* rewind and count chars to start of sig file */
1251 so_seek((STORE_S
*)msgtext
, 0L, 0);
1252 while(so_readc(&c
, (STORE_S
*)msgtext
))
1256 *redraft_pos
= (REDRAFT_POS_S
*)fs_get(sizeof(**redraft_pos
));
1257 memset((void *)*redraft_pos
, 0,sizeof(**redraft_pos
));
1258 (*redraft_pos
)->hdrname
= cpystr(":");
1262 * If explicit cursor positioning in sig file,
1263 * add offset to start of sig file plus offset into sig file.
1264 * Else, just offset to start of sig file.
1266 (*redraft_pos
)->offset
+= cnt
;
1271 so_puts((STORE_S
*)msgtext
, sig
);
1273 fs_give((void **)&sig
);
1276 gf_clear_so_writec((STORE_S
*) msgtext
);
1283 * reply_part - first replyable multipart of a multipart.
1286 reply_body_text(struct mail_bodystruct
*body
, struct mail_bodystruct
**new_body
)
1294 case TYPEMULTIPART
:
1295 if(body
->subtype
&& !strucmp(body
->subtype
, "alternative")){
1299 if(ps_global
->force_prefer_plain
1300 || (!ps_global
->force_no_prefer_plain
1301 && F_ON(F_PREFER_PLAIN_TEXT
, ps_global
))){
1302 for(part
= body
->nested
.part
; part
; part
= part
->next
)
1303 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
1304 && (!part
->body
.subtype
1305 || !strucmp(part
->body
.subtype
, "plain"))){
1306 *new_body
= &part
->body
;
1312 * Else choose last alternative among plain or html parts.
1313 * Perhaps we should really be using mime_show() to make this
1314 * potentially more general than just plain or html.
1316 for(part
= body
->nested
.part
; part
; part
= part
->next
){
1317 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
1318 && ((!part
->body
.subtype
|| !strucmp(part
->body
.subtype
, "plain"))
1320 (part
->body
.subtype
&& !strucmp(part
->body
.subtype
, "html")))){
1322 *new_body
= &part
->body
;
1329 else if(body
->nested
.part
)
1330 /* NOTE: we're only interested in "first" part of mixed */
1331 return(reply_body_text(&body
->nested
.part
->body
, new_body
));
1345 reply_signature(ACTION_S
*role
, ENVELOPE
*env
, REDRAFT_POS_S
**redraft_pos
, int *impl
)
1350 sig
= detoken(role
, env
,
1351 2, ps_global
->reply
.signature_bottom
? 0 : 1, 1,
1354 if(!ps_global
->reply
.signature_bottom
&& (!sig
|| !*sig
)){
1356 fs_give((void **)&sig
);
1358 l
= 2 * strlen(NEWLINE
);
1359 sig
= (char *)fs_get((l
+1) * sizeof(char));
1360 strncpy(sig
, NEWLINE
, l
);
1362 strncat(sig
, NEWLINE
, l
+1-1-strlen(sig
));
1372 * Buf is at least size maxlen+1
1375 get_addr_data(ENVELOPE
*env
, IndexColType type
, char *buf
, size_t maxlen
)
1377 ADDRESS
*addr
= NULL
;
1378 ADDRESS
*last_to
= NULL
;
1379 ADDRESS
*first_addr
= NULL
, *second_addr
= NULL
;
1380 ADDRESS
*third_addr
= NULL
, *fourth_addr
= NULL
;
1389 addr
= env
? env
->from
: NULL
;
1393 addr
= env
? env
->to
: NULL
;
1397 addr
= env
? env
->cc
: NULL
;
1401 addr
= env
? env
->sender
: NULL
;
1405 * Recips is To and Cc togeter. We hook the two adrlists together
1409 addr
= env
? env
->to
: NULL
;
1410 /* Find end of To list */
1411 for(last_to
= addr
; last_to
&& last_to
->next
; last_to
= last_to
->next
)
1414 /* Make the end of To list point to cc list */
1416 last_to
->next
= (env
? env
->cc
: NULL
);
1424 if(env
&& env
->from
&& env
->from
->personal
){
1425 char *name
, *initials
= NULL
;
1427 name
= (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
1428 SIZEOF_20KBUF
, env
->from
->personal
);
1429 if(name
== env
->from
->personal
){
1430 strncpy(tmp_20k_buf
, name
, SIZEOF_20KBUF
-1);
1431 tmp_20k_buf
[SIZEOF_20KBUF
- 1] = '\0';
1436 initials
= reply_quote_initials(name
);
1437 iutf8ncpy(buf
, initials
, maxlen
);
1448 orig_maxlen
= maxlen
;
1451 /* skip over rest of c-client group addr */
1452 if(first_addr
&& first_addr
->mailbox
&& !first_addr
->host
){
1453 for(second_addr
= first_addr
->next
;
1454 second_addr
&& second_addr
->host
;
1455 second_addr
= second_addr
->next
)
1458 if(second_addr
&& !second_addr
->host
)
1459 second_addr
= second_addr
->next
;
1461 else if(!(first_addr
&& first_addr
->host
&& first_addr
->host
[0] == '.'))
1462 second_addr
= first_addr
? first_addr
->next
: NULL
;
1464 if(second_addr
&& second_addr
->mailbox
&& !second_addr
->host
){
1465 for(third_addr
= second_addr
->next
;
1466 third_addr
&& third_addr
->host
;
1467 third_addr
= third_addr
->next
)
1470 if(third_addr
&& !third_addr
->host
)
1471 third_addr
= third_addr
->next
;
1473 else if(!(second_addr
&& second_addr
->host
&& second_addr
->host
[0] == '.'))
1474 third_addr
= second_addr
? second_addr
->next
: NULL
;
1476 if(third_addr
&& third_addr
->mailbox
&& !third_addr
->host
){
1477 for(fourth_addr
= third_addr
->next
;
1478 fourth_addr
&& fourth_addr
->host
;
1479 fourth_addr
= fourth_addr
->next
)
1482 if(fourth_addr
&& !fourth_addr
->host
)
1483 fourth_addr
= fourth_addr
->next
;
1485 else if(!(third_addr
&& third_addr
->host
&& third_addr
->host
[0] == '.'))
1486 fourth_addr
= third_addr
? third_addr
->next
: NULL
;
1488 /* Just attempting to make a nice display */
1489 if(first_addr
&& ((first_addr
->personal
&& first_addr
->personal
[0]) ||
1490 (first_addr
->mailbox
&& first_addr
->mailbox
[0]))){
1492 if((second_addr
->personal
&& second_addr
->personal
[0]) ||
1493 (second_addr
->mailbox
&& second_addr
->mailbox
[0])){
1495 if((third_addr
->personal
&& third_addr
->personal
[0]) ||
1496 (third_addr
->mailbox
&& third_addr
->mailbox
[0])){
1519 a_little_addr_string(first_addr
, p
, maxlen
);
1520 else if(cntaddr
== 2){
1521 a_little_addr_string(first_addr
, p
, maxlen
);
1522 maxlen
-= (l
=strlen(p
));
1525 strncpy(p
, " and ", maxlen
);
1528 a_little_addr_string(second_addr
, p
, maxlen
);
1531 else if(cntaddr
== 3){
1532 a_little_addr_string(first_addr
, p
, maxlen
);
1533 maxlen
-= (l
=strlen(p
));
1536 strncpy(p
, ", ", maxlen
);
1539 a_little_addr_string(second_addr
, p
, maxlen
);
1540 maxlen
-= (l
=strlen(p
));
1543 strncpy(p
, ", and ", maxlen
);
1546 a_little_addr_string(third_addr
, p
, maxlen
);
1550 else if(cntaddr
> 3){
1551 a_little_addr_string(first_addr
, p
, maxlen
);
1552 maxlen
-= (l
=strlen(p
));
1555 strncpy(p
, ", ", maxlen
);
1558 a_little_addr_string(second_addr
, p
, maxlen
);
1559 maxlen
-= (l
=strlen(p
));
1562 strncpy(p
, ", ", maxlen
);
1565 a_little_addr_string(third_addr
, p
, maxlen
);
1566 maxlen
-= (l
=strlen(p
));
1569 strncpy(p
, ", and others", maxlen
);
1570 else if(maxlen
>= 3)
1571 strncpy(p
, "...", maxlen
);
1578 a_string
= addr_list_string(addr
, NULL
, 0);
1579 iutf8ncpy(buf
, a_string
, maxlen
);
1581 fs_give((void **)&a_string
);
1585 last_to
->next
= NULL
;
1587 buf
[orig_maxlen
] = '\0';
1592 * Buf is at least size maxlen+1
1595 get_news_data(ENVELOPE
*env
, IndexColType type
, char *buf
, size_t maxlen
)
1597 int cntnews
= 0, orig_maxlen
;
1598 char *news
= NULL
, *p
, *q
;
1604 case iNewsAndRecips
:
1605 case iRecipsAndNews
:
1606 news
= env
? env
->newsgroups
: NULL
;
1610 if(ps_global
->mail_stream
&& IS_NEWS(ps_global
->mail_stream
))
1611 news
= ps_global
->cur_folder
;
1619 orig_maxlen
= maxlen
;
1623 while(isspace((unsigned char)*q
))
1629 while((q
= strindex(q
, ',')) != NULL
){
1631 while(isspace((unsigned char)*q
))
1642 istrncpy(buf
, news
, maxlen
);
1644 removing_leading_and_trailing_white_space(buf
);
1646 else if(cntnews
== 2){
1649 while(isspace((unsigned char)*q
))
1652 while(maxlen
> 0 && *q
&& !isspace((unsigned char)*q
) && *q
!= ','){
1658 strncpy(p
, " and ", maxlen
);
1663 while(isspace((unsigned char)*q
) || *q
== ',')
1666 while(maxlen
> 0 && *q
&& !isspace((unsigned char)*q
) && *q
!= ','){
1673 istrncpy(tmp_20k_buf
, buf
, 10000);
1674 strncpy(buf
, tmp_20k_buf
, orig_maxlen
);
1676 else if(cntnews
> 2){
1681 while(isspace((unsigned char)*q
))
1684 while(maxlen
> 0 && *q
&& !isspace((unsigned char)*q
) && *q
!= ','){
1690 snprintf(b
, sizeof(b
), " and %d other newsgroups", cntnews
-1);
1691 b
[sizeof(b
)-1] = '\0';
1692 if(maxlen
>= strlen(b
))
1693 strncpy(p
, b
, maxlen
);
1694 else if(maxlen
>= 3)
1695 strncpy(p
, "...", maxlen
);
1697 buf
[orig_maxlen
] = '\0';
1699 istrncpy(tmp_20k_buf
, buf
, 10000);
1700 tmp_20k_buf
[10000-1] = '\0';
1701 strncpy(buf
, tmp_20k_buf
, orig_maxlen
);
1704 buf
[orig_maxlen
] = '\0';
1709 * Buf is at least size maxlen+1
1712 get_reply_data(ENVELOPE
*env
, ACTION_S
*role
, IndexColType type
, char *buf
, size_t maxlen
)
1715 IndexColType addrtype
;
1720 case iRDate
: case iSDate
: case iSTime
:
1721 case iS1Date
: case iS2Date
: case iS3Date
: case iS4Date
:
1722 case iSDateIso
: case iSDateIsoS
:
1723 case iSDateS1
: case iSDateS2
: case iSDateS3
: case iSDateS4
:
1725 case iSDateTimeIso
: case iSDateTimeIsoS
:
1726 case iSDateTimeS1
: case iSDateTimeS2
: case iSDateTimeS3
: case iSDateTimeS4
:
1728 case iSDateTimeIso24
: case iSDateTimeIsoS24
:
1729 case iSDateTimeS124
: case iSDateTimeS224
: case iSDateTimeS324
: case iSDateTimeS424
:
1730 case iDateIso
: case iDateIsoS
: case iTime24
: case iTime12
:
1731 case iDay
: case iDayOrdinal
: case iDay2Digit
:
1732 case iMonAbb
: case iMonLong
: case iMon
: case iMon2Digit
:
1733 case iYear
: case iYear2Digit
:
1734 case iDate
: case iLDate
:
1735 case iTimezone
: case iDayOfWeekAbb
: case iDayOfWeek
:
1736 case iPrefDate
: case iPrefTime
: case iPrefDateTime
:
1737 if(env
&& env
->date
&& env
->date
[0] && maxlen
>= 20)
1738 date_str((char *) env
->date
, type
, 1, buf
, maxlen
+1, 0);
1750 case iCurDayOfWeekAbb
:
1756 case iCurYear2Digit
:
1758 case iCurPrefDateTime
:
1765 case iLstMonYear2Digit
:
1767 case iLstYear2Digit
:
1769 date_str(NULL
, type
, 1, buf
, maxlen
+1, 0);
1779 get_addr_data(env
, type
, buf
, maxlen
);
1783 if(role
&& role
->nick
){
1784 strncpy(buf
, role
->nick
, maxlen
);
1790 if(maxlen
>= strlen(NEWLINE
)){
1791 strncpy(buf
, NEWLINE
, maxlen
);
1798 if(env
&& env
->from
&& env
->from
->mailbox
&& env
->from
->mailbox
[0] &&
1799 strlen(env
->from
->mailbox
) <= maxlen
){
1800 strncpy(buf
, env
->from
->mailbox
, maxlen
);
1802 if(type
== iAddress
&&
1804 env
->from
->host
[0] &&
1805 env
->from
->host
[0] != '.' &&
1806 strlen(buf
) + strlen(env
->from
->host
) + 1 <= maxlen
){
1807 strncat(buf
, "@", maxlen
+1-1-strlen(buf
));
1809 strncat(buf
, env
->from
->host
, maxlen
+1-1-strlen(buf
));
1818 get_news_data(env
, type
, buf
, maxlen
);
1823 case iRecipsAndNews
:
1824 case iNewsAndRecips
:
1825 if(type
== iToAndNews
|| type
== iNewsAndTo
)
1830 if(env
&& env
->newsgroups
){
1831 space
= (char *)fs_get((maxlen
+1) * sizeof(char));
1832 get_news_data(env
, type
, space
, maxlen
);
1835 get_addr_data(env
, addrtype
, buf
, maxlen
);
1837 if(space
&& *space
&& *buf
){
1838 if(strlen(space
) + strlen(buf
) + 5 > maxlen
){
1839 if(strlen(space
) > maxlen
/2)
1840 get_news_data(env
, type
, space
, maxlen
- strlen(buf
) - 5);
1842 get_addr_data(env
, addrtype
, buf
, maxlen
- strlen(space
) - 5);
1845 if(type
== iToAndNews
|| type
== iRecipsAndNews
)
1846 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s and %s", buf
, space
);
1848 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s and %s", space
, buf
);
1850 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
1852 strncpy(buf
, tmp_20k_buf
, maxlen
);
1855 else if(space
&& *space
){
1856 strncpy(buf
, space
, maxlen
);
1861 fs_give((void **)&space
);
1866 if(env
&& env
->subject
){
1868 unsigned char *p
, *tmp
= NULL
;
1870 if((n
= 4*strlen(env
->subject
)) > SIZEOF_20KBUF
-1){
1872 p
= tmp
= (unsigned char *)fs_get(len
* sizeof(char));
1875 len
= SIZEOF_20KBUF
;
1876 p
= (unsigned char *)tmp_20k_buf
;
1879 istrncpy(buf
, (char *)rfc1522_decode_to_utf8(p
, len
, env
->subject
), maxlen
);
1884 fs_give((void **)&tmp
);
1890 if(env
&& env
->message_id
){
1891 strncpy(buf
, env
->message_id
, maxlen
);
1907 * reply_delimiter - output formatted reply delimiter for given envelope
1908 * with supplied character writing function.
1911 reply_delimiter(ENVELOPE
*env
, ACTION_S
*role
, gf_io_t pc
)
1913 #define MAX_DELIM 2000
1914 char buf
[MAX_DELIM
+1];
1916 char *filtered
= NULL
;
1917 int contains_newline_token
= 0;
1923 strncpy(buf
, ps_global
->VAR_REPLY_INTRO
, MAX_DELIM
);
1924 buf
[MAX_DELIM
] = '\0';
1925 /* preserve exact default behavior from before */
1926 if(!strcmp(buf
, DEFAULT_REPLY_INTRO
)){
1930 parse_date((char *) env
->date
, &d
);
1931 include_date
= !(d
.day
== -1 || d
.month
== -1 || d
.year
== -1);
1933 gf_puts("On ", pc
); /* All delims have... */
1934 if(d
.wkday
!= -1){ /* "On day, date month year" */
1935 gf_puts(day_abbrev(d
.wkday
), pc
); /* in common */
1939 gf_puts(int2string(d
.day
), pc
);
1941 gf_puts(month_abbrev(d
.month
), pc
);
1943 gf_puts(int2string(d
.year
), pc
);
1947 && ((env
->from
->personal
&& env
->from
->personal
[0])
1948 || (env
->from
->mailbox
&& env
->from
->mailbox
[0]))){
1949 char buftmp
[MAILTMPLEN
];
1951 a_little_addr_string(env
->from
, buftmp
, sizeof(buftmp
)-1);
1955 gf_puts(buftmp
, pc
);
1956 gf_puts(" wrote:", pc
);
1960 gf_puts(", it was written", pc
);
1962 gf_puts("It was written", pc
);
1968 * This is here for backwards compatibility. There didn't used
1969 * to be a _NEWLINE_ token. The user would enter text that should
1970 * all fit on one line and then that was followed by two newlines.
1971 * Also, truncation occurs if it is long.
1972 * Now, if _NEWLINE_ is not in the text, same thing still works
1973 * the same. However, if _NEWLINE_ is in there, then all bets are
1974 * off and the user is on his or her own. No automatic newlines
1975 * are added, only those that come from the tokens. No truncation
1976 * is done, the user is trusted to get it right. Newlines may be
1977 * embedded so that the leadin is multi-line.
1979 contains_newline_token
= (strstr(buf
, "_NEWLINE_") != NULL
);
1980 filtered
= detoken_src(buf
, FOR_REPLY_INTRO
, env
, role
,
1983 /* try to truncate if too long */
1984 if(!contains_newline_token
&& filtered
&& utf8_width(filtered
) > 80){
1985 int ended_with_colon
= 0;
1986 int ended_with_quote
= 0;
1987 int ended_with_quote_colon
= 0;
1990 l
= strlen(filtered
);
1992 if(filtered
[l
-1] == ':'){
1993 ended_with_colon
= ':';
1994 if(filtered
[l
-2] == QUOTE
|| filtered
[l
-2] == '\'')
1995 ended_with_quote_colon
= filtered
[l
-2];
1997 else if(filtered
[l
-1] == QUOTE
|| filtered
[l
-1] == '\'')
1998 ended_with_quote
= filtered
[l
-1];
2000 /* try to find space to break at */
2001 for(p
= &filtered
[75]; p
> &filtered
[60] &&
2002 !isspace((unsigned char)*p
); p
--)
2005 if(!isspace((unsigned char)*p
))
2011 if(ended_with_quote_colon
){
2012 *p
++ = ended_with_quote_colon
;
2015 else if(ended_with_colon
)
2016 *p
++ = ended_with_colon
;
2017 else if(ended_with_quote
)
2018 *p
++ = ended_with_quote
;
2023 if(filtered
&& *filtered
)
2024 gf_puts(filtered
, pc
);
2027 /* and end with two newlines unless no leadin at all */
2028 if(!contains_newline_token
){
2029 if(!strcmp(buf
, DEFAULT_REPLY_INTRO
) || (filtered
&& *filtered
)){
2030 gf_puts(NEWLINE
, pc
);
2031 gf_puts(NEWLINE
, pc
);
2036 fs_give((void **)&filtered
);
2041 free_redraft_pos(REDRAFT_POS_S
**redraft_pos
)
2043 if(redraft_pos
&& *redraft_pos
){
2044 if((*redraft_pos
)->hdrname
)
2045 fs_give((void **)&(*redraft_pos
)->hdrname
);
2047 fs_give((void **)redraft_pos
);
2052 /*----------------------------------------------------------------------
2053 Build the body for the message number/part being forwarded as ATTACHMENT
2057 Result: PARTS suitably attached to body
2059 ----------------------------------------------------------------------*/
2061 forward_mime_msg(MAILSTREAM
*stream
, long int msgno
, char *section
, ENVELOPE
*env
, struct mail_body_part
**partp
, void *msgtext
)
2067 *partp
= mail_newbody_part();
2068 b
= &(*partp
)->body
;
2069 b
->type
= TYPEMESSAGE
;
2070 b
->id
= generate_message_id();
2071 b
->description
= cpystr("Forwarded Message");
2072 b
->nested
.msg
= mail_newmsg();
2073 b
->disposition
.type
= cpystr("inline");
2075 /*---- Package each message in a storage object ----*/
2076 if((b
->contents
.text
.data
= (void *) so_get(PART_SO_TYPE
,NULL
,EDIT_ACCESS
))
2077 && (tmp_text
= mail_fetch_header(stream
,msgno
,section
,NIL
,NIL
,FT_PEEK
))
2079 so_puts((STORE_S
*) b
->contents
.text
.data
, tmp_text
);
2081 b
->size
.bytes
= strlen(tmp_text
);
2082 so_puts((STORE_S
*) b
->contents
.text
.data
, "\015\012");
2083 if((tmp_text
= pine_mail_fetch_text (stream
,msgno
,section
,&len
,NIL
)) != NULL
){
2084 so_nputs((STORE_S
*)b
->contents
.text
.data
,tmp_text
,(long) len
);
2085 b
->size
.bytes
+= len
;
2095 * forward_delimiter - return delimiter for forwarded text
2098 forward_delimiter(gf_io_t pc
)
2100 gf_puts(NEWLINE
, pc
);
2101 /* TRANSLATORS: When a message is forwarded by the user this is the
2102 text that shows where the forwarded part of the message begins. */
2103 gf_puts(_("---------- Forwarded message ----------"), pc
);
2104 gf_puts(NEWLINE
, pc
);
2108 /*----------------------------------------------------------------------
2109 Wrapper for header formatting tool
2117 Result: header suitable for reply/forward text written using "pc"
2119 ----------------------------------------------------------------------*/
2121 reply_forward_header(MAILSTREAM
*stream
, long int msgno
, char *part
, ENVELOPE
*env
,
2122 gf_io_t pc
, char *prefix
)
2126 char **list
, **new_list
= NULL
;
2128 list
= ps_global
->VAR_VIEW_HEADERS
;
2131 * If VIEW_HEADERS is set, we should remove BCC from the list so that
2132 * the user doesn't inadvertently forward the BCC header.
2134 if(list
&& list
[0]){
2141 p
= new_list
= (char **) fs_get((cnt
+1) * sizeof(char *));
2143 for(i
=0; list
[i
]; i
++)
2144 if(strucmp(list
[i
], "bcc"))
2145 *p
++ = cpystr(list
[i
]);
2149 if(new_list
&& new_list
[0])
2154 HD_INIT(&h
, list
, ps_global
->view_all_except
, FE_DEFAULT
& ~FE_BCC
);
2155 if((rv
= format_header(stream
, msgno
, part
, env
, &h
,
2156 prefix
, NULL
, FM_NOINDENT
, NULL
, pc
)) != 0){
2158 gf_puts(" [Error fetching message header data]", pc
);
2161 gf_puts(NEWLINE
, pc
); /* write header delimiter */
2164 free_list_array(&new_list
);
2168 /*----------------------------------------------------------------------
2169 Build the subject for the message number being forwarded
2171 Args: pine_state -- The usual pine structure
2172 msgno -- The message number to build subject for
2174 Result: malloc'd string containing new subject or NULL on error
2176 ----------------------------------------------------------------------*/
2178 forward_subject(ENVELOPE
*env
, int flags
)
2181 char *p
, buftmp
[MAILTMPLEN
];
2186 dprint((9, "checking subject: \"%s\"\n",
2187 env
->subject
? env
->subject
: "NULL"));
2189 if(env
->subject
&& env
->subject
[0]){ /* add (fwd)? */
2190 snprintf(buftmp
, sizeof(buftmp
), "%s", env
->subject
);
2191 buftmp
[sizeof(buftmp
)-1] = '\0';
2192 /* decode any 8bit (copy to the temp buffer if decoding doesn't) */
2193 if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf
,
2194 SIZEOF_20KBUF
, buftmp
) == (unsigned char *) buftmp
)
2195 strncpy(tmp_20k_buf
, buftmp
, SIZEOF_20KBUF
);
2197 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2199 removing_trailing_white_space(tmp_20k_buf
);
2200 if((l
= strlen(tmp_20k_buf
)) < 1000 &&
2201 (l
< 5 || strcmp(tmp_20k_buf
+l
-5,"(fwd)"))){
2202 snprintf(tmp_20k_buf
+2000, SIZEOF_20KBUF
-2000, "%s (fwd)", tmp_20k_buf
);
2203 tmp_20k_buf
[SIZEOF_20KBUF
-2000-1] = '\0';
2204 strncpy(tmp_20k_buf
, tmp_20k_buf
+2000, SIZEOF_20KBUF
);
2205 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2209 * HACK: composer can't handle embedded double quotes in attachment
2210 * comments so we substitute two single quotes.
2212 if(flags
& FS_CONVERT_QUOTES
)
2213 while((p
= strchr(tmp_20k_buf
, QUOTE
)) != NULL
)
2214 (void)rplstr(p
, SIZEOF_20KBUF
-(p
-tmp_20k_buf
), 1, "''");
2216 return(cpystr(tmp_20k_buf
));
2220 return(cpystr("Forwarded mail...."));
2224 /*----------------------------------------------------------------------
2225 Build the body for the message number/part being forwarded
2229 Result: BODY structure suitable for sending
2231 ----------------------------------------------------------------------*/
2233 forward_body(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
2234 long int msgno
, char *sect_prefix
, void *msgtext
, int flags
)
2236 BODY
*body
= NULL
, *text_body
, *tmp_body
;
2239 char *tmp_text
, *section
, sect_buf
[256];
2240 int forward_raw_body
= 0;
2243 * Check to see if messages got expunged out from underneath us. This
2244 * could have happened during the prompt to the user asking whether to
2245 * include the message as an attachment. Either the message is gone or
2246 * it might be at a different sequence number. We'd better bail.
2248 if(ps_global
->full_header
== 2
2249 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT
, ps_global
))
2250 forward_raw_body
= 1;
2251 if(sp_expunge_count(stream
))
2254 if(sect_prefix
&& forward_raw_body
== 0)
2255 snprintf(section
= sect_buf
, sizeof(sect_buf
), "%s.1", sect_prefix
);
2256 else if(sect_prefix
&& forward_raw_body
)
2257 section
= sect_prefix
;
2258 else if(!sect_prefix
&& forward_raw_body
)
2263 sect_buf
[sizeof(sect_buf
)-1] = '\0';
2265 gf_set_so_writec(&pc
, (STORE_S
*) msgtext
);
2266 if(!orig_body
|| orig_body
->type
== TYPETEXT
|| forward_raw_body
) {
2267 char *charset
= NULL
;
2269 /*---- Message has a single text part -----*/
2270 body
= mail_newbody();
2271 body
->type
= TYPETEXT
;
2272 body
->contents
.text
.data
= msgtext
;
2274 && (charset
= parameter_val(orig_body
->parameter
, "charset")))
2275 set_parameter(&body
->parameter
, "charset", charset
);
2278 fs_give((void **) &charset
);
2280 if(!(flags
& FWD_ANON
)){
2281 forward_delimiter(pc
);
2282 reply_forward_header(stream
, msgno
, sect_prefix
, env
, pc
, "");
2285 if(!get_body_part_text(stream
, forward_raw_body
? NULL
: orig_body
,
2286 msgno
, section
, 0L, pc
, NULL
, NULL
, GBPT_NONE
)){
2287 mail_free_body(&body
);
2291 else if(orig_body
->type
== TYPEMULTIPART
) {
2292 if(orig_body
->subtype
2293 && ((!strucmp(orig_body
->subtype
, "signed") && orig_body
->nested
.part
)
2295 || (!strucmp(orig_body
->subtype
, "mixed")
2296 && orig_body
->nested
.part
2297 && orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
2298 && orig_body
->nested
.part
->body
.subtype
2299 && (!strucmp(orig_body
->nested
.part
->body
.subtype
, OUR_PKCS7_ENCLOSURE_SUBTYPE
)
2300 || !strucmp(orig_body
->nested
.part
->body
.subtype
, "signed")))
2301 || !strucmp(orig_body
->subtype
, OUR_PKCS7_ENCLOSURE_SUBTYPE
)
2304 /* only operate on the signed data (not the signature) */
2305 body
= forward_body(stream
, env
, &orig_body
->nested
.part
->body
,
2307 orig_body
->nested
.part
->body
.type
== TYPEMULTIPART
2308 ? section
: sect_prefix
, msgtext
, flags
);
2310 /*---- Message is multipart ----*/
2311 else if(!(orig_body
->subtype
&& !strucmp(orig_body
->subtype
,
2313 && (body
= forward_multi_alt(stream
, env
, orig_body
, msgno
,
2314 sect_prefix
, msgtext
,
2316 /*--- Copy the body and entire structure ---*/
2317 body
= copy_body(NULL
, orig_body
);
2320 * whatever subtype it is, demote it
2321 * to plain old MIXED.
2324 fs_give((void **) &body
->subtype
);
2326 body
->subtype
= cpystr("Mixed");
2328 /*--- The text part of the message ---*/
2329 if(!body
->nested
.part
){
2330 q_status_message(SM_ORDER
| SM_DING
, 3, 6,
2331 "Error referencing body part 1");
2332 mail_free_body(&body
);
2334 else if(body
->nested
.part
->body
.type
== TYPETEXT
) {
2335 char *new_charset
= NULL
;
2337 /*--- The first part is text ----*/
2338 text_body
= &body
->nested
.part
->body
;
2339 text_body
->contents
.text
.data
= msgtext
;
2340 if(text_body
->subtype
&& strucmp(text_body
->subtype
, "Plain")){
2341 /* this text is going to the composer, it should be Plain */
2342 fs_give((void **)&text_body
->subtype
);
2343 text_body
->subtype
= cpystr("PLAIN");
2345 if(!(flags
& FWD_ANON
)){
2346 forward_delimiter(pc
);
2347 reply_forward_header(stream
, msgno
,
2348 sect_prefix
, env
, pc
, "");
2351 if(!(get_body_part_text(stream
, &orig_body
->nested
.part
->body
,
2352 msgno
, section
, 0L, pc
,
2353 NULL
, &new_charset
, GBPT_NONE
)
2354 && fetch_contents(stream
, msgno
, sect_prefix
, body
)))
2355 mail_free_body(&body
);
2356 else if(new_charset
)
2357 set_parameter(&text_body
->parameter
, "charset", new_charset
);
2359 /* BUG: ? matter that we're not setting body.size.bytes */
2361 else if(body
->nested
.part
->body
.type
== TYPEMULTIPART
2362 && body
->nested
.part
->body
.subtype
2363 && !strucmp(body
->nested
.part
->body
.subtype
, "alternative")
2364 && (tmp_body
= forward_multi_alt(stream
, env
,
2365 &body
->nested
.part
->body
,
2368 flags
| FWD_NESTED
))){
2369 /* for the forward_multi_alt call above, we want to pass
2370 * sect_prefix instead of section so we can obtain the header.
2371 * Set the FWD_NESTED flag so we fetch the right body_part.
2375 part
= body
->nested
.part
->next
;
2376 body
->nested
.part
->next
= NULL
;
2377 mail_free_body_part(&body
->nested
.part
);
2378 body
->nested
.part
= mail_newbody_part();
2379 body
->nested
.part
->body
= *tmp_body
;
2380 body
->nested
.part
->next
= part
;
2382 for(partnum
= 2; part
!= NULL
; part
= part
->next
){
2383 snprintf(sect_buf
, sizeof(sect_buf
), "%s%s%d",
2384 sect_prefix
? sect_prefix
: "",
2385 sect_prefix
? "." : "", partnum
++);
2386 sect_buf
[sizeof(sect_buf
)-1] = '\0';
2388 if(!fetch_contents(stream
, msgno
, sect_buf
, &part
->body
)){
2389 mail_free_body(&body
);
2395 if(fetch_contents(stream
, msgno
, sect_prefix
, body
)){
2396 /*--- Create a new blank text part ---*/
2397 part
= mail_newbody_part();
2398 part
->next
= body
->nested
.part
;
2399 body
->nested
.part
= part
;
2400 part
->body
.contents
.text
.data
= msgtext
;
2403 mail_free_body(&body
);
2408 /*---- A single part message, not of type text ----*/
2409 body
= mail_newbody();
2410 body
->type
= TYPEMULTIPART
;
2411 part
= mail_newbody_part();
2412 body
->nested
.part
= part
;
2414 /*--- The first part, a blank text part to be edited ---*/
2415 part
->body
.type
= TYPETEXT
;
2416 part
->body
.contents
.text
.data
= msgtext
;
2418 /*--- The second part, what ever it is ---*/
2419 part
->next
= mail_newbody_part();
2421 part
->body
.id
= generate_message_id();
2422 copy_body(&(part
->body
), orig_body
);
2425 * the idea here is to fetch part into storage object
2427 if((part
->body
.contents
.text
.data
= (void *) so_get(PART_SO_TYPE
, NULL
,
2428 EDIT_ACCESS
)) != NULL
){
2429 if((tmp_text
= pine_mail_fetch_body(stream
, msgno
, section
,
2430 &part
->body
.size
.bytes
, NIL
)) != NULL
)
2431 so_nputs((STORE_S
*)part
->body
.contents
.text
.data
, tmp_text
,
2432 part
->body
.size
.bytes
);
2434 mail_free_body(&body
);
2437 mail_free_body(&body
);
2440 gf_clear_so_writec((STORE_S
*) msgtext
);
2448 * bounce_msg_body - build body from specified message suitable
2449 * for sending as bounced message
2452 bounce_msg_body(MAILSTREAM
*stream
,
2457 ENVELOPE
**outgoingp
,
2461 char *h
, *p
, *errstr
= NULL
;
2466 *outgoingp
= mail_newenvelope();
2467 (*outgoingp
)->message_id
= generate_message_id();
2468 (*outgoingp
)->subject
= cpystr(subject
? subject
: "Resent mail....");
2471 * Fill in destination if we were given one. If so, note that we
2472 * call p_s_s() below such that it won't prompt...
2475 static char *fakedomain
= "@";
2478 /* rfc822_parse_adrlist feels free to destroy input so copy */
2479 tmp_a_string
= cpystr(*to
);
2480 rfc822_parse_adrlist(&(*outgoingp
)->to
, tmp_a_string
,
2481 (F_ON(F_COMPOSE_REJECTS_UNQUAL
, ps_global
))
2482 ? fakedomain
: ps_global
->maildomain
);
2483 fs_give((void **) &tmp_a_string
);
2486 /* build remail'd header */
2487 if((h
= mail_fetch_header(stream
, rawno
, part
, NULL
, 0, FT_PEEK
)) != NULL
){
2488 for(p
= h
, i
= 0; (p
= strchr(p
, ':')) != NULL
; p
++)
2492 (*outgoingp
)->remail
= (char *) fs_get(strlen(h
) + (2 * i
) + 1);
2495 * copy it, "X-"ing out transport headers bothersome to
2496 * software but potentially useful to the human recipient...
2498 p
= (*outgoingp
)->remail
;
2499 bounce_mask_header(&p
, h
);
2501 if(*h
== '\015' && *(h
+1) == '\012'){
2502 *p
++ = *h
++; /* copy CR LF */
2504 bounce_mask_header(&p
, h
);
2506 while((*p
++ = *h
++) != '\0');
2508 /* BUG: else complain? */
2510 /* NOT bound for the composer, so no need for PicoText */
2511 if(!(msgtext
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2512 mail_free_envelope(outgoingp
);
2513 return(_("Error allocating message text"));
2516 /* mark object for special handling */
2517 so_attr(msgtext
, "rawbody", "1");
2520 * Build a fake body description. It's ignored by pine_rfc822_header,
2521 * but we need to set it to something that makes set_mime_types
2522 * not sniff it and pine_rfc822_output_body not re-encode it.
2523 * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause
2524 * problems unless something tries to access body_encodings[] using
2525 * it without proper precautions. We don't want to use ENCOTHER
2526 * cause that tells set_mime_types to sniff it, and we don't want to
2527 * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode
2528 * it. When there's time, it'd be nice to clean this interaction
2531 *bodyp
= mail_newbody();
2532 (*bodyp
)->type
= TYPETEXT
;
2533 (*bodyp
)->encoding
= ENCMAX
+ 1;
2534 (*bodyp
)->subtype
= cpystr("Plain");
2535 (*bodyp
)->contents
.text
.data
= (void *) msgtext
;
2536 gf_set_so_writec(&pc
, msgtext
);
2538 if(seenp
&& rawno
> 0L && stream
&& rawno
<= stream
->nmsgs
){
2541 if((mc
= mail_elt(stream
, rawno
)) != NULL
)
2545 /* pass NULL body to force mail_fetchtext */
2546 if(!get_body_part_text(stream
, NULL
, rawno
, part
, 0L, pc
, NULL
, NULL
, GBPT_NONE
))
2547 errstr
= _("Error fetching message contents. Can't Bounce message");
2549 gf_clear_so_writec(msgtext
);
2556 /*----------------------------------------------------------------------
2557 Mask off any header entries we don't want xport software to see
2559 Args: d -- destination string pointer pointer
2560 s -- source string pointer pointer
2562 Postfix uses Delivered-To to detect loops.
2563 Received line counting is also used to detect loops in places.
2567 bounce_mask_header(char **d
, char *s
)
2569 if(((*s
== 'R' || *s
== 'r')
2570 && (!struncmp(s
+1, "esent-", 6) || !struncmp(s
+1, "eceived:", 8)
2571 || !struncmp(s
+1, "eturn-Path", 10)))
2572 || !struncmp(s
, "Delivered-To:", 13)){
2573 *(*d
)++ = 'X'; /* write mask */
2579 /*----------------------------------------------------------------------
2580 Fetch and format text for forwarding
2582 Args: stream -- Mail stream to fetch text from
2583 body -- Body structure of message being forwarded
2584 msg_no -- Message number of text for forward
2585 part_no -- Part number of text to forward
2586 partial -- If this is > 0 a partial fetch will be done and it will
2587 be done using FT_PEEK so the message will remain unseen.
2588 pc -- Function to write to
2589 prefix -- Prefix for each line
2590 ret_charset -- If we translate to another charset return that
2593 Returns: true if OK, false if problem occured while filtering
2595 If the text is richtext, it will be converted to plain text, since there's
2596 no rich text editing capabilities in Pine (yet).
2598 It's up to calling routines to plug in signature appropriately
2600 As with all internal text, NVT end-of-line conventions are observed.
2601 DOESN'T sanity check the prefix given!!!
2604 get_body_part_text(MAILSTREAM
*stream
, struct mail_bodystruct
*body
,
2605 long int msg_no
, char *part_no
, long partial
, gf_io_t pc
,
2606 char *prefix
, char **ret_charset
, unsigned flags
)
2608 int we_cancel
= 0, dashdata
, wrapflags
= GFW_FORCOMPOSE
, flow_res
= 0;
2609 FILTLIST_S filters
[12];
2611 char *err
, *charset
, *prefix_p
= NULL
;
2613 char *free_this
= NULL
;
2616 memset(filters
, 0, sizeof(filters
));
2618 *ret_charset
= NULL
;
2620 if(!pc_is_picotext(pc
))
2621 we_cancel
= busy_cue(NULL
, NULL
, 1);
2623 /* if null body, we must be talking to a non-IMAP2bis server.
2624 * No MIME parsing provided, so we just grab the message text...
2627 char *text
, *decode_error
;
2629 SourceType src
= CharStar
;
2632 (void) pine_mail_fetchstructure(stream
, msg_no
, NULL
);
2634 if((text
= pine_mail_fetch_text(stream
, msg_no
, part_no
, NULL
, 0)) != NULL
){
2635 gf_set_readc(&gc
, text
, (unsigned long)strlen(text
), src
, 0);
2637 gf_filter_init(); /* no filters needed */
2639 gf_link_filter(gf_prefix
, gf_prefix_opt(prefix
));
2640 if((decode_error
= gf_pipe(gc
, pc
)) != NULL
){
2641 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s%s [Formatting error: %s]%s",
2643 decode_error
, NEWLINE
);
2644 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2645 gf_puts(tmp_20k_buf
, pc
);
2650 gf_puts(NEWLINE
, pc
);
2651 gf_puts(_(" [ERROR fetching text of message]"), pc
);
2652 gf_puts(NEWLINE
, pc
);
2653 gf_puts(NEWLINE
, pc
);
2658 cancel_busy_cue(-1);
2663 charset
= parameter_val(body
->parameter
, "charset");
2665 if(charset
&& strucmp(charset
, "utf-8") && strucmp(charset
, "us-ascii")){
2667 *ret_charset
= "UTF-8";
2671 * just use detach, but add an auxiliary filter to insert prefix,
2672 * and, perhaps, digest richtext
2674 if(ps_global
->full_header
!= 2
2675 && !ps_global
->postpone_no_flow
2676 && (!body
->subtype
|| !strucmp(body
->subtype
, "plain"))){
2679 flow_res
= (F_OFF(F_QUELL_FLOWED_TEXT
, ps_global
)
2680 && F_OFF(F_STRIP_WS_BEFORE_SEND
, ps_global
)
2681 && (!prefix
|| (strucmp(prefix
,"> ") == 0)
2682 || strucmp(prefix
, ">") == 0));
2683 if((parmval
= parameter_val(body
->parameter
,
2684 "format")) != NULL
){
2685 if(!strucmp(parmval
, "flowed")){
2686 wrapflags
|= GFW_FLOWED
;
2688 fs_give((void **) &parmval
);
2689 if((parmval
= parameter_val(body
->parameter
, "delsp")) != NULL
){
2690 if(!strucmp(parmval
, "yes")){
2691 filters
[filtcnt
++].filter
= gf_preflow
;
2692 wrapflags
|= GFW_DELSP
;
2695 fs_give((void **) &parmval
);
2699 * if there's no prefix we're forwarding text
2700 * otherwise it's reply text. unless the user's
2701 * tied our hands, alter the prefix to continue flowed
2704 if(flow_res
&& ps_global
->reply
.use_flowed
)
2705 wrapflags
|= GFW_FLOW_RESULT
;
2707 filters
[filtcnt
].filter
= gf_wrap
;
2709 * The 80 will cause longer lines than what is likely
2710 * set by composer_fillcol, so we'll have longer
2711 * quoted and forwarded lines than the lines we type.
2712 * We're fine with that since the alternative is the
2713 * possible wrapping of lines we shouldn't have, which
2714 * is mitigated by this higher 80 limit.
2716 * If we were to go back to using composer_fillcol,
2717 * the correct value is composer_fillcol + 1, pine
2718 * is off-by-one from pico.
2720 filters
[filtcnt
++].data
= gf_wrap_filter_opt(
2721 MAX(MIN(ps_global
->ttyo
->screen_cols
2722 - (prefix
? strlen(prefix
) : 0),
2723 80 - (prefix
? strlen(prefix
) : 0)),
2724 30), /* doesn't have to be 30 */
2725 990, /* 998 is the SMTP limit */
2726 NULL
, 0, wrapflags
);
2731 * if not flowed, remove trailing whitespace to reduce
2732 * confusion, since we're sending out as flowed if we
2733 * can. At some future point, we might try
2734 * plugging in a user-option-controlled heuristic
2737 * We also want to fold "> " quotes so we get the
2738 * attributions correct.
2740 if(flow_res
&& ps_global
->reply
.use_flowed
&& prefix
&& !strucmp(prefix
, "> "))
2741 *(prefix_p
= prefix
+ 1) = '\0';
2742 ps_global
->reply
.use_flowed
= 1; /* reset for next call */
2743 if(!(wrapflags
& GFW_FLOWED
)
2745 filters
[filtcnt
].filter
= gf_line_test
;
2746 filters
[filtcnt
++].data
= gf_line_test_opt(twsp_strip
, NULL
);
2748 filters
[filtcnt
].filter
= gf_line_test
;
2749 filters
[filtcnt
++].data
= gf_line_test_opt(quote_fold
, NULL
);
2752 else if(body
->subtype
){
2755 if(strucmp(body
->subtype
,"richtext") == 0){
2756 filters
[filtcnt
].filter
= gf_rich2plain
;
2757 filters
[filtcnt
++].data
= gf_rich2plain_opt(&plain_opt
);
2759 else if(strucmp(body
->subtype
,"enriched") == 0){
2760 filters
[filtcnt
].filter
= gf_enriched2plain
;
2761 filters
[filtcnt
++].data
= gf_enriched2plain_opt(&plain_opt
);
2763 else if(strucmp(body
->subtype
,"html") == 0){
2764 if((flags
& GBPT_HTML_OK
) != GBPT_HTML_OK
){
2765 filters
[filtcnt
].filter
= gf_html2plain
;
2766 filters
[filtcnt
++].data
= gf_html2plain_opt(NULL
,
2767 ps_global
->ttyo
->screen_cols
,
2768 non_messageview_margin(),
2769 NULL
, NULL
, GFHP_STRIPPED
);
2775 if(ps_global
->reply
.strip_signature
){
2777 filters
[filtcnt
].filter
= gf_line_test
;
2778 filters
[filtcnt
++].data
= gf_line_test_opt(sigdash_strip
, &dashdata
);
2781 filters
[filtcnt
].filter
= gf_prefix
;
2782 filters
[filtcnt
++].data
= gf_prefix_opt(prefix
);
2784 if(wrapflags
& GFW_FLOWED
|| flow_res
){
2785 filters
[filtcnt
].filter
= gf_line_test
;
2786 filters
[filtcnt
++].data
= gf_line_test_opt(post_quote_space
, NULL
);
2790 if(flags
& GBPT_DELQUOTES
){
2791 memset(&dq
, 0, sizeof(dq
));
2792 dq
.lines
= Q_DEL_ALL
;
2794 dq
.indent_length
= 0;
2795 dq
.saved_line
= &free_this
;
2800 filters
[filtcnt
].filter
= gf_line_test
;
2801 filters
[filtcnt
++].data
= gf_line_test_opt(delete_quotes
, &dq
);
2804 err
= detach(stream
, msg_no
, part_no
, partial
, &len
, pc
,
2805 filters
[0].filter
? filters
: NULL
,
2806 ((flags
& GBPT_PEEK
) ? FT_PEEK
: 0)
2807 | ((flags
& GBPT_NOINTR
) ? DT_NOINTR
: 0));
2810 fs_give((void **) &free_this
);
2815 if (err
!= (char *) NULL
)
2816 /* TRANSLATORS: The first arg is error text, the %ld is the message number */
2817 q_status_message2(SM_ORDER
, 3, 4, "%s: message number %ld",
2818 err
, (void *) msg_no
);
2821 cancel_busy_cue(-1);
2828 quote_fold(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
2833 for(p
= line
; *p
; p
++){
2834 if(isspace((unsigned char) *p
)){
2836 ins
= gf_line_test_new_ins(ins
, p
, "", -1);
2848 twsp_strip(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
2850 char *p
, *ws
= NULL
;
2852 for(p
= line
; *p
; p
++){
2853 /* don't strip trailing space on signature line */
2854 if(*line
== '-' && *(line
+1) == '-' && *(line
+2) == ' ' && !*(line
+3))
2857 if(isspace((unsigned char) *p
)){
2866 ins
= gf_line_test_new_ins(ins
, ws
, "", -(p
- ws
));
2872 post_quote_space(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
2876 for(p
= line
; *p
; p
++)
2878 if(p
!= line
&& *p
!= ' ')
2879 ins
= gf_line_test_new_ins(ins
, p
, " ", 1);
2889 sigdash_strip(long int linenum
, char *line
, LT_INS_S
**ins
, void *local
)
2892 || (*line
== '-' && *(line
+1) == '-'
2893 && *(line
+2) == ' ' && !*(line
+3))){
2894 *((int *) local
) = 1;
2895 return(2); /* skip this line! */
2902 /*----------------------------------------------------------------------
2903 return the c-client reference name for the given end_body part
2906 body_partno(MAILSTREAM
*stream
, long int msgno
, struct mail_bodystruct
*end_body
)
2910 (void) pine_mail_fetchstructure(stream
, msgno
, &body
);
2911 return(partno(body
, end_body
));
2915 /*----------------------------------------------------------------------
2916 return the c-client reference name for the given end_body part
2919 partno(struct mail_bodystruct
*body
, struct mail_bodystruct
*end_body
)
2921 #define PARTTMPLEN 64
2924 char tmp
[PARTTMPLEN
], *p
= NULL
;
2926 if(body
&& body
->type
== TYPEMULTIPART
) {
2927 part
= body
->nested
.part
; /* first body part */
2929 do { /* for each part */
2930 num
++; /* PARTTMPLEN = sizeof(tmp) */
2931 if(&part
->body
== end_body
|| (p
= partno(&part
->body
, end_body
))){
2932 snprintf(tmp
, sizeof(tmp
), "%d%s%.*s", num
, (p
) ? "." : "",
2933 PARTTMPLEN
-10, (p
) ? p
: "");
2934 tmp
[sizeof(tmp
)-1] = '\0';
2936 fs_give((void **)&p
);
2938 return(cpystr(tmp
));
2940 } while ((part
= part
->next
) != NULL
); /* until done */
2944 else if(body
&& body
->type
== TYPEMESSAGE
&& body
->subtype
2945 && !strucmp(body
->subtype
, "rfc822")){
2946 return(partno(body
->nested
.msg
->body
, end_body
));
2949 return((body
== end_body
) ? cpystr("1") : NULL
);
2953 /*----------------------------------------------------------------------
2954 Fill in the contents of each body part
2956 Args: stream -- Stream the message is on
2957 msgno -- Message number the body structure is for
2958 section -- body section associated with body pointer
2959 body -- Body pointer to fill in
2961 Result: 1 if all went OK, 0 if there was a problem
2963 This function copies the contents from an original message/body to
2964 a new message/body. It recurses down all multipart levels.
2966 If one or more part (but not all) can't be fetched, a status message
2970 fetch_contents(MAILSTREAM
*stream
, long int msgno
, char *section
, struct mail_bodystruct
*body
)
2976 body
->id
= generate_message_id();
2978 if(body
->type
== TYPEMULTIPART
){
2979 char subsection
[256], *subp
;
2980 int n
, last_one
= 10; /* remember worst case */
2981 PART
*part
= body
->nested
.part
;
2983 if(!(part
= body
->nested
.part
))
2987 if(section
&& *section
){
2989 n
< sizeof(subsection
)-20 && (*subp
= section
[n
]); n
++, subp
++)
2997 snprintf(subp
, sizeof(subsection
)-(subp
-subsection
), "%d", n
++);
2998 subsection
[sizeof(subsection
)-1] = '\0';
2999 got_one
= fetch_contents(stream
, msgno
, subsection
, &part
->body
);
3000 last_one
= MIN(last_one
, got_one
);
3002 while((part
= part
->next
) != NULL
);
3007 if(body
->contents
.text
.data
)
3008 return(1); /* already taken care of... */
3010 if(body
->type
== TYPEMESSAGE
){
3011 if(body
->subtype
&& strucmp(body
->subtype
,"external-body")){
3013 * the idea here is to fetch everything into storage objects
3015 body
->contents
.text
.data
= (void *) so_get(PART_SO_TYPE
, NULL
,
3017 if(body
->contents
.text
.data
3018 && (tp
= pine_mail_fetch_body(stream
, msgno
, section
,
3019 &body
->size
.bytes
, NIL
))){
3020 so_truncate((STORE_S
*)body
->contents
.text
.data
,
3021 body
->size
.bytes
+ 2048);
3022 so_nputs((STORE_S
*)body
->contents
.text
.data
, tp
,
3027 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
3028 _("Error fetching part %s"), section
);
3034 * the idea here is to fetch everything into storage objects
3035 * so, grab one, then fetch the body part
3037 body
->contents
.text
.data
= (void *)so_get(PART_SO_TYPE
,NULL
,EDIT_ACCESS
);
3038 if(body
->contents
.text
.data
3039 && (tp
=pine_mail_fetch_body(stream
, msgno
, section
,
3040 &body
->size
.bytes
, NIL
))){
3041 so_truncate((STORE_S
*)body
->contents
.text
.data
,
3042 body
->size
.bytes
+ 2048);
3043 so_nputs((STORE_S
*)body
->contents
.text
.data
, tp
,
3048 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
3049 _("Error fetching part %s"), section
);
3056 /*----------------------------------------------------------------------
3057 Copy the body structure
3059 Args: new_body -- Pointer to already allocated body, or NULL, if none
3060 old_body -- The Body to copy
3063 This is traverses the body structure recursively copying all elements.
3064 The new_body parameter can be NULL in which case a new body is
3065 allocated. Alternatively it can point to an already allocated body
3066 structure. This is used when copying body parts since a PART includes a
3067 BODY. The contents fields are *not* filled in.
3071 copy_body(struct mail_bodystruct
*new_body
, struct mail_bodystruct
*old_body
)
3073 if(old_body
== NULL
)
3076 if(new_body
== NULL
)
3077 new_body
= mail_newbody();
3079 new_body
->type
= old_body
->type
;
3080 new_body
->encoding
= old_body
->encoding
;
3082 if(old_body
->subtype
)
3083 new_body
->subtype
= cpystr(old_body
->subtype
);
3085 new_body
->parameter
= copy_parameters(old_body
->parameter
);
3088 new_body
->id
= cpystr(old_body
->id
);
3090 if(old_body
->description
)
3091 new_body
->description
= cpystr(old_body
->description
);
3093 if(old_body
->disposition
.type
)
3094 new_body
->disposition
.type
= cpystr(old_body
->disposition
.type
);
3096 new_body
->disposition
.parameter
3097 = copy_parameters(old_body
->disposition
.parameter
);
3099 new_body
->size
= old_body
->size
;
3101 if(new_body
->type
== TYPEMESSAGE
3102 && new_body
->subtype
&& !strucmp(new_body
->subtype
, "rfc822")){
3103 new_body
->nested
.msg
= mail_newmsg();
3104 new_body
->nested
.msg
->body
3105 = copy_body(NULL
, old_body
->nested
.msg
->body
);
3107 else if(new_body
->type
== TYPEMULTIPART
) {
3108 PART
**new_partp
, *old_part
;
3110 new_partp
= &new_body
->nested
.part
;
3111 for(old_part
= old_body
->nested
.part
;
3113 old_part
= old_part
->next
){
3114 *new_partp
= mail_newbody_part();
3115 copy_body(&(*new_partp
)->body
, &old_part
->body
);
3116 new_partp
= &(*new_partp
)->next
;
3124 /*----------------------------------------------------------------------
3125 Copy the MIME parameter list
3127 Allocates storage for new part, and returns pointer to new paramter
3128 list. If old_p is NULL, NULL is returned.
3131 copy_parameters(PARAMETER
*old_p
)
3133 PARAMETER
*new_p
, *p1
, *p2
;
3136 return((PARAMETER
*)NULL
);
3139 for(p1
= old_p
; p1
!= NULL
; p1
= p1
->next
){
3140 set_parameter(&p2
, p1
->attribute
, p1
->value
);
3149 /*----------------------------------------------------------------------
3150 Make a complete copy of an envelope and all it's fields
3152 Args: e -- the envelope to copy
3154 Result: returns the new envelope, or NULL, if the given envelope was NULL
3159 copy_envelope(register ENVELOPE
*e
)
3161 register ENVELOPE
*e2
;
3166 e2
= mail_newenvelope();
3167 e2
->remail
= e
->remail
? cpystr(e
->remail
) : NULL
;
3168 e2
->return_path
= e
->return_path
? rfc822_cpy_adr(e
->return_path
) : NULL
;
3169 e2
->date
= e
->date
? (unsigned char *)cpystr((char *) e
->date
)
3171 e2
->from
= e
->from
? rfc822_cpy_adr(e
->from
) : NULL
;
3172 e2
->sender
= e
->sender
? rfc822_cpy_adr(e
->sender
) : NULL
;
3173 e2
->reply_to
= e
->reply_to
? rfc822_cpy_adr(e
->reply_to
) : NULL
;
3174 e2
->subject
= e
->subject
? cpystr(e
->subject
) : NULL
;
3175 e2
->to
= e
->to
? rfc822_cpy_adr(e
->to
) : NULL
;
3176 e2
->cc
= e
->cc
? rfc822_cpy_adr(e
->cc
) : NULL
;
3177 e2
->bcc
= e
->bcc
? rfc822_cpy_adr(e
->bcc
) : NULL
;
3178 e2
->in_reply_to
= e
->in_reply_to
? cpystr(e
->in_reply_to
) : NULL
;
3179 e2
->newsgroups
= e
->newsgroups
? cpystr(e
->newsgroups
) : NULL
;
3180 e2
->message_id
= e
->message_id
? cpystr(e
->message_id
) : NULL
;
3181 e2
->references
= e
->references
? cpystr(e
->references
) : NULL
;
3182 e2
->followup_to
= e
->followup_to
? cpystr(e
->references
) : NULL
;
3187 /*----------------------------------------------------------------------
3188 Generate the "In-reply-to" text from message header
3190 Args: message -- Envelope of original message
3192 Result: returns an alloc'd string or NULL if there is a problem
3195 reply_in_reply_to(ENVELOPE
*env
)
3197 return((env
&& env
->message_id
) ? cpystr(env
->message_id
) : NULL
);
3201 /*----------------------------------------------------------------------
3202 Generate a unique message id string.
3204 Args: ps -- The usual pine structure
3206 Result: Alloc'd unique string is returned
3208 Uniqueness is gaurenteed by using the host name, process id, date to the
3209 second and a single unique character
3210 *----------------------------------------------------------------------*/
3212 generate_message_id(void)
3214 static short osec
= 0, cnt
= 0;
3219 char *hostpart
= NULL
;
3220 char *alpine_name
= NULL
;
3221 char *alpine_version
= NULL
;
3222 char *system_os
= NULL
;
3224 now
= time((time_t *)0);
3225 now_x
= localtime(&now
);
3227 if(now_x
->tm_sec
== osec
)
3231 osec
= now_x
->tm_sec
;
3234 if(F_ON(F_ROT13_MESSAGE_ID
, ps_global
)){
3235 hostpart
= rot13(ps_global
->hostname
);
3236 alpine_name
= rot13("alpine");
3237 alpine_version
= rot5n(ALPINE_VERSION
);
3238 system_os
= rot13(SYSTYPE
);
3240 hostpart
= cpystr(ps_global
->hostname
);
3241 alpine_name
= cpystr("alpine");
3242 alpine_version
= cpystr(ALPINE_VERSION
);
3243 system_os
= cpystr(SYSTYPE
);
3247 hostpart
= cpystr("huh");
3249 snprintf(idbuf
, sizeof(idbuf
), "<%.6s.%.4s.%.20s.%02d%02d%02d%02d%02d%02d%X.%d@%.50s>",
3250 alpine_name
, system_os
, alpine_version
, (now_x
->tm_year
) % 100, now_x
->tm_mon
+ 1,
3251 now_x
->tm_mday
, now_x
->tm_hour
, now_x
->tm_min
, now_x
->tm_sec
,
3252 cnt
, getpid(), hostpart
);
3253 idbuf
[sizeof(idbuf
)-1] = '\0';
3257 if(hostpart
) fs_give((void **) &hostpart
);
3258 if(alpine_name
) fs_give((void **) & alpine_name
);
3259 if(alpine_version
) fs_give((void **)&alpine_version
);
3260 if(system_os
) fs_give((void **)&system_os
);
3267 generate_user_agent(void)
3272 if(F_ON(F_QUELL_USERAGENT
, ps_global
))
3275 snprintf(buf
, sizeof(buf
),
3276 "%sAlpine %s (%s %s)",
3277 (pith_opt_user_agent_prefix
) ? (*pith_opt_user_agent_prefix
)() : "",
3278 ALPINE_VERSION
, SYSTYPE
,
3279 get_alpine_revision_string(rev
, sizeof(rev
)));
3281 return(cpystr(buf
));
3288 char byte
, cap
, *p
, *ret
= NULL
;
3291 ret
= (char *) fs_get((strlen(src
)+1) * sizeof(char));
3293 while((byte
= *src
++) != '\0'){
3296 *p
++ = ((byte
>= 'A') && (byte
<= 'Z')
3297 ? ((byte
- 'A' + 13) % 26 + 'A') : byte
) | cap
;
3309 char byte
, *p
, *ret
= NULL
;
3312 ret
= (char *) fs_get((strlen(src
)+1) * sizeof(char));
3314 while((byte
= *src
++) != '\0')
3315 *p
++ = ((byte
>= '0') && (byte
<= '9')
3316 ? ((byte
- '0' + 5) % 10 + '0') : byte
);
3324 /*----------------------------------------------------------------------
3325 Return the first true address pointer (modulo group syntax allowance)
3327 Args: addr -- Address list
3329 Result: First real address pointer, or NULL
3330 ----------------------------------------------------------------------*/
3332 first_addr(struct mail_address
*addr
)
3334 while(addr
&& !addr
->host
)
3341 /*----------------------------------------------------------------------
3342 lit -- this is the source
3343 prenewlines -- prefix the file contents with this many newlines
3344 postnewlines -- postfix the file contents with this many newlines
3345 is_sig -- this is a signature (not a template)
3346 decode_constants -- change C-style constants into their values
3349 get_signature_lit(char *lit
, int prenewlines
, int postnewlines
, int is_sig
, int decode_constants
)
3354 * Should make this smart enough not to do the copying and double
3355 * allocation of space.
3358 char *tmplit
= NULL
, *p
, *q
, *d
, save
;
3361 if(decode_constants
){
3362 tmplit
= (char *) fs_get((strlen(lit
)+1) * sizeof(char));
3364 cstring_to_string(lit
, tmplit
);
3367 tmplit
= cpystr(lit
);
3369 len
= strlen(tmplit
) + 5 + (prenewlines
+postnewlines
) * strlen(NEWLINE
);
3370 sig
= (char *) fs_get((len
+1) * sizeof(char));
3371 memset(sig
, 0, len
+1);
3373 while(prenewlines
--)
3374 sstrncpy(&d
, NEWLINE
, len
-(d
-sig
));
3376 if(is_sig
&& F_ON(F_ENABLE_SIGDASHES
, ps_global
) &&
3377 !sigdashes_are_present(tmplit
)){
3378 sstrncpy(&d
, SIGDASHES
, len
-(d
-sig
));
3379 sstrncpy(&d
, NEWLINE
, len
-(d
-sig
));
3387 q
= strpbrk(p
, "\n\r");
3394 * Strip trailing space if we are doing a signature and
3395 * this line is not sigdashes.
3397 if(is_sig
&& strcmp(p
, SIGDASHES
))
3398 removing_trailing_white_space(p
);
3400 while((d
-sig
) <= len
&& (*d
= *p
++) != '\0')
3413 while(postnewlines
--)
3414 sstrncpy(&d
, NEWLINE
, len
-(d
-sig
));
3422 fs_give((void **) &tmplit
);
3430 sigdashes_are_present(char *sig
)
3434 p
= srchstr(sig
, SIGDASHES
);
3435 while(p
&& !((p
== sig
|| (p
[-1] == '\n' || p
[-1] == '\r')) &&
3436 (p
[3] == '\0' || p
[3] == '\n' || p
[3] == '\r')))
3437 p
= srchstr(p
+1, SIGDASHES
);
3443 /*----------------------------------------------------------------------
3444 Acquire the pinerc defined signature file pathname
3448 signature_path(char *sname
, char *sbuf
, size_t len
)
3451 if(sname
&& *sname
){
3452 size_t spl
= strlen(sname
);
3453 if(IS_REMOTE(sname
)){
3455 strncpy(sbuf
, sname
, len
-1);
3457 else if(is_absolute_path(sname
)){
3458 strncpy(sbuf
, sname
, len
-1);
3460 fnexpand(sbuf
, len
);
3462 else if(ps_global
->VAR_OPER_DIR
){
3463 if(strlen(ps_global
->VAR_OPER_DIR
) + spl
< len
- 1)
3464 build_path(sbuf
, ps_global
->VAR_OPER_DIR
, sname
, len
);
3467 char *lc
= last_cmpnt(ps_global
->pinerc
);
3471 strncpy(sbuf
,ps_global
->pinerc
,MIN(len
-1,lc
-ps_global
->pinerc
));
3472 sbuf
[MIN(len
-1,lc
-ps_global
->pinerc
)] = '\0';
3475 strncat(sbuf
, sname
, MAX(len
-1-strlen(sbuf
), 0));
3480 return(*sbuf
? sbuf
: NULL
);
3485 simple_read_remote_file(char *name
, char *subtype
)
3492 dprint((7, "simple_read_remote_file(%s, %s)\n", name
? name
: "?", subtype
? subtype
: "?"));
3495 * We could parse the name here to find what type it is. So far we
3496 * only have type RemImap.
3498 rd
= rd_create_remote(RemImap
, name
, subtype
,
3499 NULL
, _("Error: "), _("Can't fetch remote configuration."));
3503 try_cache
= rd_read_metadata(rd
);
3505 if(rd
->access
== MaybeRorW
){
3506 if(rd
->read_status
== 'R')
3507 rd
->access
= ReadOnly
;
3509 rd
->access
= ReadWrite
;
3512 if(rd
->access
!= NoExists
){
3514 rd_check_remvalid(rd
, 1L);
3517 * If the cached info says it is readonly but
3518 * it looks like it's been fixed now, change it to readwrite.
3520 if(rd
->read_status
== 'R'){
3522 * We go to this trouble since readonly sigfiles
3523 * are likely a mistake. They are usually supposed to be
3524 * readwrite so we open it and check if it's been fixed.
3526 rd_check_readonly_access(rd
);
3527 if(rd
->read_status
== 'W'){
3528 rd
->access
= ReadWrite
;
3529 rd
->flags
|= REM_OUTOFDATE
;
3532 rd
->access
= ReadOnly
;
3535 if(rd
->flags
& REM_OUTOFDATE
){
3536 if(rd_update_local(rd
) != 0){
3539 "simple_read_remote_file: rd_update_local failed\n"));
3541 * Don't give up altogether. We still may be
3542 * able to use a cached copy.
3547 "%s: copied remote to local (%ld)\n",
3548 rd
->rn
? rd
->rn
: "?", (long)rd
->last_use
));
3552 if(rd
->access
== ReadWrite
)
3553 rd
->flags
|= DO_REMTRIM
;
3556 /* If we couldn't get to remote folder, try using the cached copy */
3557 if(rd
->access
== NoExists
|| rd
->flags
& REM_OUTOFDATE
){
3559 rd
->access
= ReadOnly
;
3560 rd
->flags
|= USE_OLD_CACHE
;
3561 q_status_message(SM_ORDER
, 3, 4,
3562 "Can't contact remote server, using cached copy");
3564 "Can't open remote file %s, using local cached copy %s readonly\n",
3565 rd
->rn
? rd
->rn
: "?",
3566 rd
->lf
? rd
->lf
: "?"));
3569 rd
->flags
&= ~DO_REMTRIM
;
3574 file
= read_file(rd
->lf
, READ_FROM_LOCALE
);
3578 rd_close_remdata(&rd
);
3584 /*----------------------------------------------------------------------
3585 Build the body for the multipart/alternative part
3591 ----------------------------------------------------------------------*/
3593 forward_multi_alt(MAILSTREAM
*stream
, ENVELOPE
*env
, struct mail_bodystruct
*orig_body
,
3594 long int msgno
, char *sect_prefix
, void *msgtext
, gf_io_t pc
, int flags
)
3596 #define FWDTMPLEN 256
3598 PART
*part
= NULL
, *bestpart
= NULL
;
3599 char tmp_buf
[FWDTMPLEN
];
3600 char *new_charset
= NULL
;
3601 int partnum
, bestpartnum
;
3603 if(ps_global
->force_prefer_plain
3604 || (!ps_global
->force_no_prefer_plain
3605 && F_ON(F_PREFER_PLAIN_TEXT
, ps_global
))){
3606 for(part
= orig_body
->nested
.part
, partnum
= 1;
3608 part
= part
->next
, partnum
++)
3609 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
3610 && (!part
->body
.subtype
3611 || !strucmp(part
->body
.subtype
, "plain")))
3616 * Else choose last alternative among plain or html parts.
3617 * Perhaps we should really be using mime_show() to make this
3618 * potentially more general than just plain or html.
3621 for(part
= orig_body
->nested
.part
, partnum
= 1;
3623 part
= part
->next
, partnum
++){
3624 if((!part
->body
.type
|| part
->body
.type
== TYPETEXT
)
3625 && ((!part
->body
.subtype
|| !strucmp(part
->body
.subtype
, "plain"))
3627 (part
->body
.subtype
&& !strucmp(part
->body
.subtype
, "html")))){
3629 bestpartnum
= partnum
;
3634 partnum
= bestpartnum
;
3638 * IF something's interesting insert it
3639 * AND forget the rest of the multipart
3642 body
= mail_newbody();
3643 body
->type
= TYPETEXT
;
3644 body
->contents
.text
.data
= msgtext
;
3646 /* record character set, flowing, etc */
3647 body
->parameter
= copy_parameters(part
->body
.parameter
);
3648 body
->size
.bytes
= part
->body
.size
.bytes
;
3650 if(!(flags
& FWD_ANON
)){
3651 forward_delimiter(pc
);
3652 reply_forward_header(stream
, msgno
, sect_prefix
, env
, pc
, "");
3654 /* FWDTMPLEN = sizeof(tmp_buf) */
3655 snprintf(tmp_buf
, sizeof(tmp_buf
), "%.*s%s%s%d",
3656 FWDTMPLEN
/2, sect_prefix
? sect_prefix
: "",
3657 sect_prefix
? "." : "", flags
& FWD_NESTED
? "1." : "",
3659 tmp_buf
[sizeof(tmp_buf
)-1] = '\0';
3660 get_body_part_text(stream
, &part
->body
, msgno
, tmp_buf
, 0L, pc
,
3661 NULL
, &new_charset
, GBPT_NONE
);
3664 * get_body_part_text translated the data to a new charset.
3665 * We need to record that fact in body.
3668 set_parameter(&body
->parameter
, "charset", new_charset
);
3671 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3672 "No suitable part found. Forwarding as attachment");
3679 reply_append_addr(struct mail_address
**dest
, struct mail_address
*src
)
3681 for( ; *dest
; dest
= &(*dest
)->next
)