Add support for tab-completion when selecting by rule
[alpine.git] / alpine / reply.c
blob6ce5b8e6c3d3d6f434fda081689c005f7023e658
1 /*
2 * ========================================================================
3 * Copyright 2006-2008 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
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 /*======================================================================
16 Code here for forward and reply to mail
17 A few support routines as well
19 This code will forward and reply to MIME messages. The Alpine composer
20 at this time will only support non-text segments at the end of a
21 message so, things don't always come out as one would like. If you
22 always forward a message in MIME format, all will be correct. Forwarding
23 of nested MULTIPART messages will work. There's still a problem with
24 MULTIPART/ALTERNATIVE as the "first text part" rule doesn't allow modifying
25 the equivalent parts. Ideally, we should probably such segments as a
26 single attachment when forwarding/replying. It would also be real nice to
27 flatten out the nesting in the composer so pieces inside can get snipped.
29 The evolution continues...
30 =====*/
33 #include "headers.h"
34 #include "reply.h"
35 #include "status.h"
36 #include "radio.h"
37 #include "send.h"
38 #include "titlebar.h"
39 #include "mailindx.h"
40 #include "help.h"
41 #include "signal.h"
42 #include "mailcmd.h"
43 #include "alpine.h"
44 #include "roleconf.h"
45 #include "../pith/state.h"
46 #include "../pith/conf.h"
47 #include "../pith/init.h"
48 #include "../pith/filter.h"
49 #include "../pith/pattern.h"
50 #include "../pith/charset.h"
51 #include "../pith/mimedesc.h"
52 #include "../pith/remote.h"
53 #include "../pith/news.h"
54 #include "../pith/util.h"
55 #include "../pith/detoken.h"
56 #include "../pith/newmail.h"
57 #include "../pith/readfile.h"
58 #include "../pith/tempfile.h"
59 #include "../pith/busy.h"
60 #include "../pith/ablookup.h"
64 * Internal Prototypes
66 int reply_poster_followup(ENVELOPE *);
67 int sigedit_exit_for_pico(struct headerentry *, void (*)(void), int, char **);
68 long new_mail_for_pico(int, int);
69 void cmd_input_for_pico(void);
70 int display_message_for_pico(int);
71 char *checkpoint_dir_for_pico(char *, size_t);
72 void resize_for_pico(void);
73 PCOLORS *colors_for_pico(void);
74 void free_pcolors(PCOLORS **);
77 /*----------------------------------------------------------------------
78 Fill in an outgoing message for reply and pass off to send
80 Args: pine_state -- The usual pine structure
82 Result: Reply is formatted and passed off to composer/mailer
84 Reply
86 - put senders address in To field
87 - search to and cc fields to see if we aren't the only recipients
88 - if other than us, ask if we should reply to all.
89 - if answer yes, fill out the To and Cc fields
90 - fill in the fcc argument
91 - fill in the subject field
92 - fill out the body and the attachments
93 - pass off to pine_send()
94 ---*/
95 int
96 reply(struct pine *pine_state, ACTION_S *role_arg)
98 ADDRESS *saved_from, *saved_to, *saved_cc, *saved_resent;
99 ADDRESS *us_in_to_and_cc, *ap;
100 ENVELOPE *env = NULL, *outgoing;
101 BODY *body, *orig_body = NULL;
102 REPLY_S reply;
103 void *msgtext = NULL;
104 char *tmpfix = NULL, *prefix = NULL, *fcc = NULL, *errmsg = NULL;
105 long msgno, j, totalm, rflags, *seq = NULL;
106 int i, include_text = 0, times = -1, warned = 0, rv = 0,
107 flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0;
108 int rolemsg = 0, copytomsg = 0;
109 gf_io_t pc;
110 PAT_STATE dummy;
111 REDRAFT_POS_S *redraft_pos = NULL;
112 ACTION_S *role = NULL, *nrole;
113 #if defined(DOS) && !defined(_WINDOWS)
114 char *reserve;
115 #endif
117 outgoing = mail_newenvelope();
118 totalm = mn_total_cur(pine_state->msgmap);
119 seq = (long *)fs_get(((size_t)totalm + 1) * sizeof(long));
121 dprint((4,"\n - reply (%s msgs) -\n", comatose(totalm)));
123 saved_from = (ADDRESS *) NULL;
124 saved_to = (ADDRESS *) NULL;
125 saved_cc = (ADDRESS *) NULL;
126 saved_resent = (ADDRESS *) NULL;
128 us_in_to_and_cc = (ADDRESS *) NULL;
130 outgoing->subject = NULL;
132 memset((void *)&reply, 0, sizeof(reply));
134 if(ps_global->full_header == 2
135 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
136 reply_raw_body = 1;
139 * We may have to loop through first to figure out what default
140 * reply-indent-string to offer...
142 if(mn_total_cur(pine_state->msgmap) > 1 &&
143 (F_ON(F_ALT_REPLY_MENU, pine_state)
144 || F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state)) &&
145 reply_quote_str_contains_tokens()){
146 for(msgno = mn_first_cur(pine_state->msgmap);
147 msgno > 0L && !tmpfix;
148 msgno = mn_next_cur(pine_state->msgmap)){
150 env = pine_mail_fetchstructure(pine_state->mail_stream,
151 mn_m2raw(pine_state->msgmap, msgno),
152 NULL);
153 if(!env) {
154 q_status_message1(SM_ORDER,3,4,
155 _("Error fetching message %s. Can't reply to it."),
156 long2string(msgno));
157 goto done_early;
160 if(!tmpfix){ /* look for prefix? */
161 tmpfix = reply_quote_str(env);
162 if(prefix){
163 i = strcmp(tmpfix, prefix);
164 fs_give((void **) &tmpfix);
165 if(i){ /* don't check back if dissimilar */
166 fs_give((void **) &prefix);
168 * We free prefix, not tmpfix. We set tmpfix to prefix
169 * so that we won't come check again.
171 tmpfix = prefix = cpystr("> ");
174 else{
175 prefix = tmpfix;
176 tmpfix = NULL; /* check back later? */
181 tmpfix = prefix;
185 * Loop thru the selected messages building the
186 * outgoing envelope's destinations...
188 for(msgno = mn_first_cur(pine_state->msgmap);
189 msgno > 0L;
190 msgno = mn_next_cur(pine_state->msgmap)){
192 /*--- Grab current envelope ---*/
193 env = pine_mail_fetchstructure(pine_state->mail_stream,
194 seq[++times] = mn_m2raw(pine_state->msgmap, msgno),
195 NULL);
196 if(!env) {
197 q_status_message1(SM_ORDER,3,4,
198 _("Error fetching message %s. Can't reply to it."),
199 long2string(msgno));
200 goto done_early;
204 * We check for the prefix here if we didn't do it in the first
205 * loop above. This is just to save having to go through the loop
206 * twice in the cases where we don't need to.
208 if(!tmpfix){
209 tmpfix = reply_quote_str(env);
210 if(prefix){
211 i = strcmp(tmpfix, prefix);
212 fs_give((void **) &tmpfix);
213 if(i){ /* don't check back if dissimilar */
214 fs_give((void **) &prefix);
215 tmpfix = prefix = cpystr("> ");
218 else{
219 prefix = tmpfix;
220 tmpfix = NULL; /* check back later? */
225 * For consistency, the first question is always "include text?"
227 if(!times){ /* only first time */
228 char *p = cpystr(prefix);
230 if((include_text=reply_text_query(pine_state,totalm,env,&prefix)) < 0)
231 goto done_early;
233 /* edited prefix? */
234 if(strcmp(p, prefix))
235 tmpfix = prefix; /* stop looking */
237 fs_give((void **)&p);
241 * If we're agg-replying or there's a newsgroup and the user wants
242 * to post to news *and* via email, add relevant addresses to the
243 * outgoing envelope...
245 * The single message case gets us around the aggregate reply
246 * to messages in a mixed mail-news archive where some might
247 * have newsgroups and others not or whatever.
249 if(totalm > 1L || ((i = reply_news_test(env, outgoing)) & 1)){
250 if(totalm > 1)
251 flags |= RSF_FORCE_REPLY_TO;
253 if(!reply_harvest(pine_state, seq[times], NULL, env,
254 &saved_from, &saved_to, &saved_cc,
255 &saved_resent, &flags))
256 goto done_early;
258 else if(i == 0)
259 goto done_early;
261 /* collect a list of addresses that are us in to and cc fields */
262 if(env->to)
263 if((ap=reply_cp_addr(pine_state, 0L, NULL,
264 NULL, us_in_to_and_cc, NULL,
265 env->to, RCA_ONLY_US)) != NULL)
266 reply_append_addr(&us_in_to_and_cc, ap);
268 if(env->cc)
269 if((ap=reply_cp_addr(pine_state, 0L, NULL,
270 NULL, us_in_to_and_cc, NULL,
271 env->cc, RCA_ONLY_US)) != NULL)
272 reply_append_addr(&us_in_to_and_cc, ap);
274 /*------------ Format the subject line ---------------*/
275 if(outgoing->subject){
277 * if reply to more than one message, and all subjects
278 * match, so be it. otherwise set it to something generic...
280 if(!same_subject(outgoing->subject,
281 reply_subject(env->subject,tmp_20k_buf,SIZEOF_20KBUF))){
282 fs_give((void **)&outgoing->subject);
283 outgoing->subject = cpystr("Re: several messages");
286 else
287 outgoing->subject = reply_subject(env->subject, NULL, 0);
290 /* fill reply header */
291 reply_seed(pine_state, outgoing, env, saved_from,
292 saved_to, saved_cc, saved_resent,
293 &fcc, flags & RSF_FORCE_REPLY_ALL, &errmsg);
294 if(errmsg){
295 if(*errmsg){
296 q_status_message1(SM_ORDER, 3, 3, "%.200s", errmsg);
297 display_message(NO_OP_COMMAND);
300 fs_give((void **)&errmsg);
303 if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */
304 goto done_early;
306 /* Setup possible role */
307 if (ps_global->reply.role_chosen)
308 role = ps_global->reply.role_chosen;
309 else if(role_arg)
310 role = copy_action(role_arg);
312 if(!role){
313 rflags = ROLE_REPLY;
314 if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){
315 /* setup default role */
316 nrole = NULL;
317 j = mn_first_cur(pine_state->msgmap);
318 do {
319 role = nrole;
320 nrole = set_role_from_msg(pine_state, rflags,
321 mn_m2raw(pine_state->msgmap, j),
322 NULL);
323 } while(nrole && (!role || nrole == role)
324 && (j=mn_next_cur(pine_state->msgmap)) > 0L);
326 if(!role || nrole == role)
327 role = nrole;
328 else
329 role = NULL;
331 if(confirm_role(rflags, &role))
332 role = combine_inherited_role(role);
333 else{ /* cancel reply */
334 role = NULL;
335 cmd_cancelled("Reply");
336 goto done_early;
342 * Reply_seed may call c-client in get_fcc_based_on_to, so env may
343 * no longer be valid. Get it again.
344 * Similarly for set_role_from_message.
346 env = pine_mail_fetchstructure(pine_state->mail_stream, seq[times], NULL);
348 if(role){
349 rolemsg++;
350 /* override fcc gotten in reply_seed */
351 if(role->fcc && fcc)
352 fs_give((void **) &fcc);
355 if(F_ON(F_COPY_TO_TO_FROM, pine_state) && !(role && role->from)){
357 * A list of all of our addresses that appear in the To
358 * and cc fields is in us_in_to_and_cc.
359 * If there is exactly one address in that list then
360 * use it for the outgoing From.
362 if(us_in_to_and_cc && !us_in_to_and_cc->next){
363 PINEFIELD *custom, *pf;
364 ADDRESS *a = NULL;
365 char *addr = NULL;
368 * Check to see if this address is different from what
369 * we would have used anyway. If it is, notify the user
370 * with a status message. This is pretty hokey because we're
371 * mimicking how pine_send would set the From address and
372 * there is no coordination between the two.
375 /* in case user has a custom From value */
376 custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
378 pf = (PINEFIELD *) fs_get(sizeof(*pf));
379 memset((void *) pf, 0, sizeof(*pf));
380 pf->name = cpystr("From");
381 pf->addr = &a;
382 if(set_default_hdrval(pf, custom) >= UseAsDef
383 && pf->textbuf && pf->textbuf[0]){
384 removing_trailing_white_space(pf->textbuf);
385 (void)removing_double_quotes(pf->textbuf);
386 build_address(pf->textbuf, &addr, NULL, NULL, NULL);
387 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
388 if(addr)
389 fs_give((void **) &addr);
392 if(!*pf->addr)
393 *pf->addr = generate_from();
395 if(*pf->addr && !address_is_same(*pf->addr, us_in_to_and_cc)){
396 copytomsg++;
397 if(!role){
398 role = (ACTION_S *) fs_get(sizeof(*role));
399 memset((void *) role, 0, sizeof(*role));
400 role->is_a_role = 1;
403 role->from = us_in_to_and_cc;
404 us_in_to_and_cc = NULL;
407 free_customs(custom);
408 free_customs(pf);
412 if(role){
413 if(rolemsg && copytomsg)
414 q_status_message1(SM_ORDER, 3, 4,
415 _("Replying using role \"%s\" and To as From"), role->nick);
416 else if(rolemsg)
417 q_status_message1(SM_ORDER, 3, 4,
418 _("Replying using role \"%s\""), role->nick);
419 else if(copytomsg)
420 q_status_message(SM_ORDER, 3, 4,
421 _("Replying using incoming To as outgoing From"));
424 if(us_in_to_and_cc)
425 mail_free_address(&us_in_to_and_cc);
428 seq[++times] = -1L; /* mark end of sequence list */
430 /*========== Other miscellaneous fields ===================*/
431 outgoing->in_reply_to = reply_in_reply_to(env);
432 outgoing->references = reply_build_refs(env);
433 outgoing->message_id = generate_message_id(role);
435 if(!outgoing->to &&
436 !outgoing->cc &&
437 !outgoing->bcc &&
438 !outgoing->newsgroups)
439 q_status_message(SM_ORDER | SM_DING, 3, 6,
440 _("Warning: no valid addresses to reply to!"));
443 /*==================== Now fix up the message body ====================*/
446 * create storage object to be used for message text
448 if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
449 q_status_message(SM_ORDER | SM_DING, 3, 4,
450 _("Error allocating message text"));
451 goto done_early;
454 gf_set_so_writec(&pc, (STORE_S *) msgtext);
456 /*---- Include the original text if requested ----*/
457 if(include_text && totalm > 1L){
458 char *sig;
459 int impl, template_len = 0, leave_cursor_at_top = 0;
462 env = NULL;
463 if(role && role->template){
464 char *filtered;
466 impl = 0;
467 filtered = detoken(role, env, 0,
468 ps_global->reply.signature_bottom,
469 0, &redraft_pos, &impl);
470 if(filtered){
471 if(*filtered){
472 so_puts((STORE_S *)msgtext, filtered);
473 if(impl == 1)
474 template_len = strlen(filtered);
475 else if(impl == 2)
476 leave_cursor_at_top++;
479 fs_give((void **)&filtered);
481 else
482 impl = 1;
484 else
485 impl = 1;
487 if((sig = reply_signature(role, env, &redraft_pos, &impl)) &&
488 !ps_global->reply.signature_bottom){
491 * If CURSORPOS was set explicitly in sig_file, and there was a
492 * template file before that, we need to adjust the offset by the
493 * length of the template file. However, if the template had
494 * a set CURSORPOS in it then impl was 2 before getting to the
495 * signature, so offset wouldn't have been reset by the signature
496 * CURSORPOS and offset would already be correct. That case will
497 * be ok here because template_len will be 0 and adding it does
498 * nothing. If template
499 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
500 * by the CURSORPOS in the sig. In that case we have to adjust the
501 * offset. That's what the next line does. It adjusts it if
502 * template_len is nonzero and if CURSORPOS was set in sig_file.
504 if(impl == 2)
505 redraft_pos->offset += template_len;
507 if(*sig)
508 so_puts((STORE_S *)msgtext, sig);
511 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
512 * is set, we won't have used it yet and want it to be non-NULL.
514 fs_give((void **)&sig);
518 * Only put cursor in sig if there is a cursorpos there but not
519 * one in the template, and sig-at-bottom.
521 if(!(sig && impl == 2 && !leave_cursor_at_top))
522 leave_cursor_at_top++;
524 body = mail_newbody();
525 body->type = TYPETEXT;
526 body->contents.text.data = msgtext;
528 for(msgno = mn_first_cur(pine_state->msgmap);
529 msgno > 0L;
530 msgno = mn_next_cur(pine_state->msgmap)){
532 if(env){ /* put 2 between messages */
533 gf_puts(NEWLINE, pc);
534 gf_puts(NEWLINE, pc);
537 /*--- Grab current envelope ---*/
538 env = pine_mail_fetchstructure(pine_state->mail_stream,
539 mn_m2raw(pine_state->msgmap, msgno),
540 &orig_body);
541 if(!env){
542 q_status_message1(SM_ORDER,3,4,
543 _("Error fetching message %s. Can't reply to it."),
544 long2string(mn_get_cur(pine_state->msgmap)));
545 goto done_early;
548 if(orig_body == NULL || orig_body->type == TYPETEXT || reply_raw_body) {
549 reply_delimiter(env, role, pc);
550 if(ps_global->reply.include_header)
551 reply_forward_header(pine_state->mail_stream,
552 mn_m2raw(pine_state->msgmap,msgno),
553 NULL, env, pc, prefix);
555 get_body_part_text(pine_state->mail_stream, reply_raw_body ? NULL : orig_body,
556 mn_m2raw(pine_state->msgmap, msgno),
557 reply_raw_body ? NULL : "1", 0L, pc, prefix,
558 NULL, GBPT_NONE);
560 else if(orig_body->type == TYPEMULTIPART) {
561 if(!warned++)
562 q_status_message(SM_ORDER,3,7,
563 _("WARNING! Attachments not included in multiple reply."));
565 if(orig_body->nested.part
566 && orig_body->nested.part->body.type == TYPETEXT) {
567 /*---- First part of the message is text -----*/
568 reply_delimiter(env, role, pc);
569 if(ps_global->reply.include_header)
570 reply_forward_header(pine_state->mail_stream,
571 mn_m2raw(pine_state->msgmap,
572 msgno),
573 NULL, env, pc, prefix);
575 get_body_part_text(pine_state->mail_stream,
576 &orig_body->nested.part->body,
577 mn_m2raw(pine_state->msgmap, msgno),
578 "1", 0L, pc, prefix, NULL, GBPT_NONE);
580 else{
581 q_status_message(SM_ORDER,0,3,
582 _("Multipart with no leading text part."));
585 else{
586 /*---- Single non-text message of some sort ----*/
587 q_status_message(SM_ORDER,3,3,
588 _("Non-text message not included."));
592 if(!leave_cursor_at_top){
593 long cnt = 0L;
594 unsigned char c;
596 /* rewind and count chars to start of sig file */
597 so_seek((STORE_S *)msgtext, 0L, 0);
598 while(so_readc(&c, (STORE_S *)msgtext))
599 cnt++;
601 if(!redraft_pos){
602 redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(*redraft_pos));
603 memset((void *)redraft_pos, 0,sizeof(*redraft_pos));
604 redraft_pos->hdrname = cpystr(":");
608 * If explicit cursor positioning in sig file,
609 * add offset to start of sig file plus offset into sig file.
610 * Else, just offset to start of sig file.
612 redraft_pos->offset += cnt;
615 if(sig){
616 if(*sig)
617 so_puts((STORE_S *)msgtext, sig);
619 fs_give((void **)&sig);
622 else{
623 msgno = mn_m2raw(pine_state->msgmap,
624 mn_get_cur(pine_state->msgmap));
626 /*--- Grab current envelope ---*/
627 env = pine_mail_fetchstructure(pine_state->mail_stream, msgno,
628 &orig_body);
631 * If the charset of the body part is different from ascii and
632 * charset conversion is _not_ happening, then preserve the original
633 * charset from the message so that if we don't enter any new
634 * chars with the hibit set we can use the original charset.
635 * If not all those things, then don't try to preserve it.
637 if(orig_body){
638 char *charset;
640 charset = parameter_val(orig_body->parameter, "charset");
641 if(charset && strucmp(charset, "us-ascii") != 0){
642 CONV_TABLE *ct;
645 * There is a non-ascii charset, is there conversion happening?
647 if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
648 reply.orig_charset = charset;
649 charset = NULL;
653 if(charset)
654 fs_give((void **) &charset);
657 if(env) {
658 if(!(body = reply_body(pine_state->mail_stream, env, orig_body,
659 msgno, NULL, msgtext, prefix,
660 include_text, role, 1, &redraft_pos)))
661 goto done_early;
663 else{
664 q_status_message1(SM_ORDER,3,4,
665 _("Error fetching message %s. Can't reply to it."),
666 long2string(mn_get_cur(pine_state->msgmap)));
667 goto done_early;
671 /* fill in reply structure */
672 reply.prefix = prefix;
673 reply.mailbox = cpystr(pine_state->mail_stream->mailbox);
674 reply.origmbox = cpystr(pine_state->mail_stream->original_mailbox
675 ? pine_state->mail_stream->original_mailbox
676 : pine_state->mail_stream->mailbox);
677 reply.data.uid.msgs = (imapuid_t *) fs_get((times + 1) * sizeof(imapuid_t));
678 if((reply.data.uid.validity = pine_state->mail_stream->uid_validity) != 0){
679 reply.uid = 1;
680 for(i = 0; i < times ; i++)
681 reply.data.uid.msgs[i] = mail_uid(pine_state->mail_stream, seq[i]);
683 else{
684 reply.msgno = 1;
685 for(i = 0; i < times ; i++)
686 reply.data.uid.msgs[i] = seq[i];
689 reply.data.uid.msgs[i] = 0; /* tie off list */
691 #if defined(DOS) && !defined(_WINDOWS)
692 free((void *)reserve);
693 #endif
695 /* partially formatted outgoing message */
696 pine_send(outgoing, &body, _("COMPOSE MESSAGE REPLY"),
697 role, fcc, &reply, redraft_pos, NULL, NULL, 0);
699 rv++;
700 pine_free_body(&body);
701 if(reply.mailbox)
702 fs_give((void **) &reply.mailbox);
703 if(reply.origmbox)
704 fs_give((void **) &reply.origmbox);
705 if(reply.orig_charset)
706 fs_give((void **) &reply.orig_charset);
707 fs_give((void **) &reply.data.uid.msgs);
708 done_early:
709 if((STORE_S *) msgtext)
710 gf_clear_so_writec((STORE_S *) msgtext);
712 mail_free_envelope(&outgoing);
713 mail_free_address(&saved_from);
714 mail_free_address(&saved_to);
715 mail_free_address(&saved_cc);
716 mail_free_address(&saved_resent);
718 fs_give((void **)&seq);
720 if(prefix)
721 fs_give((void **)&prefix);
723 if(fcc)
724 fs_give((void **) &fcc);
726 free_redraft_pos(&redraft_pos);
727 free_action(&role);
728 return rv;
733 * Ask user to confirm role choice, or choose another role.
735 * Args role -- A pointer into the pattern_h space at the default
736 * role to use. This can't be a copy, the comparison
737 * relies on it pointing at the actual role.
738 * This arg is also used to return a pointer to the
739 * chosen role.
741 * Returns 1 -- Yes, use role which is now in *role. This may not be
742 * the same as the role passed in and it may even be NULL.
743 * 0 -- Cancel reply.
746 confirm_role(long int rflags, ACTION_S **role)
748 ACTION_S *role_p = NULL;
749 ACTION_S *default_role = NULL;
750 char prompt[80], *prompt_fodder;
751 int cmd, done, ret = 1;
752 void (*prev_screen)(struct pine *) = ps_global->prev_screen,
753 (*redraw)(void) = ps_global->redrawer;
754 PAT_S *curpat, *pat;
755 PAT_STATE pstate;
756 HelpType help;
757 ESCKEY_S ekey[4];
759 if(!nonempty_patterns(ROLE_DO_ROLES, &pstate) || !role)
760 return(ret);
763 * If this is a reply or forward and the role doesn't require confirmation,
764 * then we just return with what was passed in.
766 if(((rflags & ROLE_REPLY) &&
767 *role && (*role)->repl_type == ROLE_REPL_NOCONF) ||
768 ((rflags & ROLE_FORWARD) &&
769 *role && (*role)->forw_type == ROLE_FORW_NOCONF) ||
770 ((rflags & ROLE_COMPOSE) &&
771 *role && (*role)->comp_type == ROLE_COMP_NOCONF) ||
772 (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global)
773 && !ps_global->default_role))
774 return(ret);
777 * Check that there is at least one role available. This is among all
778 * roles, not just the reply roles or just the forward roles. That's
779 * because we have ^T take us to all the roles, not the category-specific
780 * roles.
782 if(!(pat = last_pattern(&pstate)))
783 return(ret);
785 ekey[0].ch = 'y';
786 ekey[0].rval = 'y';
788 ekey[1].ch = 'n';
789 ekey[1].rval = 'n';
791 ekey[2].ch = ctrl('T');
792 ekey[2].rval = 2;
793 ekey[2].name = "^T";
795 ekey[3].ch = -1;
797 /* check for more than one role available (or no role set) */
798 if(pat == first_pattern(&pstate) && *role) /* no ^T */
799 ekey[2].ch = -1;
802 * Setup default role
803 * Go through the loop just in case default_role doesn't point
804 * to a real current role.
806 if(ps_global->default_role){
807 for(pat = first_pattern(&pstate);
808 pat;
809 pat = next_pattern(&pstate)){
810 if(pat->action == ps_global->default_role){
811 default_role = ps_global->default_role;
812 break;
817 curpat = NULL;
819 /* override default */
820 if(*role){
821 for(pat = first_pattern(&pstate);
822 pat;
823 pat = next_pattern(&pstate)){
824 if(pat->action == *role){
825 curpat = pat;
826 break;
831 if(rflags & ROLE_REPLY)
832 prompt_fodder = _("Reply");
833 else if(rflags & ROLE_FORWARD)
834 prompt_fodder = _("Forward");
835 else
836 prompt_fodder = _("Compose");
838 done = 0;
839 while(!done){
840 if(curpat){
841 char buf[100];
843 help = h_role_confirm;
844 ekey[0].name = "Y";
845 ekey[0].label = N_("Yes");
846 ekey[1].name = "N";
847 if(default_role)
848 ekey[1].label = N_("No, use default role");
849 else
850 ekey[1].label = N_("No, use default settings");
852 ekey[2].label = N_("To Select Alternate Role");
854 if(curpat->patgrp && curpat->patgrp->nick)
855 /* TRANSLATORS: This is something like Use role <nickname of role> for Reply? */
856 snprintf(prompt, sizeof(prompt), _("Use role \"%s\" for %s? "),
857 short_str(curpat->patgrp->nick, buf, sizeof(buf), 50, MidDots),
858 prompt_fodder);
859 else
860 snprintf(prompt, sizeof(prompt),
861 _("Use role \"<a role without a nickname>\" for %s? "),
862 prompt_fodder);
864 else{
865 help = h_norole_confirm;
866 ekey[0].name = "Ret";
867 ekey[0].label = prompt_fodder;
868 ekey[1].name = "";
869 ekey[1].label = "";
870 ekey[2].label = N_("To Select Role");
871 snprintf(prompt, sizeof(prompt),
872 _("Press Return to %s using %s role, or ^T to select a role "),
873 prompt_fodder, default_role ? _("default") : _("no"));
876 prompt[sizeof(prompt)-1] = '\0';
878 cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
879 'y', 'x', help, RB_NORM);
881 switch(cmd){
882 case 'y': /* Accept */
883 done++;
884 *role = curpat ? curpat->action : default_role;
885 break;
887 case 'x': /* Cancel */
888 ret = 0;
889 /* fall through */
891 case 'n': /* NoRole */
892 done++;
893 *role = default_role;
894 break;
896 case 2: /* ^T */
897 if(role_select_screen(ps_global, &role_p, 0) >= 0){
898 if(role_p){
899 for(pat = first_pattern(&pstate);
900 pat;
901 pat = next_pattern(&pstate)){
902 if(pat->action == role_p){
903 curpat = pat;
904 break;
908 else
909 curpat = NULL;
912 ClearBody();
913 ps_global->mangled_body = 1;
914 ps_global->prev_screen = prev_screen;
915 ps_global->redrawer = redraw;
916 break;
920 return(ret);
925 * reply_to_all_query - Ask user about replying to all recipients
927 * Returns: -1 if cancel, 0 otherwise
928 * by reference: flagp
931 reply_to_all_query(int *flagp)
933 char prompt[80];
934 ESCKEY_S ekey[4];
935 char cmd;
937 ekey[0].name = "Y";
938 ekey[0].ch = 'y';
939 ekey[0].rval = 'y';
940 ekey[0].label = N_("Yes");
941 ekey[1].name = "N";
942 ekey[1].ch = 'n';
943 ekey[1].rval = 'n';
944 ekey[1].label = N_("No");
945 ekey[2].name = "P";
946 ekey[2].ch = 'p';
947 ekey[2].rval = 'p';
948 ekey[3].ch = -1;
950 ps_global->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps_global);
951 loop:
952 ekey[2].label = ps_global->reply.preserve_fields ? N_("Not Preserve") : N_("Preserve");
953 snprintf(prompt, sizeof(prompt), _("Reply to all recipients%s"),
954 ps_global->reply.preserve_fields ? _(" (preserving fields)? ") : "? ");
956 prompt[sizeof(prompt)-1] = '\0';
958 switch(cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
959 'n', 'x', h_preserve_field, RB_NORM)){
961 case 'x' :
962 return(-1);
964 case 'y' : /* set reply-all bit */
965 (*flagp) |= RSF_FORCE_REPLY_ALL;
966 break;
968 case 'n' : /* clear reply-all bit */
969 (*flagp) &= ~RSF_FORCE_REPLY_ALL;
970 break;
972 case 'p' :
973 ps_global->reply.preserve_fields =
974 (ps_global->reply.preserve_fields + 1) % 2;
975 goto loop; /* ugly, but saves me a variable */
976 break;
979 return(0);
984 * reply_using_replyto_query - Ask user about replying with reply-to value
986 * Returns: 'y' if yes
987 * 'x' if cancel
990 reply_using_replyto_query(void)
992 return(want_to("Use \"Reply-To:\" address instead of \"From:\" address",
993 'y', 'x', NO_HELP,WT_SEQ_SENSITIVE));
998 * reply_text_query - Ask user about replying with text, or in the case
999 * of alternate reply menu, set values to the answer to all questions
1000 * asked during reply.
1002 * Returns: 1 if include the text
1003 * 0 if we're NOT to include the text
1004 * -1 on cancel or error
1006 #define MAX_REPLY_OPTIONS 10
1008 reply_text_query(struct pine *ps, long int many, ENVELOPE *env, char **prefix)
1010 int ret, edited = 0, headers = 0;
1011 static ESCKEY_S compose_style[MAX_REPLY_OPTIONS];
1012 int ekey_num;
1014 ps->reply.use_flowed = *prefix && **prefix ? (F_OFF(F_QUELL_FLOWED_TEXT, ps)
1015 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
1016 && (strcmp(*prefix, "> ") == 0
1017 || strcmp(*prefix, ">") == 0)) : 0;
1018 ps->reply.strip_signature = ps->full_header == 0
1019 && (F_ON(F_ENABLE_STRIP_SIGDASHES, ps)
1020 || F_ON(F_ENABLE_SIGDASHES, ps));
1021 ps->reply.keep_attach = F_ON(F_ATTACHMENTS_IN_REPLY, ps);
1022 ps->reply.include_header = F_ON(F_INCLUDE_HEADER, ps);
1023 ps->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps);
1024 ps->reply.signature_bottom = F_ON(F_SIG_AT_BOTTOM, ps);
1025 ps->reply.role_chosen = NULL;
1027 if(F_OFF(F_ALT_REPLY_MENU, ps)
1028 && F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)
1029 && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps)
1030 && F_OFF(F_ALT_REPLY_MENU,ps))
1031 return(1);
1033 while(1){
1034 compose_style[ekey_num = 0].ch = 'y';
1035 compose_style[ekey_num].rval = 'y';
1036 compose_style[ekey_num].name = "Y";
1037 compose_style[ekey_num++].label = N_("Yes");
1039 compose_style[ekey_num].ch = 'n';
1040 compose_style[ekey_num].rval = 'n';
1041 compose_style[ekey_num].name = "N";
1042 compose_style[ekey_num++].label = N_("No");
1044 if (F_OFF(F_ALT_REPLY_MENU, ps)){ /**** Standard menu ****/
1045 /* TRANSLATORS: The final five %s's can probably be safely ignored */
1046 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply%s%s%s%s%s%s? "),
1047 (many > 1L) ? comatose(many) : "",
1048 (many > 1L) ? " " : "",
1049 (many > 1L) ? "s" : "",
1050 (many > 1L) ? "s" : "",
1051 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
1052 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
1053 ps->reply.role_chosen ? "\" and role \"" : "",
1054 ps->reply.role_chosen ? ps->reply.role_chosen->nick : "",
1055 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");
1056 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1058 if (F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){
1059 compose_style[ekey_num].ch = ctrl('R');
1060 compose_style[ekey_num].rval = 'r';
1061 compose_style[ekey_num].name = "^R";
1062 compose_style[ekey_num++].label = N_("Edit Indent String");
1064 } else { /***** Alternate Reply Menu ********/
1065 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply (using \"%s%s%s\")? "),
1066 (many > 1L) ? comatose(many) : "",
1067 (many > 1L) ? " " : "",
1068 (many > 1L) ? "s" : "",
1069 *prefix,
1070 ps->reply.role_chosen ? "\" and role \"" : "",
1071 ps->reply.role_chosen ? ps->reply.role_chosen->nick : "");
1072 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1074 compose_style[ekey_num].ch = 'h';
1075 compose_style[ekey_num].rval = 'H';
1076 compose_style[ekey_num].name = "H";
1077 compose_style[ekey_num++].label = ps->reply.include_header
1078 ? N_("No Header") : N_("Inc Headr");
1080 compose_style[ekey_num].ch = 's';
1081 compose_style[ekey_num].rval = 'S';
1082 compose_style[ekey_num].name = "S";
1083 compose_style[ekey_num++].label = ps->reply.strip_signature
1084 ? N_("No Strip"): N_("Strip Sig");
1086 compose_style[ekey_num].ch = 'a';
1087 compose_style[ekey_num].rval = 'A';
1088 compose_style[ekey_num].name = "A";
1089 compose_style[ekey_num++].label = ps->reply.keep_attach
1090 ? N_("No Attach"): N_("Inc Attach");
1092 compose_style[ekey_num].ch = 'b';
1093 compose_style[ekey_num].rval = 'B';
1094 compose_style[ekey_num].name = "B";
1095 compose_style[ekey_num++].label = ps->reply.signature_bottom
1096 ? N_("Sig Top") : N_("Sig Bottom");
1098 compose_style[ekey_num].ch = 'r';
1099 compose_style[ekey_num].rval = 'R';
1100 compose_style[ekey_num].name = "R";
1101 compose_style[ekey_num++].label = N_("Set Role");
1103 compose_style[ekey_num].ch = ctrl('R');
1104 compose_style[ekey_num].rval = 'r';
1105 compose_style[ekey_num].name = "^R";
1106 compose_style[ekey_num++].label = N_("Edit Indent String");
1108 /***** End Alt Reply Menu *********/
1111 compose_style[ekey_num].ch = -1;
1112 compose_style[ekey_num].name = NULL;
1113 compose_style[ekey_num].label = NULL;
1115 switch(ret = radio_buttons(tmp_20k_buf,
1116 ps->ttyo->screen_rows > 4
1117 ? -FOOTER_ROWS(ps) : -1,
1118 compose_style,
1119 (edited || headers || F_ON(F_AUTO_INCLUDE_IN_REPLY, ps))
1120 ? 'y' : 'n',
1121 'x', NO_HELP, RB_SEQ_SENSITIVE)){
1122 case 'x':
1123 cmd_cancelled("Reply");
1124 return(-1);
1126 case 'A':
1127 ps->reply.keep_attach = (ps->reply.keep_attach + 1) % 2;
1128 break;
1130 case 'B':
1131 ps->reply.signature_bottom = (ps->reply.signature_bottom + 1) % 2;
1132 break;
1134 case 'F':
1135 ps->reply.use_flowed = (ps->reply.use_flowed + 1) % 2;
1136 break;
1138 case 'S':
1139 ps->reply.strip_signature = (ps->reply.strip_signature + 1) % 2;
1140 break;
1142 case 'H':
1143 ps->reply.include_header = (ps->reply.include_header + 1) % 2;
1144 headers = ps->reply.include_header;
1145 break;
1147 case 'R':
1149 void (*prev_screen)(struct pine *) = ps->prev_screen,
1150 (*redraw)(void) = ps->redrawer;
1151 ps->redrawer = NULL;
1152 ps->next_screen = SCREEN_FUN_NULL;
1153 if(role_select_screen(ps, &ps->reply.role_chosen, 1) < 0){
1154 cmd_cancelled("Reply");
1155 ps->next_screen = prev_screen;
1156 ps->redrawer = redraw;
1157 if (ps->redrawer)
1158 (*ps->redrawer)();
1159 continue;
1161 ps->next_screen = prev_screen;
1162 ps->redrawer = redraw;
1163 if(ps->reply.role_chosen)
1164 ps->reply.role_chosen = combine_inherited_role(ps->reply.role_chosen);
1166 if (ps->redrawer)
1167 (*ps->redrawer)();
1168 break;
1170 case 'r':
1171 if(prefix && *prefix){
1172 int done = 0;
1173 char buf[64];
1174 int flags;
1176 while(!done){
1177 strncpy(buf, *prefix, sizeof(buf)-1);
1178 buf[sizeof(buf)-1] = '\0';
1180 flags = OE_APPEND_CURRENT |
1181 OE_KEEP_TRAILING_SPACE |
1182 OE_DISALLOW_HELP |
1183 OE_SEQ_SENSITIVE;
1185 switch(optionally_enter(buf, ps->ttyo->screen_rows > 4
1186 ? -FOOTER_ROWS(ps) : -1,
1187 0, sizeof(buf), "Reply prefix : ",
1188 NULL, NO_HELP, &flags)){
1189 case 0: /* entry successful, continue */
1190 if(flags & OE_USER_MODIFIED){
1191 fs_give((void **)prefix);
1192 *prefix = removing_quotes(cpystr(buf));
1193 ps->reply.use_flowed = *prefix && **prefix ?
1194 (F_OFF(F_QUELL_FLOWED_TEXT, ps)
1195 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
1196 && (strcmp(*prefix, "> ") == 0
1197 || strcmp(*prefix, ">") == 0)) : 0;
1198 edited = 1;
1201 done++;
1202 break;
1204 case 1:
1205 cmd_cancelled("Reply");
1207 case -1:
1208 return(-1);
1210 case 4:
1211 EndInverse();
1212 ClearScreen();
1213 redraw_titlebar();
1214 if(ps_global->redrawer != NULL)
1215 (*ps->redrawer)();
1217 redraw_keymenu();
1218 break;
1220 case 3:
1221 break;
1223 case 2:
1224 default:
1225 q_status_message(SM_ORDER, 3, 4,
1226 "Programmer botch in reply_text_query()");
1227 return(-1);
1232 break;
1234 case 'y':
1235 return(1);
1237 case 'n':
1238 return(0);
1240 default:
1241 q_status_message1(SM_ORDER, 3, 4,
1242 "Invalid rval \'%s\'", pretty_command(ret));
1243 return(-1);
1250 * reply_poster_followup - return TRUE if "followup-to" set to "poster"
1252 * NOTE: queues status message indicating such
1255 reply_poster_followup(ENVELOPE *e)
1257 if(e && e->followup_to && !strucmp(e->followup_to, "poster")){
1258 q_status_message(SM_ORDER, 2, 3,
1259 _("Replying to Poster as specified in \"Followup-To\""));
1260 return(1);
1263 return(0);
1268 * reply_news_test - Test given envelope for newsgroup data and copy
1269 * it at the users request
1270 * RETURNS:
1271 * 0 if error or cancel
1272 * 1 reply via email
1273 * 2 follow-up via news
1274 * 3 do both
1277 reply_news_test(ENVELOPE *env, ENVELOPE *outgoing)
1279 int ret = 1;
1280 static ESCKEY_S news_opt[] = { {'f', 'f', "F", N_("Follow-up")},
1281 {'r', 'r', "R", N_("Reply")},
1282 {'b', 'b', "B", N_("Both")},
1283 {-1, 0, NULL, NULL} };
1285 if(env->newsgroups && *env->newsgroups && !reply_poster_followup(env))
1287 * Now that we know a newsgroups field is present,
1288 * ask if the user is posting a follow-up article...
1290 switch(radio_buttons(
1291 _("Follow-up to news group(s), Reply via email to author or Both? "),
1292 -FOOTER_ROWS(ps_global), news_opt, 'r', 'x',
1293 NO_HELP, RB_NORM)){
1294 case 'r' : /* Reply */
1295 ret = 1;
1296 break;
1298 case 'f' : /* Follow-Up via news ONLY! */
1299 ret = 2;
1300 break;
1302 case 'b' : /* BOTH */
1303 ret = 3;
1304 break;
1306 case 'x' : /* cancel or unknown response */
1307 default :
1308 cmd_cancelled("Reply");
1309 ret = 0;
1310 break;
1313 if(ret > 1){
1314 if(env->followup_to){
1315 q_status_message(SM_ORDER, 2, 3,
1316 _("Posting to specified Followup-To groups"));
1317 outgoing->newsgroups = cpystr(env->followup_to);
1319 else if(!outgoing->newsgroups)
1320 outgoing->newsgroups = cpystr(env->newsgroups);
1323 return(ret);
1327 /*----------------------------------------------------------------------
1328 Acquire the pinerc defined signature file
1329 It is allocated here and freed by the caller.
1331 file -- use this file
1332 prenewlines -- prefix the file contents with this many newlines
1333 postnewlines -- postfix the file contents with this many newlines
1334 is_sig -- this is a signature (not a template)
1335 ----*/
1336 char *
1337 get_signature_file(char *file, int prenewlines, int postnewlines, int is_sig)
1339 char *sig, *tmp_sig = NULL, sig_path[MAXPATH+1];
1340 int len, do_the_pipe_thang = 0;
1341 long sigsize = 0L, cntdown;
1343 sig_path[0] = '\0';
1344 if(!signature_path(file, sig_path, MAXPATH))
1345 return(NULL);
1347 dprint((5, "get_signature(%s)\n", sig_path));
1349 if(sig_path[(len=strlen(sig_path))-1] == '|'){
1350 if(is_sig && F_ON(F_DISABLE_PIPES_IN_SIGS, ps_global)){
1351 q_status_message(SM_ORDER | SM_DING, 3, 4,
1352 _("Pipes for signatures are administratively disabled"));
1353 return(NULL);
1355 else if(!is_sig && F_ON(F_DISABLE_PIPES_IN_TEMPLATES, ps_global)){
1356 q_status_message(SM_ORDER | SM_DING, 3, 4,
1357 _("Pipes for templates are administratively disabled"));
1358 return(NULL);
1361 sig_path[len-1] = '\0';
1362 removing_trailing_white_space(sig_path);
1363 do_the_pipe_thang++;
1366 if(!IS_REMOTE(sig_path) && ps_global->VAR_OPER_DIR &&
1367 !in_dir(ps_global->VAR_OPER_DIR, sig_path)){
1368 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1369 /* TRANSLATORS: First arg is the directory name, second is
1370 the file user wants to read but can't. */
1371 _("Can't read file outside %s: %s"),
1372 ps_global->VAR_OPER_DIR, file);
1374 return(NULL);
1377 if(IS_REMOTE(sig_path) || can_access(sig_path, ACCESS_EXISTS) == 0){
1378 if(do_the_pipe_thang){
1379 if(can_access(sig_path, EXECUTE_ACCESS) == 0){
1380 STORE_S *store;
1381 int flags;
1382 PIPE_S *syspipe;
1383 gf_io_t pc, gc;
1384 long start;
1386 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
1388 flags = PIPE_READ | PIPE_STDERR | PIPE_NOSHELL;
1390 start = time(0);
1392 if((syspipe = open_system_pipe(sig_path, NULL, NULL, flags, 5,
1393 pipe_callback, pipe_report_error)) != NULL){
1394 unsigned char c;
1395 char *error, *q;
1397 gf_set_so_writec(&pc, store);
1398 gf_set_readc(&gc, (void *)syspipe, 0, PipeStar, READ_FROM_LOCALE);
1399 gf_filter_init();
1401 if((error = gf_pipe(gc, pc)) != NULL){
1402 (void)close_system_pipe(&syspipe, NULL, pipe_callback);
1403 gf_clear_so_writec(store);
1404 so_give(&store);
1405 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1406 _("Can't get file: %s"), error);
1407 return(NULL);
1410 if(close_system_pipe(&syspipe, NULL, pipe_callback)){
1411 long now;
1413 now = time(0);
1414 q_status_message2(SM_ORDER, 3, 4,
1415 _("Error running program \"%s\"%s"),
1416 file,
1417 (now - start > 4) ? ": timed out" : "");
1420 gf_clear_so_writec(store);
1422 /* rewind and count chars */
1423 so_seek(store, 0L, 0);
1424 while(so_readc(&c, store) && sigsize < 100000L)
1425 sigsize++;
1427 /* allocate space */
1428 tmp_sig = fs_get((sigsize + 1) * sizeof(char));
1429 tmp_sig[0] = '\0';
1430 q = tmp_sig;
1432 /* rewind and copy chars, no prenewlines... */
1433 so_seek(store, 0L, 0);
1434 cntdown = sigsize;
1435 while(so_readc(&c, store) && cntdown-- > 0L)
1436 *q++ = c;
1438 *q = '\0';
1439 so_give(&store);
1441 else{
1442 so_give(&store);
1443 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1444 _("Error running program \"%s\""),
1445 file);
1448 else
1449 q_status_message(SM_ORDER | SM_DING, 3, 4,
1450 "Error allocating space for sig or template program");
1452 else
1453 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1454 /* TRANSLATORS: Arg is a program name */
1455 _("Can't execute \"%s\": Permission denied"),
1456 sig_path);
1458 else if((IS_REMOTE(sig_path) &&
1459 (tmp_sig = simple_read_remote_file(sig_path, REMOTE_SIG_SUBTYPE))) ||
1460 (tmp_sig = read_file(sig_path, READ_FROM_LOCALE)))
1461 sigsize = strlen(tmp_sig);
1462 else
1463 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1464 /* TRANSLATORS: First arg is error description, 2nd is
1465 filename */
1466 _("Error \"%s\" reading file \"%s\""),
1467 error_description(errno), sig_path);
1470 sig = get_signature_lit(tmp_sig, prenewlines, postnewlines, is_sig, 0);
1471 if(tmp_sig)
1472 fs_give((void **)&tmp_sig);
1474 return(sig);
1479 /*----------------------------------------------------------------------
1480 Partially set up message to forward and pass off to composer/mailer
1482 Args: pine_state -- The usual pine structure
1484 Result: outgoing envelope and body created and passed off to composer/mailer
1486 Create the outgoing envelope for the mail being forwarded, which is
1487 not much more than filling in the subject, and create the message body
1488 of the outgoing message which requires formatting the header from the
1489 envelope of the original message.
1490 ----------------------------------------------------------------------*/
1492 forward(struct pine *ps, ACTION_S *role_arg)
1494 char *sig;
1495 int ret, forward_raw_body = 0, rv = 0, i;
1496 long msgno = 0L, j, totalmsgs, rflags;
1497 ENVELOPE *env = NULL, *outgoing;
1498 BODY *orig_body, *body = NULL;
1499 REPLY_S reply;
1500 void *msgtext = NULL;
1501 gf_io_t pc;
1502 int impl, template_len = 0;
1503 PAT_STATE dummy;
1504 REDRAFT_POS_S *redraft_pos = NULL;
1505 ACTION_S *role = NULL, *nrole;
1506 #if defined(DOS) && !defined(_WINDOWS)
1507 char *reserve;
1508 #endif
1510 dprint((4, "\n - forward -\n"));
1512 memset((void *)&reply, 0, sizeof(reply));
1513 outgoing = mail_newenvelope();
1515 if(ps_global->full_header == 2
1516 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
1517 forward_raw_body = 1;
1519 if((totalmsgs = mn_total_cur(ps->msgmap)) > 1L){
1520 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s forwarded messages...", comatose(totalmsgs));
1521 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1522 outgoing->subject = cpystr(tmp_20k_buf);
1524 else{
1525 /*---------- Get the envelope of message we're forwarding ------*/
1526 msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
1527 if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL))
1528 && (outgoing->subject = forward_subject(env, 0)))){
1529 q_status_message1(SM_ORDER,3,4,
1530 _("Error fetching message %s. Can't forward it."),
1531 long2string(msgno));
1532 goto clean;
1537 * as with all text bound for the composer, build it in
1538 * a storage object of the type it understands...
1540 if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
1541 q_status_message(SM_ORDER | SM_DING, 3, 4,
1542 _("Error allocating message text"));
1543 goto clean;
1546 ret = (F_ON(F_FORWARD_AS_ATTACHMENT, ps_global))
1547 ? 'y'
1548 : (totalmsgs > 1L)
1549 ? want_to(_("Forward messages as a MIME digest"), 'y', 'x', NO_HELP, WT_SEQ_SENSITIVE)
1550 : (ps->full_header == 2)
1551 ? want_to(_("Forward message as an attachment"), 'n', 'x', NO_HELP, WT_SEQ_SENSITIVE)
1552 : 0;
1554 if(ret == 'x'){
1555 cmd_cancelled("Forward");
1556 so_give((STORE_S **)&msgtext);
1557 goto clean;
1560 /* Setup possible role */
1561 if(role_arg)
1562 role = copy_action(role_arg);
1564 if(!role){
1565 rflags = ROLE_FORWARD;
1566 if(nonempty_patterns(rflags, &dummy)){
1567 /* setup default role */
1568 nrole = NULL;
1569 j = mn_first_cur(ps->msgmap);
1570 do {
1571 role = nrole;
1572 nrole = set_role_from_msg(ps, rflags,
1573 mn_m2raw(ps->msgmap, j), NULL);
1574 } while(nrole && (!role || nrole == role)
1575 && (j=mn_next_cur(ps->msgmap)) > 0L);
1577 if(!role || nrole == role)
1578 role = nrole;
1579 else
1580 role = NULL;
1582 if(confirm_role(rflags, &role))
1583 role = combine_inherited_role(role);
1584 else{ /* cancel reply */
1585 role = NULL;
1586 cmd_cancelled("Forward");
1587 so_give((STORE_S **)&msgtext);
1588 goto clean;
1593 if(role)
1594 q_status_message1(SM_ORDER, 3, 4,
1595 _("Forwarding using role \"%s\""), role->nick);
1597 outgoing->message_id = generate_message_id(role);
1599 if(role && role->template){
1600 char *filtered;
1602 impl = 1;
1603 filtered = detoken(role, (totalmsgs == 1L) ? env : NULL,
1604 0, 0, 0, &redraft_pos, &impl);
1605 if(filtered){
1606 if(*filtered){
1607 so_puts((STORE_S *)msgtext, filtered);
1608 if(impl == 1)
1609 template_len = strlen(filtered);
1612 fs_give((void **)&filtered);
1615 else
1616 impl = 1;
1618 if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
1619 if(impl == 2)
1620 redraft_pos->offset += template_len;
1622 so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);
1624 fs_give((void **)&sig);
1626 else
1627 so_puts((STORE_S *)msgtext, NEWLINE);
1629 gf_set_so_writec(&pc, (STORE_S *)msgtext);
1631 #if defined(DOS) && !defined(_WINDOWS)
1632 #if defined(LWP) || defined(PCTCP) || defined(PCNFS)
1633 #define IN_RESERVE 8192
1634 #else
1635 #define IN_RESERVE 16384
1636 #endif
1637 if((reserve=(char *)malloc(IN_RESERVE)) == NULL){
1638 gf_clear_so_writec((STORE_S *) msgtext);
1639 so_give((STORE_S **)&msgtext);
1640 q_status_message(SM_ORDER | SM_DING, 3, 4,
1641 _("Insufficient memory for message text"));
1642 goto clean;
1644 #endif
1647 * If we're forwarding multiple messages *or* the forward-as-mime
1648 * is turned on and the users wants it done that way, package things
1649 * up...
1651 if(ret == 'y'){ /* attach message[s]!!! */
1652 PART **pp;
1653 long totalsize = 0L;
1655 /*---- New Body to start with ----*/
1656 body = mail_newbody();
1657 body->type = TYPEMULTIPART;
1659 /*---- The TEXT part/body ----*/
1660 body->nested.part = mail_newbody_part();
1661 body->nested.part->body.type = TYPETEXT;
1662 body->nested.part->body.contents.text.data = msgtext;
1664 if(totalmsgs > 1L){
1665 /*---- The MULTIPART/DIGEST part ----*/
1666 body->nested.part->next = mail_newbody_part();
1667 body->nested.part->next->body.type = TYPEMULTIPART;
1668 body->nested.part->next->body.subtype = cpystr("Digest");
1669 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Digest of %s messages", comatose(totalmsgs));
1670 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1671 body->nested.part->next->body.description = cpystr(tmp_20k_buf);
1672 pp = &(body->nested.part->next->body.nested.part);
1674 else
1675 pp = &(body->nested.part->next);
1677 /*---- The Message body subparts ----*/
1678 for(msgno = mn_first_cur(ps->msgmap);
1679 msgno > 0L;
1680 msgno = mn_next_cur(ps->msgmap)){
1682 msgno = mn_m2raw(ps->msgmap, msgno);
1683 env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL);
1685 if(forward_mime_msg(ps->mail_stream,msgno,NULL,env,pp,msgtext)){
1686 totalsize += (*pp)->body.size.bytes;
1687 pp = &((*pp)->next);
1689 else
1690 goto bomb;
1693 if(totalmsgs > 1L)
1694 body->nested.part->next->body.size.bytes = totalsize;
1696 else if(totalmsgs > 1L){
1697 int warned = 0;
1698 body = mail_newbody();
1699 body->type = TYPETEXT;
1700 body->contents.text.data = msgtext;
1701 env = NULL;
1703 for(msgno = mn_first_cur(ps->msgmap);
1704 msgno > 0L;
1705 msgno = mn_next_cur(ps->msgmap)){
1707 if(env){ /* put 2 between messages */
1708 gf_puts(NEWLINE, pc);
1709 gf_puts(NEWLINE, pc);
1712 /*--- Grab current envelope ---*/
1713 env = pine_mail_fetchstructure(ps->mail_stream,
1714 mn_m2raw(ps->msgmap, msgno),
1715 &orig_body);
1716 if(!env || !orig_body){
1717 q_status_message1(SM_ORDER,3,4,
1718 _("Error fetching message %s. Can't forward it."),
1719 long2string(msgno));
1720 goto bomb;
1723 if(orig_body == NULL || orig_body->type == TYPETEXT || forward_raw_body) {
1724 forward_delimiter(pc);
1725 reply_forward_header(ps->mail_stream,
1726 mn_m2raw(ps->msgmap, msgno),
1727 NULL, env, pc, "");
1729 if(!get_body_part_text(ps->mail_stream, forward_raw_body ? NULL : orig_body,
1730 mn_m2raw(ps->msgmap, msgno),
1731 forward_raw_body ? NULL : "1", 0L, pc,
1732 NULL, NULL, GBPT_NONE))
1733 goto bomb;
1734 } else if(orig_body->type == TYPEMULTIPART) {
1735 if(!warned++)
1736 q_status_message(SM_ORDER,3,7,
1737 _("WARNING! Attachments not included in multiple forward."));
1739 if(orig_body->nested.part &&
1740 orig_body->nested.part->body.type == TYPETEXT) {
1741 /*---- First part of the message is text -----*/
1742 forward_delimiter(pc);
1743 reply_forward_header(ps->mail_stream,
1744 mn_m2raw(ps->msgmap,msgno),
1745 NULL, env, pc, "");
1747 if(!get_body_part_text(ps->mail_stream,
1748 &orig_body->nested.part->body,
1749 mn_m2raw(ps->msgmap, msgno),
1750 "1", 0L, pc,
1751 NULL, NULL, GBPT_NONE))
1752 goto bomb;
1753 } else {
1754 q_status_message(SM_ORDER,0,3,
1755 _("Multipart with no leading text part!"));
1757 } else {
1758 /*---- Single non-text message of some sort ----*/
1759 q_status_message(SM_ORDER,0,3,
1760 _("Non-text message not included!"));
1764 else if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno,
1765 &orig_body))
1766 && (body = forward_body(ps->mail_stream, env, orig_body, msgno,
1767 NULL, msgtext,
1768 FWD_NONE)))){
1769 q_status_message1(SM_ORDER,3,4,
1770 _("Error fetching message %s. Can't forward it."),
1771 long2string(msgno));
1772 goto clean;
1775 if(ret != 'y' && totalmsgs == 1L && orig_body){
1776 char *charset;
1778 charset = parameter_val(orig_body->parameter, "charset");
1779 if(charset && strucmp(charset, "us-ascii") != 0){
1780 CONV_TABLE *ct;
1783 * There is a non-ascii charset, is there conversion happening?
1785 if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
1786 reply.orig_charset = charset;
1787 charset = NULL;
1791 if(charset)
1792 fs_give((void **) &charset);
1795 * I don't think orig_charset is ever used except possibly
1796 * right here. Hubert 2008-01-15.
1798 if(reply.orig_charset)
1799 reply.forw = 1;
1802 /* fill in reply structure */
1803 reply.forwarded = 1;
1804 reply.mailbox = cpystr(ps->mail_stream->mailbox);
1805 reply.origmbox = cpystr(ps->mail_stream->original_mailbox
1806 ? ps->mail_stream->original_mailbox
1807 : ps->mail_stream->mailbox);
1808 reply.data.uid.msgs = (imapuid_t *) fs_get((totalmsgs + 1) * sizeof(imapuid_t));
1809 if((reply.data.uid.validity = ps->mail_stream->uid_validity) != 0){
1810 reply.uid = 1;
1811 for(msgno = mn_first_cur(ps->msgmap), i = 0;
1812 msgno > 0L;
1813 msgno = mn_next_cur(ps->msgmap), i++)
1814 reply.data.uid.msgs[i] = mail_uid(ps->mail_stream, mn_m2raw(ps->msgmap, msgno));
1816 else{
1817 reply.msgno = 1;
1818 for(msgno = mn_first_cur(ps->msgmap), i = 0;
1819 msgno > 0L;
1820 msgno = mn_next_cur(ps->msgmap), i++)
1821 reply.data.uid.msgs[i] = mn_m2raw(ps->msgmap, msgno);
1824 reply.data.uid.msgs[i] = 0; /* tie off list */
1826 #if defined(DOS) && !defined(_WINDOWS)
1827 free((void *)reserve);
1828 #endif
1829 pine_send(outgoing, &body, "FORWARD MESSAGE",
1830 role, NULL, &reply, redraft_pos,
1831 NULL, NULL, 0);
1832 rv++;
1834 clean:
1835 if(body)
1836 pine_free_body(&body);
1838 if((STORE_S *) msgtext)
1839 gf_clear_so_writec((STORE_S *) msgtext);
1841 mail_free_envelope(&outgoing);
1842 free_redraft_pos(&redraft_pos);
1843 free_action(&role);
1845 if(reply.orig_charset)
1846 fs_give((void **)&reply.orig_charset);
1848 return rv;
1850 bomb:
1851 q_status_message(SM_ORDER | SM_DING, 4, 5,
1852 _("Error fetching message contents. Can't forward message."));
1853 goto clean;
1857 /*----------------------------------------------------------------------
1858 Partially set up message to forward and pass off to composer/mailer
1860 Args: pine_state -- The usual pine structure
1861 message -- The MESSAGECACHE of entry to reply to
1863 Result: outgoing envelope and body created and passed off to composer/mailer
1865 Create the outgoing envelope for the mail being forwarded, which is
1866 not much more than filling in the subject, and create the message body
1867 of the outgoing message which requires formatting the header from the
1868 envelope of the original message.
1869 ----------------------------------------------------------------------*/
1870 void
1871 forward_text(struct pine *pine_state, void *text, SourceType source)
1873 ENVELOPE *env;
1874 BODY *body;
1875 gf_io_t pc, gc;
1876 STORE_S *msgtext;
1877 char *enc_error, *sig;
1878 ACTION_S *role = NULL;
1879 PAT_STATE dummy;
1880 long rflags = ROLE_COMPOSE;
1882 if((msgtext = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
1883 env = mail_newenvelope();
1884 body = mail_newbody();
1885 body->type = TYPETEXT;
1886 body->contents.text.data = (void *) msgtext;
1888 if(nonempty_patterns(rflags, &dummy)){
1890 * This is really more like Compose, even though it
1891 * is called Forward.
1893 if(confirm_role(rflags, &role))
1894 role = combine_inherited_role(role);
1895 else{ /* cancel */
1896 cmd_cancelled("Composition");
1897 display_message('x');
1898 mail_free_envelope(&env);
1899 pine_free_body(&body);
1900 return;
1904 if(role)
1905 q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
1906 role->nick);
1908 env->message_id = generate_message_id(role);
1910 sig = detoken(role, NULL, 2, 0, 1, NULL, NULL);
1911 so_puts(msgtext, (sig && *sig) ? sig : NEWLINE);
1912 so_puts(msgtext, NEWLINE);
1913 so_puts(msgtext, "----- Included text -----");
1914 so_puts(msgtext, NEWLINE);
1915 if(sig)
1916 fs_give((void **)&sig);
1918 gf_filter_init();
1919 gf_set_so_writec(&pc, msgtext);
1920 gf_set_readc(&gc,text,(source == CharStar) ? strlen((char *)text) : 0L,
1921 source, 0);
1923 if((enc_error = gf_pipe(gc, pc)) == NULL){
1924 pine_send(env, &body, "SEND MESSAGE", role, NULL, NULL, NULL,
1925 NULL, NULL, 0);
1926 pine_state->mangled_screen = 1;
1928 else{
1929 q_status_message1(SM_ORDER | SM_DING, 3, 5,
1930 _("Error reading text \"%s\""),enc_error);
1931 display_message('x');
1934 gf_clear_so_writec(msgtext);
1935 mail_free_envelope(&env);
1936 pine_free_body(&body);
1938 else {
1939 q_status_message(SM_ORDER | SM_DING, 3, 4,
1940 _("Error allocating message text"));
1941 display_message('x');
1944 free_action(&role);
1948 /*----------------------------------------------------------------------
1949 Partially set up message to resend and pass off to mailer
1951 Args: pine_state -- The usual pine structure
1953 Result: outgoing envelope and body created and passed off to mailer
1955 Create the outgoing envelope for the mail being resent, which is
1956 not much more than filling in the subject, and create the message body
1957 of the outgoing message which requires formatting the header from the
1958 envelope of the original message.
1959 ----------------------------------------------------------------------*/
1961 bounce(struct pine *pine_state, ACTION_S *role)
1963 ENVELOPE *env;
1964 long msgno, rawno;
1965 char *save_to = NULL, **save_toptr = NULL, *errstr = NULL,
1966 *prmpt_who = NULL, *prmpt_cnf = NULL;
1968 dprint((4, "\n - bounce -\n"));
1970 if(mn_total_cur(pine_state->msgmap) > 1L){
1971 save_toptr = &save_to;
1972 if(role)
1973 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages (using role %s) to : "),
1974 mn_total_cur(pine_state->msgmap), role->nick);
1975 else
1976 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages to : "),
1977 mn_total_cur(pine_state->msgmap));
1978 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1979 prmpt_who = cpystr(tmp_20k_buf);
1980 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Send %ld messages "),
1981 mn_total_cur(pine_state->msgmap));
1982 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1983 prmpt_cnf = cpystr(tmp_20k_buf);
1986 for(msgno = mn_first_cur(pine_state->msgmap);
1987 msgno > 0L;
1988 msgno = mn_next_cur(pine_state->msgmap)){
1990 rawno = mn_m2raw(pine_state->msgmap, msgno);
1991 if((env = pine_mail_fetchstructure(pine_state->mail_stream, rawno, NULL)) != NULL)
1992 errstr = bounce_msg(pine_state->mail_stream, rawno, NULL, role,
1993 save_toptr, env->subject, prmpt_who, prmpt_cnf);
1994 else
1995 errstr = _("Can't fetch Subject for Bounce");
1998 if(errstr){
1999 if(*errstr)
2000 q_status_message(SM_ORDER | SM_DING, 4, 7, errstr);
2002 break;
2006 if(save_to)
2007 fs_give((void **)&save_to);
2009 if(prmpt_who)
2010 fs_give((void **) &prmpt_who);
2012 if(prmpt_cnf)
2013 fs_give((void **) &prmpt_cnf);
2015 return(errstr ? 0 : 1);
2020 char *
2021 bounce_msg(MAILSTREAM *stream,
2022 long int rawno,
2023 char *part,
2024 ACTION_S *role,
2025 char **to,
2026 char *subject,
2027 char *pmt_who,
2028 char *pmt_cnf)
2030 char *errstr = NULL;
2031 int was_seen = -1;
2032 ENVELOPE *outgoing;
2033 BODY *body = NULL;
2034 MESSAGECACHE *mc;
2036 #ifdef SMIME
2037 /* When we bounce a message, we will leave the original message
2038 * intact, which means that it will not be signed or encrypted,
2039 * so we turn off signing and encrypting now. It will be turned
2040 * on again in send_exit_for_pico().
2042 if(ps_global->smime)
2043 ps_global->smime->do_sign = ps_global->smime->do_encrypt = 0;
2044 #endif /* SMIME */
2046 if((errstr = bounce_msg_body(stream, rawno, part, to, subject, &outgoing, &body, &was_seen)) == NULL){
2047 if(pine_simple_send(outgoing, &body, &role, pmt_who, pmt_cnf, to,
2048 !(to && *to) ? SS_PROMPTFORTO : 0) < 0){
2049 errstr = ""; /* p_s_s() better have explained! */
2050 /* clear seen flag */
2051 if(was_seen == 0 && rawno > 0L
2052 && stream && rawno <= stream->nmsgs
2053 && (mc = mail_elt(stream, rawno)) && mc->seen)
2054 mail_flag(stream, long2string(rawno), "\\SEEN", 0);
2058 /* Just for good measure... */
2059 mail_free_envelope(&outgoing);
2060 pine_free_body(&body);
2062 return(errstr); /* no problem-o */
2066 /*----------------------------------------------------------------------
2067 Serve up the current signature within pico for editing
2069 Args: file to edit
2071 Result: signature changed or not.
2072 ---*/
2073 char *
2074 signature_edit(char *sigfile, char *title)
2076 int editor_result;
2077 char sig_path[MAXPATH+1], errbuf[2000], *errstr = NULL;
2078 char *ret = NULL;
2079 STORE_S *msgso, *tmpso = NULL;
2080 gf_io_t gc, pc;
2081 PICO pbf;
2082 struct variable *vars = ps_global->vars;
2083 REMDATA_S *rd = NULL;
2085 if(!signature_path(sigfile, sig_path, MAXPATH))
2086 return(cpystr(_("No signature file defined.")));
2088 if(IS_REMOTE(sigfile)){
2089 rd = rd_create_remote(RemImap, sig_path, REMOTE_SIG_SUBTYPE,
2090 NULL, "Error: ",
2091 _("Can't access remote configuration."));
2092 if(!rd)
2093 return(cpystr(_("Error attempting to edit remote configuration")));
2095 (void)rd_read_metadata(rd);
2097 if(rd->access == MaybeRorW){
2098 if(rd->read_status == 'R')
2099 rd->access = ReadOnly;
2100 else
2101 rd->access = ReadWrite;
2104 if(rd->access != NoExists){
2106 rd_check_remvalid(rd, 1L);
2109 * If the cached info says it is readonly but
2110 * it looks like it's been fixed now, change it to readwrite.
2112 if(rd->read_status == 'R'){
2113 rd_check_readonly_access(rd);
2114 if(rd->read_status == 'W'){
2115 rd->access = ReadWrite;
2116 rd->flags |= REM_OUTOFDATE;
2118 else
2119 rd->access = ReadOnly;
2123 if(rd->flags & REM_OUTOFDATE){
2124 if(rd_update_local(rd) != 0){
2126 dprint((1,
2127 "signature_edit: rd_update_local failed\n"));
2128 rd_close_remdata(&rd);
2129 return(cpystr(_("Can't access remote sig")));
2132 else
2133 rd_open_remote(rd);
2135 if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
2136 rd_close_remdata(&rd);
2137 return(cpystr(_("Can't get write permission for remote sig")));
2140 rd->flags |= DO_REMTRIM;
2142 strncpy(sig_path, rd->lf, sizeof(sig_path)-1);
2143 sig_path[sizeof(sig_path)-1] = '\0';
2146 standard_picobuf_setup(&pbf);
2147 pbf.tty_fix = PineRaw;
2148 pbf.composer_help = h_composer_sigedit;
2149 pbf.exittest = sigedit_exit_for_pico;
2150 pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
2151 ? upload_msg_to_pico : NULL;
2152 pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
2153 ? VAR_EDITOR : NULL;
2154 pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
2155 pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
2156 pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
2157 pbf.allow_flowed_text = 0;
2159 pbf.pine_anchor = set_titlebar(title,
2160 ps_global->mail_stream,
2161 ps_global->context_current,
2162 ps_global->cur_folder,
2163 ps_global->msgmap,
2164 0, FolderName, 0, 0, NULL);
2166 /* NOTE: at this point, a lot of pico struct fields are null'd out
2167 * thanks to the leading memset; in particular "headents" which tells
2168 * pico to behave like a normal editor (though modified slightly to
2169 * let the caller dictate the file to edit and such)...
2172 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, sig_path)){
2173 size_t l;
2175 l = strlen(VAR_OPER_DIR) + 100;
2176 ret = (char *) fs_get((l+1) * sizeof(char));
2177 snprintf(ret, l+1, _("Can't edit file outside of %s"), VAR_OPER_DIR);
2178 ret[l] = '\0';
2179 return(ret);
2183 * Now alloc and init the text to pass pico
2185 if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
2186 ret = cpystr(_("Error allocating space for file"));
2187 dprint((1, "Can't alloc space for signature_edit"));
2188 return(ret);
2190 else
2191 pbf.msgtext = so_text(msgso);
2193 if(can_access(sig_path, READ_ACCESS) == 0
2194 && !(tmpso = so_get(FileStar, sig_path, READ_ACCESS|READ_FROM_LOCALE))){
2195 char *problem = error_description(errno);
2197 snprintf(errbuf, sizeof(errbuf), _("Error editing \"%s\": %s"),
2198 sig_path, problem ? problem : "<NULL>");
2199 errbuf[sizeof(errbuf)-1] = '\0';
2200 ret = cpystr(errbuf);
2202 dprint((1, "signature_edit: can't open %s: %s", sig_path,
2203 problem ? problem : "<NULL>"));
2204 return(ret);
2206 else if(tmpso){ /* else, fill pico's edit buffer */
2207 gf_set_so_readc(&gc, tmpso); /* read from file, write pico buf */
2208 gf_set_so_writec(&pc, msgso);
2209 gf_filter_init(); /* no filters needed */
2210 if((errstr = gf_pipe(gc, pc)) != NULL){
2211 snprintf(errbuf, sizeof(errbuf), _("Error reading file: \"%s\""), errstr);
2212 errbuf[sizeof(errbuf)-1] = '\0';
2213 ret = cpystr(errbuf);
2216 gf_clear_so_readc(tmpso);
2217 gf_clear_so_writec(msgso);
2218 so_give(&tmpso);
2221 if(!errstr){
2222 #ifdef _WINDOWS
2223 mswin_setwindowmenu (MENU_COMPOSER);
2224 #endif
2226 /*------ OK, Go edit the signature ------*/
2227 editor_result = pico(&pbf);
2229 #ifdef _WINDOWS
2230 mswin_setwindowmenu (MENU_DEFAULT);
2231 #endif
2232 if(editor_result & COMP_GOTHUP){
2233 hup_signal(); /* do what's normal for a hup */
2235 else{
2236 fix_windsize(ps_global);
2237 init_signals();
2240 if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
2242 else{
2243 /*------ Must have an edited buffer, write it to .sig -----*/
2244 our_unlink(sig_path); /* blast old copy */
2245 if((tmpso = so_get(FileStar, sig_path, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
2246 so_seek(msgso, 0L, 0);
2247 gf_set_so_readc(&gc, msgso); /* read from pico buf */
2248 gf_set_so_writec(&pc, tmpso); /* write sig file */
2249 gf_filter_init(); /* no filters needed */
2250 if((errstr = gf_pipe(gc, pc)) != NULL){
2251 snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
2252 errstr);
2253 errbuf[sizeof(errbuf)-1] = '\0';
2254 ret = cpystr(errbuf);
2257 gf_clear_so_readc(msgso);
2258 gf_clear_so_writec(tmpso);
2259 if(so_give(&tmpso)){
2260 errstr = error_description(errno);
2261 snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
2262 errstr);
2263 errbuf[sizeof(errbuf)-1] = '\0';
2264 ret = cpystr(errbuf);
2267 if(IS_REMOTE(sigfile)){
2268 int e, we_cancel;
2269 char datebuf[200];
2271 datebuf[0] = '\0';
2273 we_cancel = busy_cue("Copying to remote sig", NULL, 1);
2274 if((e = rd_update_remote(rd, datebuf)) != 0){
2275 if(e == -1){
2276 q_status_message2(SM_ORDER | SM_DING, 3, 5,
2277 _("Error opening temporary sig file %s: %s"),
2278 rd->lf, error_description(errno));
2279 dprint((1,
2280 "write_remote_sig: error opening temp file %s\n",
2281 rd->lf ? rd->lf : "?"));
2283 else{
2284 q_status_message2(SM_ORDER | SM_DING, 3, 5,
2285 _("Error copying to %s: %s"),
2286 rd->rn, error_description(errno));
2287 dprint((1,
2288 "write_remote_sig: error copying from %s to %s\n",
2289 rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
2292 q_status_message(SM_ORDER | SM_DING, 5, 5,
2293 _("Copy of sig to remote folder failed, changes NOT saved remotely"));
2295 else{
2296 rd_update_metadata(rd, datebuf);
2297 rd->read_status = 'W';
2300 rd_close_remdata(&rd);
2302 if(we_cancel)
2303 cancel_busy_cue(-1);
2306 else{
2307 snprintf(errbuf, sizeof(errbuf), _("Error writing \"%s\""), sig_path);
2308 errbuf[sizeof(errbuf)-1] = '\0';
2309 ret = cpystr(errbuf);
2310 dprint((1, "signature_edit: can't write %s",
2311 sig_path));
2316 standard_picobuf_teardown(&pbf);
2317 so_give(&msgso);
2318 return(ret);
2322 /*----------------------------------------------------------------------
2323 Serve up the current signature within pico for editing
2325 Args: literal signature to edit
2327 Result: raw edited signature is returned in result arg
2328 ---*/
2329 char *
2330 signature_edit_lit(char *litsig, char **result, char *title, HelpType composer_help)
2332 int editor_result;
2333 char *errstr = NULL;
2334 char *ret = NULL;
2335 STORE_S *msgso;
2336 PICO pbf;
2337 struct variable *vars = ps_global->vars;
2339 standard_picobuf_setup(&pbf);
2340 pbf.tty_fix = PineRaw;
2341 pbf.search_help = h_sigedit_search;
2342 pbf.composer_help = composer_help;
2343 pbf.exittest = sigedit_exit_for_pico;
2344 pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
2345 ? upload_msg_to_pico : NULL;
2346 pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
2347 ? VAR_EDITOR : NULL;
2348 pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
2349 pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
2350 pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
2351 pbf.allow_flowed_text = 0;
2353 pbf.pine_anchor = set_titlebar(title,
2354 ps_global->mail_stream,
2355 ps_global->context_current,
2356 ps_global->cur_folder,
2357 ps_global->msgmap,
2358 0, FolderName, 0, 0, NULL);
2360 /* NOTE: at this point, a lot of pico struct fields are null'd out
2361 * thanks to the leading memset; in particular "headents" which tells
2362 * pico to behave like a normal editor (though modified slightly to
2363 * let the caller dictate the file to edit and such)...
2367 * Now alloc and init the text to pass pico
2369 if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
2370 ret = cpystr(_("Error allocating space"));
2371 dprint((1, "Can't alloc space for signature_edit_lit"));
2372 return(ret);
2374 else
2375 pbf.msgtext = so_text(msgso);
2377 so_puts(msgso, litsig ? litsig : "");
2380 if(!errstr){
2381 #ifdef _WINDOWS
2382 mswin_setwindowmenu (MENU_COMPOSER);
2383 #endif
2385 /*------ OK, Go edit the signature ------*/
2386 editor_result = pico(&pbf);
2388 #ifdef _WINDOWS
2389 mswin_setwindowmenu (MENU_DEFAULT);
2390 #endif
2391 if(editor_result & COMP_GOTHUP){
2392 hup_signal(); /* do what's normal for a hup */
2394 else{
2395 fix_windsize(ps_global);
2396 init_signals();
2399 if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
2400 ret = cpystr(_("Edit Cancelled"));
2402 else{
2403 /*------ Must have an edited buffer, write it to .sig -----*/
2404 unsigned char c;
2405 int cnt = 0;
2406 char *p;
2408 so_seek(msgso, 0L, 0);
2409 while(so_readc(&c, msgso))
2410 cnt++;
2412 *result = (char *)fs_get((cnt+1) * sizeof(char));
2413 p = *result;
2414 so_seek(msgso, 0L, 0);
2415 while(so_readc(&c, msgso))
2416 *p++ = c;
2418 *p = '\0';
2422 standard_picobuf_teardown(&pbf);
2423 so_give(&msgso);
2424 return(ret);
2429 * Returns 0 for Save Changes and exit
2430 * 1 for Cancel Exit
2431 * -1 exit but Dont Save Changes
2434 sigedit_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
2435 char **result)
2437 int rv;
2438 char *rstr = NULL;
2439 void (*redraw)(void) = ps_global->redrawer;
2440 static ESCKEY_S opts[] = {
2441 {'s', 's', "S", N_("Save changes")},
2442 {'d', 'd', "D", N_("Don't save changes")},
2443 {-1, 0, NULL, NULL}
2446 ps_global->redrawer = redraw_pico;
2447 fix_windsize(ps_global);
2449 while(1){
2450 rv = radio_buttons(_("Exit editor? "),
2451 -FOOTER_ROWS(ps_global), opts,
2452 's', 'x', h_exit_editor, RB_NORM);
2453 if(rv == 's'){ /* user ACCEPTS! */
2454 break;
2456 else if(rv == 'd'){ /* Declined! */
2457 rstr = _("No Changes Saved");
2458 break;
2460 else if(rv == 'x'){ /* Cancelled! */
2461 rstr = _("Exit Cancelled");
2462 break;
2466 if(result)
2467 *result = rstr;
2469 ps_global->redrawer = redraw;
2470 return((rv == 's') ? 0 : (rv == 'd') ? -1 : 1);
2475 * Common stuff we almost always want to set when calling pico.
2477 void
2478 standard_picobuf_setup(PICO *pbf)
2480 memset(pbf, 0, sizeof(*pbf));
2482 pbf->pine_version = ALPINE_VERSION;
2483 pbf->fillcolumn = ps_global->composer_fillcol;
2484 pbf->menu_rows = FOOTER_ROWS(ps_global) - 1;
2485 pbf->colors = colors_for_pico();
2486 pbf->wordseps = user_wordseps(ps_global->VAR_WORDSEPS);
2487 pbf->helper = helper;
2488 pbf->showmsg = display_message_for_pico;
2489 pbf->suspend = do_suspend;
2490 pbf->keybinput = cmd_input_for_pico;
2491 pbf->tty_fix = ttyfix; /* watch out for this one */
2492 pbf->newmail = new_mail_for_pico;
2493 pbf->ckptdir = checkpoint_dir_for_pico;
2494 pbf->resize = resize_for_pico;
2495 pbf->input_cs = ps_global->input_cs;
2496 pbf->winch_cleanup = winch_cleanup;
2497 pbf->search_help = h_composer_search;
2498 pbf->ins_help = h_composer_ins;
2499 pbf->ins_m_help = h_composer_ins_m;
2500 pbf->composer_help = h_composer;
2501 pbf->browse_help = h_composer_browse;
2502 pbf->attach_help = h_composer_ctrl_j;
2504 pbf->pine_flags =
2505 ( (F_ON(F_CAN_SUSPEND,ps_global) ? P_SUSPEND : 0L)
2506 | (F_ON(F_USE_FK,ps_global) ? P_FKEYS : 0L)
2507 | (ps_global->restricted ? P_SECURE : 0L)
2508 | (F_ON(F_ALT_ED_NOW,ps_global) ? P_ALTNOW : 0L)
2509 | (F_ON(F_USE_CURRENT_DIR,ps_global) ? P_CURDIR : 0L)
2510 | (F_ON(F_SUSPEND_SPAWNS,ps_global) ? P_SUBSHELL : 0L)
2511 | (F_ON(F_COMPOSE_MAPS_DEL,ps_global) ? P_DELRUBS : 0L)
2512 | (F_ON(F_ENABLE_TAB_COMPLETE,ps_global) ? P_COMPLETE : 0L)
2513 | (F_ON(F_SHOW_CURSOR,ps_global) ? P_SHOCUR : 0L)
2514 | (F_ON(F_DEL_FROM_DOT,ps_global) ? P_DOTKILL : 0L)
2515 | (F_ON(F_ENABLE_DOT_FILES,ps_global) ? P_DOTFILES : 0L)
2516 | (F_ON(F_ALLOW_GOTO,ps_global) ? P_ALLOW_GOTO : 0L)
2517 | (F_ON(F_ENABLE_SEARCH_AND_REPL,ps_global) ? P_REPLACE : 0L)
2518 | (!ps_global->pass_ctrl_chars
2519 && !ps_global->pass_c1_ctrl_chars ? P_HICTRL : 0L)
2520 | ((F_ON(F_ENABLE_ALT_ED,ps_global)
2521 || F_ON(F_ALT_ED_NOW,ps_global)
2522 || (ps_global->VAR_EDITOR
2523 && ps_global->VAR_EDITOR[0]
2524 && ps_global->VAR_EDITOR[0][0]))
2525 ? P_ADVANCED : 0L)
2526 | ((!ps_global->keyboard_charmap
2527 || !strucmp(ps_global->keyboard_charmap, "US-ASCII"))
2528 ? P_HIBITIGN : 0L));
2530 if(ps_global->VAR_OPER_DIR){
2531 pbf->oper_dir = ps_global->VAR_OPER_DIR;
2532 pbf->pine_flags |= P_TREE;
2535 pbf->home_dir = ps_global->home_dir;
2539 void
2540 standard_picobuf_teardown(PICO *pbf)
2542 if(pbf){
2543 if(pbf->colors)
2544 free_pcolors(&pbf->colors);
2546 if(pbf->wordseps)
2547 fs_give((void **) &pbf->wordseps);
2552 /*----------------------------------------------------------------------
2553 Call back for pico to use to check for new mail.
2555 Args: cursor -- pointer to in to tell caller if cursor location changed
2556 if NULL, turn off cursor positioning.
2557 timing -- whether or not it's a good time to check
2560 Returns: returns 1 on success, zero on error.
2561 ----*/
2562 long
2563 new_mail_for_pico(int timing, int status)
2566 * If we're not interested in the status, don't display the busy
2567 * cue either...
2569 /* don't know where the cursor's been, reset it */
2570 clear_cursor_pos();
2571 return(new_mail(0, timing,
2572 (status ? NM_STATUS_MSG : NM_NONE) | NM_DEFER_SORT
2573 | NM_FROM_COMPOSER));
2577 void
2578 cmd_input_for_pico(void)
2580 zero_new_mail_count();
2584 /*----------------------------------------------------------------------
2585 Call back for pico to get newmail status messages displayed
2587 Args: x -- char processed
2589 Returns:
2590 ----*/
2592 display_message_for_pico(int x)
2594 int rv;
2596 clear_cursor_pos(); /* can't know where cursor is */
2597 mark_status_dirty(); /* don't count on cached text */
2598 fix_windsize(ps_global);
2599 init_sigwinch();
2600 display_message(x);
2601 rv = ps_global->mangled_screen;
2602 ps_global->mangled_screen = 0;
2603 return(rv);
2607 /*----------------------------------------------------------------------
2608 Call back for pico to get desired directory for its check point file
2610 Args: s -- buffer to write directory name
2611 n -- length of that buffer
2613 Returns: pointer to static buffer
2614 ----*/
2615 char *
2616 checkpoint_dir_for_pico(char *s, size_t n)
2618 #if defined(DOS) || defined(OS2)
2620 * we can't assume anything about root or home dirs, so
2621 * just plunk it down in the same place as the pinerc
2623 if(!getenv("HOME")){
2624 char *lc = last_cmpnt(ps_global->pinerc);
2626 if(lc != NULL){
2627 strncpy(s, ps_global->pinerc, MIN(n-1,lc-ps_global->pinerc));
2628 s[MIN(n-1,lc-ps_global->pinerc)] = '\0';
2630 else{
2631 strncpy(s, ".\\", n-1);
2632 s[n-1] = '\0';
2635 else
2636 #endif
2637 strncpy(s, ps_global->home_dir, n-1);
2638 s[n-1] = '\0';
2640 return(s);
2644 /*----------------------------------------------------------------------
2645 Call back for pico to tell us the window size's changed
2647 Args: none
2649 Returns: none (but pine's ttyo structure may have been updated)
2650 ----*/
2651 void
2652 resize_for_pico(void)
2654 fix_windsize(ps_global);
2658 PCOLORS *
2659 colors_for_pico(void)
2661 PCOLORS *colors = NULL;
2662 struct variable *vars = ps_global->vars;
2664 if (pico_usingcolor()){
2665 colors = (PCOLORS *)fs_get(sizeof(PCOLORS));
2667 colors->tbcp = current_titlebar_color();
2669 if (VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR){
2670 colors->klcp = new_color_pair(VAR_KEYLABEL_FORE_COLOR,
2671 VAR_KEYLABEL_BACK_COLOR);
2672 if (!pico_is_good_colorpair(colors->klcp))
2673 free_color_pair(&colors->klcp);
2675 else colors->klcp = NULL;
2677 if (colors->klcp && VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR){
2678 colors->kncp = new_color_pair(VAR_KEYNAME_FORE_COLOR,
2679 VAR_KEYNAME_BACK_COLOR);
2681 else colors->kncp = NULL;
2683 if (VAR_STATUS_FORE_COLOR && VAR_STATUS_BACK_COLOR){
2684 colors->stcp = new_color_pair(VAR_STATUS_FORE_COLOR,
2685 VAR_STATUS_BACK_COLOR);
2687 else colors->stcp = NULL;
2689 if (VAR_PROMPT_FORE_COLOR && VAR_PROMPT_BACK_COLOR){
2690 colors->prcp = new_color_pair(VAR_PROMPT_FORE_COLOR,
2691 VAR_PROMPT_BACK_COLOR);
2693 else colors->prcp = NULL;
2695 if (VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR){
2696 colors->qlcp = new_color_pair(VAR_QUOTE1_FORE_COLOR,
2697 VAR_QUOTE1_BACK_COLOR);
2699 else colors->qlcp = NULL;
2701 if (VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR){
2702 colors->qllcp = new_color_pair(VAR_QUOTE2_FORE_COLOR,
2703 VAR_QUOTE2_BACK_COLOR);
2705 else colors->qllcp = NULL;
2707 if (VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR){
2708 colors->qlllcp = new_color_pair(VAR_QUOTE3_FORE_COLOR,
2709 VAR_QUOTE3_BACK_COLOR);
2711 else colors->qlllcp = NULL;
2713 if (VAR_NORM_FORE_COLOR && VAR_NORM_BACK_COLOR){
2714 colors->ntcp = new_color_pair(VAR_NORM_FORE_COLOR,
2715 VAR_NORM_BACK_COLOR);
2717 else colors->ntcp = NULL;
2719 if (VAR_REV_FORE_COLOR && VAR_REV_BACK_COLOR){
2720 colors->rtcp = new_color_pair(VAR_REV_FORE_COLOR,
2721 VAR_REV_BACK_COLOR);
2723 else colors->rtcp = NULL;
2725 if (VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR){
2726 colors->sbcp = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
2727 VAR_SIGNATURE_BACK_COLOR);
2729 else colors->sbcp = NULL;
2732 return colors;
2736 void
2737 free_pcolors(PCOLORS **colors)
2739 if (*colors){
2740 if ((*colors)->tbcp)
2741 free_color_pair(&(*colors)->tbcp);
2742 if ((*colors)->kncp)
2743 free_color_pair(&(*colors)->kncp);
2744 if ((*colors)->klcp)
2745 free_color_pair(&(*colors)->klcp);
2746 if ((*colors)->stcp)
2747 free_color_pair(&(*colors)->stcp);
2748 if ((*colors)->prcp)
2749 free_color_pair(&(*colors)->prcp);
2750 if ((*colors)->qlcp)
2751 free_color_pair(&(*colors)->qlcp);
2752 if ((*colors)->qllcp)
2753 free_color_pair(&(*colors)->qllcp);
2754 if ((*colors)->qlllcp)
2755 free_color_pair(&(*colors)->qlllcp);
2756 if ((*colors)->ntcp)
2757 free_color_pair(&(*colors)->ntcp);
2758 if ((*colors)->rtcp)
2759 free_color_pair(&(*colors)->rtcp);
2760 if ((*colors)->sbcp)
2761 free_color_pair(&(*colors)->sbcp);
2762 fs_give((void **)colors);
2763 *colors = NULL;