1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
4 
5 /*
6  * ========================================================================
7  * Copyright 2013-2021 Eduardo Chappa
8  * Copyright 2006-2009 University of Washington
9  *
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
13  *
14  *     http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * ========================================================================
17  */
18 
19 /*======================================================================
20      mailcmd.c
21      The meat and pototoes of mail processing here:
22        - initial command processing and dispatch
23        - save message
24        - capture address off incoming mail
25        - jump to specific numbered message
26        - open (broach) a new folder
27        - search message headers (where is) command
28   ====*/
29 
30 
31 #include "headers.h"
32 #include "mailcmd.h"
33 #include "status.h"
34 #include "mailview.h"
35 #include "flagmaint.h"
36 #include "listsel.h"
37 #include "keymenu.h"
38 #include "alpine.h"
39 #include "mailpart.h"
40 #include "mailindx.h"
41 #include "folder.h"
42 #include "reply.h"
43 #include "help.h"
44 #include "titlebar.h"
45 #include "signal.h"
46 #include "radio.h"
47 #include "pipe.h"
48 #include "send.h"
49 #include "takeaddr.h"
50 #include "roleconf.h"
51 #include "smime.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
76 #ifdef _WINDOWS
77 #include "../pico/osdep/mswin.h"
78 #endif
79 
80 /*
81  * Internal Prototypes
82  */
83 int       cmd_flag(struct pine *, MSGNO_S *, int);
84 int	  cmd_flag_prompt(struct pine *, struct flag_screen *, int);
85 void      free_flag_table(struct flag_table **);
86 int       cmd_reply(struct pine *, MSGNO_S *, int, ACTION_S *);
87 int       cmd_forward(struct pine *, MSGNO_S *, int, ACTION_S *);
88 int       cmd_bounce(struct pine *, MSGNO_S *, int, ACTION_S *);
89 int       cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
90 void      role_compose(struct pine *);
91 int	  cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
92 int       cmd_export(struct pine *, MSGNO_S *, int, int);
93 char	 *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
94 char	 *cmd_delete_view(struct pine *, MSGNO_S *);
95 char	 *cmd_delete_index(struct pine *, MSGNO_S *);
96 long      get_level(int, UCS, SCROLL_S *);
97 long      closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
98 int	  update_folder_spec(char *, size_t, char *);
99 int       cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
100 int       cmd_pipe(struct pine *, MSGNO_S *, int);
101 STORE_S	 *list_mgmt_text(RFC2369_S *, long);
102 void	  list_mgmt_screen(STORE_S *);
103 int	  aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
104 int	  select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
105 int	  select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
106 int	  select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
107 int	  select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
108 int	  select_by_gm_content(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
109 int	  select_by_size(MAILSTREAM *, SEARCHSET **);
110 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
111 int	  select_by_status(MAILSTREAM *, SEARCHSET **);
112 int	  select_by_rule(MAILSTREAM *, SEARCHSET **);
113 int	  select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
114 char     *choose_a_rule(int);
115 int	  select_by_keyword(MAILSTREAM *, SEARCHSET **);
116 char     *choose_a_keyword(void);
117 int	  select_sort(struct pine *, int, SortOrder *, int *);
118 int       print_index(struct pine *, MSGNO_S *, int);
119 
120 /*
121  * List of Select options used by apply_* functions...
122  */
123 static char *sel_pmt1 = N_("ALTER message selection : ");
124 ESCKEY_S sel_opts1[] = {
125     /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
126        we will add more messages to the selection, Narrow selection means we will
127        remove some selections (like a logical AND instead of logical OR), and Flip
128        Selected means that all the messages that are currently selected become unselected,
129        and all the unselected messages become selected. */
130     {'a', 'a', "A", N_("unselect All")},
131     {'c', 'c', "C", NULL},
132     {'b', 'b', "B", N_("Broaden selctn")},
133     {'n', 'n', "N", N_("Narrow selctn")},
134     {'f', 'f', "F", N_("Flip selected")},
135     {'r', 'r', "R", N_("Replace selctn")},
136     {-1, 0, NULL, NULL}
137 };
138 
139 
140 #define SEL_OPTS_THREAD 9	/* index number of "tHread" */
141 #define SEL_OPTS_THREAD_CH 'h'
142 #define SEL_OPTS_XGMEXT 10	/* index number of "boX" */
143 #define SEL_OPTS_XGMEXT_CH 'g'
144 
145 char *sel_pmt2 = "SELECT criteria : ";
146 static ESCKEY_S sel_opts2[] = {
147     /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
148        means select the currently highlighted message; select by Number is by message
149        number; Status is by status of the message, for example the message might be
150        New or it might be Unseen or marked Important; Size has the Z upper case because
151        it is a Z command; Keyword is an alpine keyword that has been set by the user;
152        and Rule is an alpine rule */
153     {'a', 'a', "A", N_("select All")},
154     {'c', 'c', "C", N_("select Cur")},
155     {'n', 'n', "N", N_("Number")},
156     {'d', 'd', "D", N_("Date")},
157     {'t', 't', "T", N_("Text")},
158     {'s', 's', "S", N_("Status")},
159     {'z', 'z', "Z", N_("siZe")},
160     {'k', 'k', "K", N_("Keyword")},
161     {'r', 'r', "R", N_("Rule")},
162     {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
163     {-1, 0, NULL, NULL}
164 };
165 
166 
167 static ESCKEY_S sel_opts3[] = {
168     /* TRANSLATORS: these are operations we can do on a set of selected messages.
169        Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
170        the address book; Save means to save the messages into another alpine folder;
171        Export means to copy the messages to a file outside of alpine, external to
172        alpine's world. */
173     {'d', 'd',  "D", N_("Del")},
174     {'u', 'u',  "U", N_("Undel")},
175     {'r', 'r',  "R", N_("Reply")},
176     {'f', 'f',  "F", N_("Forward")},
177     {'%', '%',  "%", N_("Print")},
178     {'t', 't',  "T", N_("TakeAddr")},
179     {'s', 's',  "S", N_("Save")},
180     {'e', 'e',  "E", N_("Export")},
181     { -1,   0, NULL, NULL},
182     { -1,   0, NULL, NULL},
183     { -1,   0, NULL, NULL},
184     { -1,   0, NULL, NULL},
185     { -1,   0, NULL, NULL},
186     { -1,   0, NULL, NULL},
187     { -1,   0, NULL, NULL},
188     {-1,    0, NULL, NULL}
189 };
190 
191 static ESCKEY_S sel_opts4[] = {
192     {'a', 'a', "A", N_("select All")},
193     /* TRANSLATORS: select currently highlighted message Thread */
194     {'c', 'c', "C", N_("select Curthrd")},
195     {'n', 'n', "N", N_("Number")},
196     {'d', 'd', "D", N_("Date")},
197     {'t', 't', "T", N_("Text")},
198     {'s', 's', "S", N_("Status")},
199     {'z', 'z', "Z", N_("siZe")},
200     {'k', 'k', "K", N_("Keyword")},
201     {'r', 'r', "R", N_("Rule")},
202     {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
203     {-1, 0, NULL, NULL}
204 };
205 
206 static ESCKEY_S sel_opts5[] = {
207     /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
208        means select the currently highlighted message; select by Number is by message
209        number; Status is by status of the message, for example the message might be
210        New or it might be Unseen or marked Important; Size has the Z upper case because
211        it is a Z command; Keyword is an alpine keyword that has been set by the user;
212        and Rule is an alpine rule */
213     {'a', 'a', "A", N_("select All")},
214     {'c', 'c', "C", N_("select Cur")},
215     {'n', 'n', "N", N_("Number")},
216     {'d', 'd', "D", N_("Date")},
217     {'t', 't', "T", N_("Text")},
218     {'s', 's', "S", N_("Status")},
219     {'z', 'z', "Z", N_("siZe")},
220     {'k', 'k', "K", N_("Keyword")},
221     {'r', 'r', "R", N_("Rule")},
222     {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
223     {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")},
224     {-1, 0, NULL, NULL},
225     {-1, 0, NULL, NULL},
226     {-1, 0, NULL, NULL},
227     {-1, 0, NULL, NULL},
228     {-1, 0, NULL, NULL}
229 };
230 
231 static ESCKEY_S sel_opts6[] = {
232     {'a', 'a', "A", N_("select All")},
233     /* TRANSLATORS: select currently highlighted message Thread */
234     {'c', 'c', "C", N_("select Curthrd")},
235     {'n', 'n', "N", N_("Number")},
236     {'d', 'd', "D", N_("Date")},
237     {'t', 't', "T", N_("Text")},
238     {'s', 's', "S", N_("Status")},
239     {'z', 'z', "Z", N_("siZe")},
240     {'k', 'k', "K", N_("Keyword")},
241     {'r', 'r', "R", N_("Rule")},
242     {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
243     {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")},
244     {-1, 0, NULL, NULL},
245     {-1, 0, NULL, NULL},
246     {-1, 0, NULL, NULL},
247     {-1, 0, NULL, NULL},
248     {-1, 0, NULL, NULL}
249 };
250 
251 
252 static char *sel_flag =
253     N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
254 static char *sel_flag_not =
255     N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
256 static ESCKEY_S sel_flag_opt[] = {
257     /* TRANSLATORS: When selecting messages by message Status these are the
258        different types of Status you can select on. Is the message New, Recent,
259        and so on. Not means flip the meaning of the selection to the opposite
260        thing, so message is not New or not Important. */
261     {'n', 'n', "N", N_("New")},
262     {'*', '*', "*", N_("Important")},
263     {'d', 'd', "D", N_("Deleted")},
264     {'a', 'a', "A", N_("Answered")},
265     {'f', 'f', "F", N_("Forwarded")},
266     {-2, 0, NULL, NULL},
267     {'!', '!', "!", N_("Not")},
268     {-2, 0, NULL, NULL},
269     {'r', 'r', "R", N_("Recent")},
270     {'u', 'u', "U", N_("Unseen")},
271     {-1, 0, NULL, NULL}
272 };
273 
274 
275 static ESCKEY_S sel_date_opt[] = {
276     {0, 0, NULL, NULL},
277     /* TRANSLATORS: options when selecting messages by Date */
278     {ctrl('P'), 12, "^P", N_("Prev Day")},
279     {ctrl('N'), 13, "^N", N_("Next Day")},
280     {ctrl('X'), 11, "^X", N_("Cur Msg")},
281     {ctrl('W'), 14, "^W", N_("Toggle When")},
282     {KEY_UP,    12, "", ""},
283     {KEY_DOWN,  13, "", ""},
284     {-1, 0, NULL, NULL}
285 };
286 
287 
288 static char *sel_x_gm_ext =
289     N_("Search: ");
290 static char *sel_text =
291     N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
292 static char *sel_text_not =
293     N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
294 static ESCKEY_S sel_text_opt[] = {
295     /* TRANSLATORS: Select messages based on the text contained in the From line, or
296        the Subject line, and so on. */
297     {'f', 'f', "F", N_("From")},
298     {'s', 's', "S", N_("Subject")},
299     {'t', 't', "T", N_("To")},
300     {'a', 'a', "A", N_("All Text")},
301     {'c', 'c', "C", N_("Cc")},
302     {'!', '!', "!", N_("Not")},
303     {'r', 'r', "R", N_("Recipient")},
304     {'p', 'p', "P", N_("Participant")},
305     {'b', 'b', "B", N_("Body")},
306     {'h', 'h', "H", N_("Header")},
307     {-1, 0, NULL, NULL}
308 };
309 
310 static ESCKEY_S choose_action[] = {
311     {'c', 'c', "C", N_("Compose")},
312     {'r', 'r', "R", N_("Reply")},
313     {'f', 'f', "F", N_("Forward")},
314     {'b', 'b', "B", N_("Bounce")},
315     {-1, 0, NULL, NULL}
316 };
317 
318 static char *select_num =
319   N_("Enter comma-delimited list of numbers (dash between ranges): ");
320 
321 static char *select_size_larger_msg =
322   N_("Select messages with size larger than: ");
323 
324 static char *select_size_smaller_msg =
325   N_("Select messages with size smaller than: ");
326 
327 static char *sel_size_larger  = N_("Larger");
328 static char *sel_size_smaller = N_("Smaller");
329 static ESCKEY_S sel_size_opt[] = {
330     {0, 0, NULL, NULL},
331     {ctrl('W'), 14, "^W", NULL},
332     {-1, 0, NULL, NULL}
333 };
334 
335 static ESCKEY_S sel_key_opt[] = {
336     {0, 0, NULL, NULL},
337     {ctrl('T'), 14, "^T", N_("To List")},
338     {0, 0, NULL, NULL},
339     {'!', '!', "!", N_("Not")},
340     {-1, 0, NULL, NULL}
341 };
342 
343 static ESCKEY_S flag_text_opt[] = {
344     /* TRANSLATORS: these are types of flags (markers) that the user can
345        set. For example, they can flag the message as an important message. */
346     {'n', 'n', "N", N_("New")},
347     {'*', '*', "*", N_("Important")},
348     {'d', 'd', "D", N_("Deleted")},
349     {'a', 'a', "A", N_("Answered")},
350     {'f', 'f', "F", N_("Forwarded")},
351     {'!', '!', "!", N_("Not")},
352     {ctrl('T'), 10, "^T", N_("To Flag Details")},
353     {-1, 0, NULL, NULL}
354 };
355 
356 int
alpine_smime_confirm_save(char * email)357 alpine_smime_confirm_save(char *email)
358 {
359   char prompt[128];
360 
361   snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
362 		email ? email : _("missing address"));
363   return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
364 }
365 
366 int
alpine_get_password(char * prompt,char * pass,size_t len)367 alpine_get_password(char *prompt, char *pass, size_t len)
368 {
369   int flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
370   flags |= OE_DISALLOW_HELP;
371   pass[0] = '\0';
372   return optionally_enter(pass,
373 			-(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
374                          0, len, prompt, NULL, NO_HELP, &flags);
375 }
376 
377 int
smime_import_certificate(char * filename,char * full_filename,char * what,size_t len)378 smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
379 {
380    int   r = 1;
381    static HISTORY_S *history = NULL;
382    static ESCKEY_S eopts[] = {
383 	{ctrl('T'), 10, "^T", N_("To Files")},
384 	{-1, 0, NULL, NULL},
385 	{-1, 0, NULL, NULL}};
386 
387    if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
388       eopts[r].ch    =  ctrl('I');
389       eopts[r].rval  = 11;
390       eopts[r].name  = "TAB";
391       eopts[r].label = N_("Complete");
392    }
393 
394    eopts[++r].ch = -1;
395 
396    filename[0] = '\0';
397    full_filename[0] = '\0';
398 
399    r = get_export_filename(ps_global, filename, NULL, full_filename,
400               len, what, "IMPORT", eopts, NULL,
401               -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
402 
403    return r;
404 }
405 
406 
407 /*----------------------------------------------------------------------
408          The giant switch on the commands for index and viewing
409 
410   Input:  command  -- The command char/code
411           in_index -- flag indicating command is from index
412           orig_command -- The original command typed before pre-processing
413   Output: force_mailchk -- Set to tell caller to force call to new_mail().
414 
415   Result: Manifold
416 
417           Returns 1 if the message number or attachment to show changed
418  ---*/
419 int
process_cmd(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,int command,CmdWhere in_index,int * force_mailchk)420 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
421 	    int command, CmdWhere in_index, int *force_mailchk)
422 {
423     int           question_line, a_changed, flags = 0, ret, j;
424     int           notrealinbox;
425     long          new_msgno, del_count, old_msgno, i;
426     long          start;
427     char         *newfolder, prompt[MAX_SCREEN_COLS+1];
428     CONTEXT_S    *tc;
429     MESSAGECACHE *mc;
430 #if	defined(DOS) && !defined(_WINDOWS)
431     extern long coreleft();
432 #endif
433 
434     dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
435 
436     question_line         = -FOOTER_ROWS(state);
437     state->mangled_screen = 0;
438     state->mangled_footer = 0;
439     state->mangled_header = 0;
440     state->next_screen    = SCREEN_FUN_NULL;
441     old_msgno             = mn_get_cur(msgmap);
442     a_changed             = FALSE;
443     *force_mailchk        = 0;
444 
445     switch (command) {
446 	/*------------- Help --------*/
447       case MC_HELP :
448 	/*
449 	 * We're not using the h_mail_view portion of this right now because
450 	 * that call is being handled in scrolltool() before it gets
451 	 * here.  Leave it in case we change how it works.
452 	 */
453 	helper((in_index == MsgIndx)
454 		 ? h_mail_index
455 		 : (in_index == View)
456 		   ? h_mail_view
457 		   : h_mail_thread_index,
458 	       (in_index == MsgIndx)
459 	         ? _("HELP FOR MESSAGE INDEX")
460 		 : (in_index == View)
461 		   ? _("HELP FOR MESSAGE TEXT")
462 		   : _("HELP FOR THREAD INDEX"),
463 	       HLPD_NONE);
464 	dprint((4,"MAIL_CMD: did help command\n"));
465 	state->mangled_screen = 1;
466 	break;
467 
468 
469           /*--------- Return to main menu ------------*/
470       case MC_MAIN :
471 	state->next_screen = main_menu_screen;
472 	dprint((2,"MAIL_CMD: going back to main menu\n"));
473 	break;
474 
475 
476           /*------- View message text --------*/
477       case MC_VIEW_TEXT :
478 view_text:
479 	if(any_messages(msgmap, NULL, "to View")){
480 	    state->next_screen = mail_view_screen;
481 	}
482 
483 	break;
484 
485 
486           /*------- View attachment --------*/
487       case MC_VIEW_ATCH :
488 	state->next_screen = attachment_screen;
489 	dprint((2,"MAIL_CMD: going to attachment screen\n"));
490 	break;
491 
492 
493           /*---------- Previous message ----------*/
494       case MC_PREVITEM :
495 	if(any_messages(msgmap, NULL, NULL)){
496 	    if((i = mn_get_cur(msgmap)) > 1L){
497 		mn_dec_cur(stream, msgmap,
498 			   (in_index == View && THREADING()
499 			    && sp_viewing_a_thread(stream))
500 			     ? MH_THISTHD
501 			     : (in_index == View)
502 			       ? MH_ANYTHD : MH_NONE);
503 		if(i == mn_get_cur(msgmap)){
504 		    PINETHRD_S *thrd = NULL, *topthrd = NULL;
505 
506 		    if(THRD_INDX_ENABLED()){
507 			mn_dec_cur(stream, msgmap, MH_ANYTHD);
508 			if(i == mn_get_cur(msgmap))
509 			  q_status_message1(SM_ORDER, 0, 2,
510 				      _("Already on first %s in Zoomed Index"),
511 				      THRD_INDX() ? _("thread") : _("message"));
512 			else{
513 			    if(in_index == View
514 			       || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
515 			      ret = 'y';
516 			    else
517 			      ret = want_to(_("View previous thread"), 'y', 'x',
518 					    NO_HELP, WT_NORM);
519 
520 			    if(ret == 'y'){
521 				q_status_message(SM_ORDER, 0, 2,
522 						 _("Viewing previous thread"));
523 				new_msgno = mn_get_cur(msgmap);
524 				mn_set_cur(msgmap, i);
525 				if(unview_thread(state, stream, msgmap)){
526 				    state->next_screen = mail_index_screen;
527 				    state->view_skipped_index = 0;
528 				    state->mangled_screen = 1;
529 				}
530 
531 				mn_set_cur(msgmap, new_msgno);
532 				if(THRD_AUTO_VIEW() && in_index == View){
533 
534 				    thrd = fetch_thread(stream,
535 							mn_m2raw(msgmap,
536 								 new_msgno));
537 				    if(count_lflags_in_thread(stream, thrd,
538 							      msgmap,
539 							      MN_NONE) == 1){
540 					if(view_thread(state, stream, msgmap, 1)){
541 					    if(current_index_state)
542 					      msgmap->top_after_thrd = current_index_state->msg_at_top;
543 
544 					    state->view_skipped_index = 1;
545 					    command = MC_VIEW_TEXT;
546 					    goto view_text;
547 					}
548 				    }
549 				}
550 
551 				j = 0;
552 				if(THRD_AUTO_VIEW() && in_index != View){
553 				    thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
554 				    if(thrd && thrd->top)
555 				      topthrd = fetch_thread(stream, thrd->top);
556 
557 				    if(topthrd)
558 				      j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
559 				}
560 
561 				if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
562 				    if(view_thread(state, stream, msgmap, 1)
563 				       && current_index_state)
564 				      msgmap->top_after_thrd = current_index_state->msg_at_top;
565 
566 				}
567 
568 				state->next_screen = SCREEN_FUN_NULL;
569 			    }
570 			    else
571 			      mn_set_cur(msgmap, i);	/* put it back */
572 			}
573 		    }
574 		    else
575 		      q_status_message1(SM_ORDER, 0, 2,
576 				  _("Already on first %s in Zoomed Index"),
577 				  THRD_INDX() ? _("thread") : _("message"));
578 		}
579 	    }
580 	    else{
581 		time_t now;
582 
583 		if(!IS_NEWS(stream)
584 		   && ((now = time(0)) > state->last_nextitem_forcechk)){
585 		    *force_mailchk = 1;
586 		    /* check at most once a second */
587 		    state->last_nextitem_forcechk = now;
588 		}
589 
590 		q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
591 				  THRD_INDX() ? _("thread") : _("message"));
592 	    }
593 	}
594 
595 	break;
596 
597 
598           /*---------- Next Message ----------*/
599       case MC_NEXTITEM :
600 	if(mn_get_total(msgmap) > 0L
601 	   && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
602 	    mn_inc_cur(stream, msgmap,
603 		       (in_index == View && THREADING()
604 		        && sp_viewing_a_thread(stream))
605 			 ? MH_THISTHD
606 			 : (in_index == View)
607 			   ? MH_ANYTHD : MH_NONE);
608 	    if(i == mn_get_cur(msgmap)){
609 		PINETHRD_S *thrd, *topthrd;
610 
611 		if(THRD_INDX_ENABLED()){
612 		    if(!THRD_INDX())
613 		      mn_inc_cur(stream, msgmap, MH_ANYTHD);
614 
615 		    if(i == mn_get_cur(msgmap)){
616 			if(any_lflagged(msgmap, MN_HIDE))
617 			  any_messages(NULL, "more", "in Zoomed Index");
618 			else
619 			  goto nfolder;
620 		    }
621 		    else{
622 			if(in_index == View
623 			   || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
624 			  ret = 'y';
625 			else
626 			  ret = want_to(_("View next thread"), 'y', 'x',
627 					NO_HELP, WT_NORM);
628 
629 			if(ret == 'y'){
630 			    q_status_message(SM_ORDER, 0, 2,
631 					     _("Viewing next thread"));
632 			    new_msgno = mn_get_cur(msgmap);
633 			    mn_set_cur(msgmap, i);
634 			    if(unview_thread(state, stream, msgmap)){
635 				state->next_screen = mail_index_screen;
636 				state->view_skipped_index = 0;
637 				state->mangled_screen = 1;
638 			    }
639 
640 			    mn_set_cur(msgmap, new_msgno);
641 			    if(THRD_AUTO_VIEW() && in_index == View){
642 
643 				thrd = fetch_thread(stream,
644 						    mn_m2raw(msgmap,
645 							     new_msgno));
646 				if(count_lflags_in_thread(stream, thrd,
647 							  msgmap,
648 							  MN_NONE) == 1){
649 				    if(view_thread(state, stream, msgmap, 1)){
650 					if(current_index_state)
651 					  msgmap->top_after_thrd = current_index_state->msg_at_top;
652 
653 					state->view_skipped_index = 1;
654 					command = MC_VIEW_TEXT;
655 					goto view_text;
656 				    }
657 				}
658 			    }
659 
660 			    j = 0;
661 			    if(THRD_AUTO_VIEW() && in_index != View){
662 				thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
663 				if(thrd && thrd->top)
664 				  topthrd = fetch_thread(stream, thrd->top);
665 				else
666 				  topthrd = NULL;
667 
668 				if(topthrd)
669 				j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
670 			    }
671 
672 			    if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
673 				if(view_thread(state, stream, msgmap, 1)
674 				   && current_index_state)
675 				  msgmap->top_after_thrd = current_index_state->msg_at_top;
676 
677 			    }
678 
679 			    state->next_screen = SCREEN_FUN_NULL;
680 			}
681 			else
682 			  mn_set_cur(msgmap, i);	/* put it back */
683 		    }
684 		}
685 		else if(THREADING()
686 			&& (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
687 			&& thrd->next
688 			&& get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
689 		       q_status_message(SM_ORDER, 0, 2,
690 			       _("Expand collapsed thread to see more messages"));
691 		}
692 		else
693 		  any_messages(NULL, "more", "in Zoomed Index");
694 	    }
695 	}
696 	else{
697 	    time_t now;
698 nfolder:
699 	    prompt[0] = '\0';
700 	    if(IS_NEWS(stream)
701 	       || (state->context_current->use & CNTXT_INCMNG)){
702 		char nextfolder[MAXPATH];
703 
704 		strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
705 		nextfolder[sizeof(nextfolder)-1] = '\0';
706 		if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
707 			       state->context_current, NULL, NULL))
708 		  strncpy(prompt, _(".  Press TAB for next folder."),
709 			  sizeof(prompt));
710 		else
711 		  strncpy(prompt, _(".  No more folders to TAB to."),
712 			  sizeof(prompt));
713 
714 		prompt[sizeof(prompt)-1] = '\0';
715 	    }
716 
717 	    any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
718 			 prompt[0] ? prompt : NULL);
719 
720 	    if(!IS_NEWS(stream)
721 	       && ((now = time(0)) > state->last_nextitem_forcechk)){
722 		*force_mailchk = 1;
723 		/* check at most once a second */
724 		state->last_nextitem_forcechk = now;
725 	    }
726 	}
727 
728 	break;
729 
730 
731           /*---------- Delete message ----------*/
732       case MC_DELETE :
733 	(void) cmd_delete(state, msgmap, MCMD_NONE,
734 			  (in_index == View) ? cmd_delete_view : cmd_delete_index);
735 	break;
736 
737 
738           /*---------- Undelete message ----------*/
739       case MC_UNDELETE :
740 	(void) cmd_undelete(state, msgmap, MCMD_NONE);
741 	update_titlebar_status();
742 	break;
743 
744 
745           /*---------- Reply to message ----------*/
746       case MC_REPLY :
747 	(void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
748 	break;
749 
750 
751           /*---------- Forward message ----------*/
752       case MC_FORWARD :
753 	(void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
754 	break;
755 
756 
757           /*---------- Quit pine ------------*/
758       case MC_QUIT :
759 	state->next_screen = quit_screen;
760 	dprint((1,"MAIL_CMD: quit\n"));
761 	break;
762 
763 
764           /*---------- Compose message ----------*/
765       case MC_COMPOSE :
766 	state->prev_screen = (in_index == View) ? mail_view_screen
767 						: mail_index_screen;
768 	compose_screen(state);
769 	state->mangled_screen = 1;
770 	if (state->next_screen)
771 	  a_changed = TRUE;
772 	break;
773 
774 
775           /*---------- Alt Compose message ----------*/
776       case MC_ROLE :
777 	state->prev_screen = (in_index == View) ? mail_view_screen
778 						: mail_index_screen;
779 	role_compose(state);
780 	if(state->next_screen)
781 	  a_changed = TRUE;
782 
783 	break;
784 
785 
786           /*--------- Folders menu ------------*/
787       case MC_FOLDERS :
788 	state->start_in_context = 1;
789 
790           /*--------- Top of Folders list menu ------------*/
791       case MC_COLLECTIONS :
792 	state->next_screen = folder_screen;
793 	dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
794 	break;
795 
796 
797           /*---------- Open specific new folder ----------*/
798       case MC_GOTO :
799 	tc = (state->context_last && !NEWS_TEST(state->context_current))
800 	       ? state->context_last : state->context_current;
801 
802 	newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
803 	if(newfolder){
804 	    visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
805 	    a_changed = TRUE;
806 	}
807 
808 	break;
809 
810 
811           /*------- Go to Index Screen ----------*/
812       case MC_INDEX :
813 	state->next_screen = mail_index_screen;
814 	break;
815 
816           /*------- Skip to next interesting message -----------*/
817       case MC_TAB :
818 	if(THRD_INDX()){
819 	    PINETHRD_S *thrd;
820 
821 	    /*
822 	     * If we're in the thread index, start looking after this
823 	     * thread. We don't want to match something in the current
824 	     * thread.
825 	     */
826 	    start = mn_get_cur(msgmap);
827 	    thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
828 	    if(mn_get_revsort(msgmap)){
829 		/* if reversed, top of thread is last one before next thread */
830 		if(thrd && thrd->top)
831 		  start = mn_raw2m(msgmap, thrd->top);
832 	    }
833 	    else{
834 		/* last msg of thread is at the ends of the branches/nexts */
835 		while(thrd){
836 		    start = mn_raw2m(msgmap, thrd->rawno);
837 		    if(thrd->branch)
838 		      thrd = fetch_thread(stream, thrd->branch);
839 		    else if(thrd->next)
840 		      thrd = fetch_thread(stream, thrd->next);
841 		    else
842 		      thrd = NULL;
843 		}
844 	    }
845 
846 	    /*
847 	     * Flags is 0 in this case because we want to not skip
848 	     * messages inside of threads so that we can find threads
849 	     * which have some unseen messages even though the top-level
850 	     * of the thread is already seen.
851 	     * If new_msgno ends up being a message which is not visible
852 	     * because it isn't at the top-level, the current message #
853 	     * will be adjusted below in adjust_cur.
854 	     */
855 	    flags = 0;
856 	    new_msgno = next_sorted_flagged((F_UNDEL
857 					     | F_UNSEEN
858 					     | ((F_ON(F_TAB_TO_NEW,state))
859 						 ? 0 : F_OR_FLAG)),
860 					    stream, start, &flags);
861 	}
862 	else if(THREADING() && sp_viewing_a_thread(stream)){
863 	    PINETHRD_S *thrd, *topthrd = NULL;
864 
865 	    start = mn_get_cur(msgmap);
866 
867 	    /*
868 	     * Things are especially complicated when we're viewing_a_thread
869 	     * from the thread index. First we have to check within the
870 	     * current thread for a new message. If none is found, then
871 	     * we search in the next threads and offer to continue in
872 	     * them. Then we offer to go to the next folder.
873 	     */
874 	    flags = NSF_SKIP_CHID;
875 	    new_msgno = next_sorted_flagged((F_UNDEL
876 					     | F_UNSEEN
877 					     | ((F_ON(F_TAB_TO_NEW,state))
878 					       ? 0 : F_OR_FLAG)),
879 					    stream, start, &flags);
880 	    /*
881 	     * If we found a match then we are done, that is another message
882 	     * in the current thread index. Otherwise, we have to look
883 	     * further.
884 	     */
885 	    if(!(flags & NSF_FLAG_MATCH)){
886 		ret = 'n';
887 		while(1){
888 
889 		    flags = 0;
890 		    new_msgno = next_sorted_flagged((F_UNDEL
891 						     | F_UNSEEN
892 						     | ((F_ON(F_TAB_TO_NEW,
893 							      state))
894 							 ? 0 : F_OR_FLAG)),
895 						    stream, start, &flags);
896 		    /*
897 		     * If we got a match, new_msgno is a message in
898 		     * a different thread from the one we are viewing.
899 		     */
900 		    if(flags & NSF_FLAG_MATCH){
901 			thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
902 			if(thrd && thrd->top)
903 			  topthrd = fetch_thread(stream, thrd->top);
904 
905 			if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
906 			    static ESCKEY_S next_opt[] = {
907 				{'y', 'y', "Y", N_("Yes")},
908 				{'n', 'n', "N", N_("No")},
909 				{TAB, 'n', "Tab", N_("NextNew")},
910 				{-1, 0, NULL, NULL}
911 			    };
912 
913 			    if(in_index)
914 			      snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
915 				     topthrd ? comatose(topthrd->thrdno) : "?");
916 			    else
917 			      snprintf(prompt, sizeof(prompt),
918 				     _("View message in thread number %s? "),
919 				     topthrd ? comatose(topthrd->thrdno) : "?");
920 
921 			    prompt[sizeof(prompt)-1] = '\0';
922 
923 			    ret = radio_buttons(prompt, -FOOTER_ROWS(state),
924 						next_opt, 'y', 'x', NO_HELP,
925 						RB_NORM);
926 			    if(ret == 'x'){
927 				cmd_cancelled(NULL);
928 				goto get_out;
929 			    }
930 			}
931 			else
932 			  ret = 'y';
933 
934 			if(ret == 'y'){
935 			    if(unview_thread(state, stream, msgmap)){
936 				state->next_screen = mail_index_screen;
937 				state->view_skipped_index = 0;
938 				state->mangled_screen = 1;
939 			    }
940 
941 			    mn_set_cur(msgmap, new_msgno);
942 			    if(THRD_AUTO_VIEW()){
943 
944 				if(count_lflags_in_thread(stream, topthrd,
945 				                         msgmap, MN_NONE) == 1){
946 				    if(view_thread(state, stream, msgmap, 1)){
947 					if(current_index_state)
948 					  msgmap->top_after_thrd = current_index_state->msg_at_top;
949 
950 					state->view_skipped_index = 1;
951 					command = MC_VIEW_TEXT;
952 					goto view_text;
953 				    }
954 				}
955 			    }
956 
957 			    if(view_thread(state, stream, msgmap, 1) && current_index_state)
958 			      msgmap->top_after_thrd = current_index_state->msg_at_top;
959 
960 			    state->next_screen = SCREEN_FUN_NULL;
961 			    break;
962 			}
963 			else if(ret == 'n' && topthrd){
964 			    /*
965 			     * skip to end of this thread and look starting
966 			     * in the next thread.
967 			     */
968 			    if(mn_get_revsort(msgmap)){
969 				/*
970 				 * if reversed, top of thread is last one
971 				 * before next thread
972 				 */
973 				start = mn_raw2m(msgmap, topthrd->rawno);
974 			    }
975 			    else{
976 				/*
977 				 * last msg of thread is at the ends of
978 				 * the branches/nexts
979 				 */
980 				thrd = topthrd;
981 				while(thrd){
982 				    start = mn_raw2m(msgmap, thrd->rawno);
983 				    if(thrd->branch)
984 				      thrd = fetch_thread(stream, thrd->branch);
985 				    else if(thrd->next)
986 				      thrd = fetch_thread(stream, thrd->next);
987 				    else
988 				      thrd = NULL;
989 				}
990 			    }
991 			}
992 			else if(ret == 'n')
993 			  break;
994 		    }
995 		    else
996 		      break;
997 		}
998 	    }
999 	}
1000 	else{
1001 
1002 	    start = mn_get_cur(msgmap);
1003 
1004 	    /*
1005 	     * If we are on a collapsed thread, start looking after the
1006 	     * collapsed part, unless we are viewing the message.
1007 	     */
1008 	    if(THREADING() && in_index != View){
1009 		PINETHRD_S *thrd;
1010 		long        rawno;
1011 		int         collapsed;
1012 
1013 		rawno = mn_m2raw(msgmap, start);
1014 		thrd = fetch_thread(stream, rawno);
1015 		collapsed = thrd && thrd->next
1016 			    && get_lflag(stream, NULL, rawno, MN_COLL);
1017 
1018 		if(collapsed){
1019 		    if(mn_get_revsort(msgmap)){
1020 			if(thrd && thrd->top)
1021 			  start = mn_raw2m(msgmap, thrd->top);
1022 		    }
1023 		    else{
1024 			while(thrd){
1025 			    start = mn_raw2m(msgmap, thrd->rawno);
1026 			    if(thrd->branch)
1027 			      thrd = fetch_thread(stream, thrd->branch);
1028 			    else if(thrd->next)
1029 			      thrd = fetch_thread(stream, thrd->next);
1030 			    else
1031 			      thrd = NULL;
1032 			}
1033 		    }
1034 
1035 		}
1036 	    }
1037 
1038 	    new_msgno = next_sorted_flagged((F_UNDEL
1039 					     | F_UNSEEN
1040 					     | ((F_ON(F_TAB_TO_NEW,state))
1041 						 ? 0 : F_OR_FLAG)),
1042 					    stream, start, &flags);
1043 	}
1044 
1045 	/*
1046 	 * If there weren't any unread messages left, OR there
1047 	 * aren't any messages at all, we may want to offer to
1048 	 * go on to the next folder...
1049 	 */
1050 	if(flags & NSF_FLAG_MATCH){
1051 	    mn_set_cur(msgmap, new_msgno);
1052 	    if(in_index != View)
1053 	      adjust_cur_to_visible(stream, msgmap);
1054 	}
1055 	else{
1056 	    int in_inbox = sp_flagged(stream, SP_INBOX);
1057 
1058 	    if(state->context_current
1059 	       && ((NEWS_TEST(state->context_current)
1060 		    && context_isambig(state->cur_folder))
1061 		   || ((state->context_current->use & CNTXT_INCMNG)
1062 		       && (in_inbox
1063 			   || folder_index(state->cur_folder,
1064 					   state->context_current,
1065 					   FI_FOLDER) >= 0)))){
1066 		char	    nextfolder[MAXPATH];
1067 		MAILSTREAM *nextstream = NULL;
1068 		long	    recent_cnt;
1069 		int         did_cancel = 0;
1070 
1071 		strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1072 		nextfolder[sizeof(nextfolder)-1] = '\0';
1073 		while(1){
1074 		    if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1075 				     state->context_current, &recent_cnt,
1076 				     F_ON(F_TAB_NO_CONFIRM,state)
1077 				       ? NULL : &did_cancel))){
1078 			if(!in_inbox){
1079 			    static ESCKEY_S inbox_opt[] = {
1080 				{'y', 'y', "Y", N_("Yes")},
1081 				{'n', 'n', "N", N_("No")},
1082 				{TAB, 'z', "Tab", N_("To Inbox")},
1083 				{-1, 0, NULL, NULL}
1084 			    };
1085 
1086 			    if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1087 			      ret = 'y';
1088 			    else{
1089 				/* TRANSLATORS: this is a question, with some information followed
1090 				   by Return to INBOX? */
1091 				if(state->context_current->use&CNTXT_INCMNG)
1092 				  snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1093 				else
1094 				  snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1095 
1096 				ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1097 						    inbox_opt, 'y', 'x',
1098 						    NO_HELP, RB_NORM);
1099 			    }
1100 
1101 			    /*
1102 			     * 'z' is a synonym for 'y'.  It is not 'y'
1103 			     * so that it isn't displayed as a default
1104 			     * action with square-brackets around it
1105 			     * in the keymenu...
1106 			     */
1107 			    if(ret == 'y' || ret == 'z'){
1108 				visit_folder(state, state->inbox_name,
1109 					     state->context_current,
1110 					     NULL, DB_INBOXWOCNTXT);
1111 				a_changed = TRUE;
1112 			    }
1113 			}
1114 			else if (did_cancel)
1115 			  cmd_cancelled(NULL);
1116 			else{
1117 			  if(state->context_current->use&CNTXT_INCMNG)
1118 			    q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1119 			  else
1120 			    q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1121 			}
1122 
1123 			break;
1124 		    }
1125 
1126 		    {
1127 #define CNTLEN 80
1128 		     char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1129 		     int rbspace, avail, need, take_back;
1130 
1131 			/*
1132 			 * View_next_
1133 			 * Incoming_folder_ or news_group_ or folder_ or group_
1134 			 * "foldername"
1135 			 * _(13 recent) or _(some recent) or nothing
1136 			 * ?_
1137 			 */
1138 			front = "View next";
1139 			strncpy(type,
1140 				(state->context_current->use & CNTXT_INCMNG)
1141 				    ? "Incoming folder" : "news group",
1142 				sizeof(type));
1143 			type[sizeof(type)-1] = '\0';
1144 			snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1145 				recent_cnt ? long2string(recent_cnt) : "some",
1146 				F_ON(F_TAB_USES_UNSEEN, ps_global)
1147 				    ? "unseen" : "recent");
1148 			cnt[sizeof(cnt)-1] = '\0';
1149 
1150 			/*
1151 			 * Space reserved for radio_buttons call.
1152 			 * If we make this 3 then radio_buttons won't mess
1153 			 * with the prompt. If we make it 2, then we get
1154 			 * one more character to use but radio_buttons will
1155 			 * cut off the last character of our prompt, which is
1156 			 * ok because it is a space.
1157 			 */
1158 			rbspace = 2;
1159 			avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1160 						: 80;
1161 			need = strlen(front)+1 + strlen(type)+1 +
1162 			       + strlen(nextfolder)+2 + strlen(cnt) +
1163 			       2 + rbspace;
1164 			if(avail < need){
1165 			    take_back = strlen(type);
1166 			    strncpy(type,
1167 				    (state->context_current->use & CNTXT_INCMNG)
1168 					? "folder" : "group", sizeof(type));
1169 			    take_back -= strlen(type);
1170 			    need -= take_back;
1171 			    if(avail < need){
1172 				need -= strlen(cnt);
1173 				cnt[0] = '\0';
1174 			    }
1175 			}
1176 						/* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1177 			snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1178 				(MAX_SCREEN_COLS+1)/8, front,
1179 				(MAX_SCREEN_COLS+1)/8, type,
1180 				(MAX_SCREEN_COLS+1)/2,
1181 				short_str(nextfolder, fbuf, sizeof(fbuf),
1182 					  strlen(nextfolder) -
1183 					    ((need>avail) ? (need-avail) : 0),
1184 					  MidDots),
1185 				(MAX_SCREEN_COLS+1)/8, cnt);
1186 			prompt[sizeof(prompt)-1] = '\0';
1187 		    }
1188 
1189 		    /*
1190 		     * When help gets added, this'll have to become
1191 		     * a loop like the rest...
1192 		     */
1193 		    if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1194 			static ESCKEY_S next_opt[] = {
1195 			    {'y', 'y', "Y", N_("Yes")},
1196 			    {'n', 'n', "N", N_("No")},
1197 			    {TAB, 'n', "Tab", N_("NextNew")},
1198 			    {-1, 0, NULL, NULL}
1199 			};
1200 
1201 			ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1202 					    next_opt, 'y', 'x', NO_HELP,
1203 					    RB_NORM);
1204 			if(ret == 'x'){
1205 			    cmd_cancelled(NULL);
1206 			    break;
1207 			}
1208 		    }
1209 		    else
1210 		      ret = 'y';
1211 
1212 		    if(ret == 'y'){
1213 			if(nextstream && sp_dead_stream(nextstream))
1214 			  nextstream = NULL;
1215 
1216 			visit_folder(state, nextfolder,
1217 				     state->context_current, nextstream,
1218 				     DB_FROMTAB);
1219 			/* visit_folder takes care of nextstream */
1220 			nextstream = NULL;
1221 			a_changed = TRUE;
1222 			break;
1223 		    }
1224 		}
1225 
1226 		if(nextstream)
1227 		  pine_mail_close(nextstream);
1228 	    }
1229 	    else
1230 	      any_messages(NULL,
1231 			   (mn_get_total(msgmap) > 0L)
1232 			     ? IS_NEWS(stream) ? "more undeleted" : "more new"
1233 			     : NULL,
1234 			   NULL);
1235 	}
1236 
1237 get_out:
1238 
1239 	break;
1240 
1241 
1242           /*------- Zoom -----------*/
1243       case MC_ZOOM :
1244 	/*
1245 	 * Right now the way zoom is implemented is sort of silly.
1246 	 * There are two per-message flags where just one and a
1247 	 * global "zoom mode" flag to suppress messages from the index
1248 	 * should suffice.
1249 	 */
1250 	if(any_messages(msgmap, NULL, "to Zoom on")){
1251 	    if(unzoom_index(state, stream, msgmap)){
1252 		dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1253 		q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1254 	    }
1255 	    else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1256 		if(any_lflagged(msgmap, MN_HIDE)){
1257 		    dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1258 		    q_status_message4(SM_ORDER, 0, 2,
1259 				      _("In Zoomed Index of %s%s%s%s.  Use \"Z\" to restore regular Index"),
1260 				      THRD_INDX() ? "" : comatose(i),
1261 				      THRD_INDX() ? "" : " ",
1262 				      THRD_INDX() ? _("threads") : _("message"),
1263 				      THRD_INDX() ? "" : plural(i));
1264 		}
1265 		else
1266 		  q_status_message(SM_ORDER, 0, 2,
1267 		     _("All messages selected, so not entering Index Zoom Mode"));
1268 	    }
1269 	    else
1270 	      any_messages(NULL, "selected", "to Zoom on");
1271 	}
1272 
1273 	break;
1274 
1275 
1276           /*---------- print message on paper ----------*/
1277       case MC_PRINTMSG :
1278 	if(any_messages(msgmap, NULL, "to print"))
1279 	  (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1280 
1281 	break;
1282 
1283 
1284           /*---------- Take Address ----------*/
1285       case MC_TAKE :
1286 	if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1287 	   any_messages(msgmap, NULL, "to Take address from"))
1288 	  (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1289 
1290 	break;
1291 
1292 
1293           /*---------- Save Message ----------*/
1294       case MC_SAVE :
1295 	if(any_messages(msgmap, NULL, "to Save"))
1296 	  (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1297 
1298 	break;
1299 
1300 
1301           /*---------- Export message ----------*/
1302       case MC_EXPORT :
1303 	if(any_messages(msgmap, NULL, "to Export")){
1304 	    (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1305 	    state->mangled_footer = 1;
1306 	}
1307 
1308 	break;
1309 
1310 
1311           /*---------- Expunge ----------*/
1312       case MC_EXPUNGE :
1313 	(void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1314 	break;
1315 
1316 
1317           /*------- Unexclude -----------*/
1318       case MC_UNEXCLUDE :
1319 	if(!(IS_NEWS(stream) && stream->rdonly)){
1320 	    q_status_message(SM_ORDER, 0, 3,
1321 			     _("Unexclude not available for mail folders"));
1322 	}
1323 	else if(any_lflagged(msgmap, MN_EXLD)){
1324 	    SEARCHPGM *pgm;
1325 	    long       i;
1326 	    int	       exbits;
1327 
1328 	    /*
1329 	     * Since excluded means "hidden deleted" and "killed",
1330 	     * the count should reflect the former.
1331 	     */
1332 	    pgm = mail_newsearchpgm();
1333 	    pgm->deleted = 1;
1334 	    pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1335 	    for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1336 	      if((mc = mail_elt(stream, i)) && mc->searched
1337 		 && get_lflag(stream, NULL, i, MN_EXLD)
1338 		 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1339 		      && (exbits & MSG_EX_FILTERED)))
1340 		del_count++;
1341 
1342 	    if(del_count > 0L){
1343 		state->mangled_footer = 1;		/* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1344 		snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1345 			plural(del_count), MAX_SCREEN_COLS+1-45,
1346 			pretty_fn(state->cur_folder));
1347 		prompt[sizeof(prompt)-1] = '\0';
1348 		if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1349 		   || (F_ON(F_AUTO_EXPUNGE, state)
1350 		       && (state->context_current
1351 			   && (state->context_current->use & CNTXT_INCMNG))
1352 		       && context_isambig(state->cur_folder))
1353 		   || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1354 		    long save_cur_rawno;
1355 		    int  were_viewing_a_thread;
1356 
1357 		    save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1358 		    were_viewing_a_thread = (THREADING()
1359 					     && sp_viewing_a_thread(stream));
1360 
1361 		    if(msgno_include(stream, msgmap, MI_NONE)){
1362 			clear_index_cache(stream, 0);
1363 
1364 			if(stream && stream->spare)
1365 			  erase_threading_info(stream, msgmap);
1366 
1367 			refresh_sort(stream, msgmap, SRT_NON);
1368 		    }
1369 
1370 		    if(were_viewing_a_thread){
1371 			if(save_cur_rawno > 0L)
1372 			  mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1373 
1374 			if(view_thread(state, stream, msgmap, 1) && current_index_state)
1375 			  msgmap->top_after_thrd = current_index_state->msg_at_top;
1376 		    }
1377 
1378 		    if(save_cur_rawno > 0L)
1379 		      mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1380 
1381 		    state->mangled_screen = 1;
1382 		    q_status_message2(SM_ORDER, 0, 4,
1383 				      "%s message%s UNexcluded",
1384 				      long2string(del_count),
1385 				      plural(del_count));
1386 
1387 		    if(in_index != View)
1388 		      adjust_cur_to_visible(stream, msgmap);
1389 		}
1390 		else
1391 		  any_messages(NULL, NULL, "UNexcluded");
1392 	    }
1393 	    else
1394 	      any_messages(NULL, "excluded", "to UNexclude");
1395 	}
1396 	else
1397 	  any_messages(NULL, "excluded", "to UNexclude");
1398 
1399 	break;
1400 
1401 
1402           /*------- Make Selection -----------*/
1403       case MC_SELECT :
1404 	if(any_messages(msgmap, NULL, "to Select")){
1405 	    if(aggregate_select(state, msgmap, question_line, in_index) == 0
1406 	       && (in_index == MsgIndx || in_index == ThrdIndx)
1407 	       && F_ON(F_AUTO_ZOOM, state)
1408 	       && any_lflagged(msgmap, MN_SLCT) > 0L
1409 	       && !any_lflagged(msgmap, MN_HIDE))
1410 	      (void) zoom_index(state, stream, msgmap, MN_SLCT);
1411 	}
1412 
1413 	break;
1414 
1415 
1416           /*------- Toggle Current Message Selection State -----------*/
1417       case MC_SELCUR :
1418 	if(any_messages(msgmap, NULL, NULL)){
1419 	   if((select_by_current(state, msgmap, in_index)
1420 	       || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1421 	           && !any_lflagged(msgmap, MN_HIDE)))
1422 	       && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1423 		/* advance current */
1424 		mn_inc_cur(stream, msgmap,
1425 			   (in_index == View && THREADING()
1426 			    && sp_viewing_a_thread(stream))
1427 			     ? MH_THISTHD
1428 			     : (in_index == View)
1429 			       ? MH_ANYTHD : MH_NONE);
1430 	   }
1431 	}
1432 
1433 	break;
1434 
1435 
1436           /*------- Apply command -----------*/
1437       case MC_APPLY :
1438 	if(any_messages(msgmap, NULL, NULL)){
1439 	    if(any_lflagged(msgmap, MN_SLCT) > 0L){
1440 		if(apply_command(state, stream, msgmap, 0,
1441 				 AC_NONE, question_line)){
1442 		    if(F_ON(F_AUTO_UNSELECT, state)){
1443 			agg_select_all(stream, msgmap, NULL, 0);
1444 			unzoom_index(state, stream, msgmap);
1445 		    }
1446 		    else if(F_ON(F_AUTO_UNZOOM, state))
1447 		      unzoom_index(state, stream, msgmap);
1448 		}
1449 	    }
1450 	    else
1451 	      any_messages(NULL, NULL, "to Apply command to.  Try \"Select\"");
1452 	}
1453 
1454 	break;
1455 
1456 
1457           /*-------- Sort command -------*/
1458       case MC_SORT :
1459 	{
1460 	    int were_threading = THREADING();
1461 	    SortOrder sort = mn_get_sort(msgmap);
1462 	    int	      rev  = mn_get_revsort(msgmap);
1463 
1464 	    dprint((1,"MAIL_CMD: sort\n"));
1465 	    if(select_sort(state, question_line, &sort, &rev)){
1466 		/* $ command reinitializes threading collapsed/expanded info */
1467 		if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1468 		  erase_threading_info(stream, msgmap);
1469 
1470 		if(ps_global && ps_global->ttyo){
1471 		    blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1472 		    ps_global->mangled_footer = 1;
1473 		}
1474 
1475 		sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1476 	    }
1477 
1478 	    state->mangled_footer = 1;
1479 
1480 	    /*
1481 	     * We've changed whether we are threading or not so we need to
1482 	     * exit the index and come back in so that we switch between the
1483 	     * thread index and the regular index. Sort_folder will have
1484 	     * reset viewing_a_thread if necessary.
1485 	     */
1486 	    if(SEP_THRDINDX()
1487 	       && ((!were_threading && THREADING())
1488 	            || (were_threading && !THREADING()))){
1489 		state->next_screen = mail_index_screen;
1490 		state->mangled_screen = 1;
1491 	    }
1492 	}
1493 
1494 	break;
1495 
1496 
1497           /*------- Toggle Full Headers -----------*/
1498       case MC_FULLHDR :
1499 	state->full_header++;
1500 	if(state->full_header == 1){
1501 	    if(!(state->quote_suppression_threshold
1502 	         && (state->some_quoting_was_suppressed || in_index != View)))
1503 	      state->full_header++;
1504 	}
1505 	else if(state->full_header > 2)
1506 	  state->full_header = 0;
1507 
1508 	switch(state->full_header){
1509 	  case 0:
1510 	    q_status_message(SM_ORDER, 0, 3,
1511 			     _("Display of full headers is now off."));
1512 	    break;
1513 
1514 	  case 1:
1515 	    q_status_message1(SM_ORDER, 0, 3,
1516 			  _("Quotes displayed, use %s to see full headers"),
1517 			  F_ON(F_USE_FK, state) ? "F9" : "H");
1518 	    break;
1519 
1520 	  case 2:
1521 	    q_status_message(SM_ORDER, 0, 3,
1522 			     _("Display of full headers is now on."));
1523 	    break;
1524 
1525 	}
1526 
1527 	a_changed = TRUE;
1528 	break;
1529 
1530 
1531       case MC_TOGGLE :
1532 	a_changed = TRUE;
1533 	break;
1534 
1535 
1536 #ifdef SMIME
1537           /*------- Try to decrypt message -----------*/
1538       case MC_DECRYPT:
1539 	if(state->smime && state->smime->need_passphrase)
1540 	  smime_get_passphrase();
1541 
1542 	a_changed = TRUE;
1543 	break;
1544 
1545       case MC_SECURITY:
1546 	smime_info_screen(state);
1547 	break;
1548 #endif
1549 
1550 
1551           /*------- Bounce -----------*/
1552       case MC_BOUNCE :
1553 	(void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1554 	break;
1555 
1556 
1557           /*------- Flag -----------*/
1558       case MC_FLAG :
1559 	dprint((4, "\n - flag message -\n"));
1560 	(void) cmd_flag(state, msgmap, MCMD_NONE);
1561 	break;
1562 
1563 
1564           /*------- Pipe message -----------*/
1565       case MC_PIPE :
1566 	(void) cmd_pipe(state, msgmap, MCMD_NONE);
1567 	break;
1568 
1569 
1570           /*--------- Default, unknown command ----------*/
1571       default:
1572 	alpine_panic("Unexpected command case");
1573 	break;
1574     }
1575 
1576     return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1577 }
1578 
1579 
1580 
1581 /*----------------------------------------------------------------------
1582      Map some of the special characters into sensible strings for human
1583    consumption.
1584    c is a UCS-4 character!
1585   ----*/
1586 char *
pretty_command(UCS c)1587 pretty_command(UCS c)
1588 {
1589     static char  buf[10];
1590     char	*s;
1591 
1592     buf[0] = '\0';
1593     s = buf;
1594 
1595     switch(c){
1596       case ' '       : s = "SPACE";		break;
1597       case '\033'    : s = "ESC";		break;
1598       case '\177'    : s = "DEL";		break;
1599       case ctrl('I') : s = "TAB";		break;
1600       case ctrl('J') : s = "LINEFEED";		break;
1601       case ctrl('M') : s = "RETURN";		break;
1602       case ctrl('Q') : s = "XON";		break;
1603       case ctrl('S') : s = "XOFF";		break;
1604       case KEY_UP    : s = "Up Arrow";		break;
1605       case KEY_DOWN  : s = "Down Arrow";	break;
1606       case KEY_RIGHT : s = "Right Arrow";	break;
1607       case KEY_LEFT  : s = "Left Arrow";	break;
1608       case KEY_PGUP  : s = "Prev Page";		break;
1609       case KEY_PGDN  : s = "Next Page";		break;
1610       case KEY_HOME  : s = "Home";		break;
1611       case KEY_END   : s = "End";		break;
1612       case KEY_DEL   : s = "Delete";		break; /* Not necessary DEL! */
1613       case KEY_JUNK  : s = "Junk!";		break;
1614       case BADESC    : s = "Bad Esc";		break;
1615       case NO_OP_IDLE      : s = "NO_OP_IDLE";		break;
1616       case NO_OP_COMMAND   : s = "NO_OP_COMMAND";	break;
1617       case KEY_RESIZE      : s = "KEY_RESIZE";		break;
1618       case KEY_UTF8        : s = "KEY_UTF8";		break;
1619       case KEY_MOUSE       : s = "KEY_MOUSE";		break;
1620       case KEY_SCRLUPL     : s = "KEY_SCRLUPL";		break;
1621       case KEY_SCRLDNL     : s = "KEY_SCRLDNL";		break;
1622       case KEY_SCRLTO      : s = "KEY_SCRLTO";		break;
1623       case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE";	break;
1624       case KEY_DOUBLE_ESC  : s = "KEY_DOUBLE_ESC";	break;
1625       case CTRL_KEY_UP     : s = "Ctrl Up Arrow";	break;
1626       case CTRL_KEY_DOWN   : s = "Ctrl Down Arrow";	break;
1627       case CTRL_KEY_RIGHT  : s = "Ctrl Right Arrow";	break;
1628       case CTRL_KEY_LEFT   : s = "Ctrl Left Arrow";	break;
1629       case PF1	     :
1630       case PF2	     :
1631       case PF3	     :
1632       case PF4	     :
1633       case PF5	     :
1634       case PF6	     :
1635       case PF7	     :
1636       case PF8	     :
1637       case PF9	     :
1638       case PF10	     :
1639       case PF11	     :
1640       case PF12	     :
1641         snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1642 	break;
1643 
1644       default:
1645 	if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1646 	  char d;
1647 	  int  c1;
1648 
1649 	  c1 = (c >= 0x80);
1650 	  d = (c & 0x1f) + 'A' - 1;
1651 	  snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1652 	}
1653 	else{
1654 	  memset(buf, 0, sizeof(buf));
1655 	  utf8_put((unsigned char *) buf, (unsigned long) c);
1656 	}
1657 
1658 	break;
1659     }
1660 
1661     return(s);
1662 }
1663 
1664 
1665 /*----------------------------------------------------------------------
1666    Complain about bogus input
1667 
1668   Args: ch -- input command to complain about
1669 	help -- string indicating where to get help
1670 
1671  ----*/
1672 void
bogus_command(UCS cmd,char * help)1673 bogus_command(UCS cmd, char *help)
1674 {
1675     if(cmd == ctrl('Q') || cmd == ctrl('S'))
1676       q_status_message1(SM_ASYNC, 0, 2,
1677  "%s char received.  Set \"preserve-start-stop\" feature in Setup/Config.",
1678 			pretty_command(cmd));
1679     else if(cmd == KEY_JUNK)
1680       q_status_message3(SM_ORDER, 0, 2,
1681 		      "Invalid key pressed.%s%s%s",
1682 		      (help) ? " Use " : "",
1683 		      (help) ?  help   : "",
1684 		      (help) ? " for help" : "");
1685     else
1686       q_status_message4(SM_ORDER, 0, 2,
1687 	  "Command \"%s\" not defined for this screen.%s%s%s",
1688 		      pretty_command(cmd),
1689 		      (help) ? " Use " : "",
1690 		      (help) ?  help   : "",
1691 		      (help) ? " for help" : "");
1692 }
1693 
1694 
1695 void
bogus_utf8_command(char * cmd,char * help)1696 bogus_utf8_command(char *cmd, char *help)
1697 {
1698     q_status_message4(SM_ORDER, 0, 2,
1699 	  "Command \"%s\" not defined for this screen.%s%s%s",
1700 		      cmd ? cmd : "?",
1701 		      (help) ? " Use " : "",
1702 		      (help) ?  help   : "",
1703 		      (help) ? " for help" : "");
1704 }
1705 
1706 
1707 /*----------------------------------------------------------------------
1708    Execute FLAG message command
1709 
1710   Args: state --  Various satate info
1711         msgmap --  map of c-client to local message numbers
1712 
1713  Result: with side effect of "current" message FLAG flag set or UNset
1714 
1715  ----*/
1716 int
cmd_flag(struct pine * state,MSGNO_S * msgmap,int aopt)1717 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1718 {
1719     char	  *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1720     char          *keyword_array[2];
1721     int            user_defined_flags = 0, mailbox_flags = 0;
1722     int            directly_to_maint_screen = 0;
1723     int 	   use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1724     long	   unflagged, flagged, flags, rawno;
1725     MESSAGECACHE  *mc = NULL;
1726     KEYWORD_S     *kw;
1727     int            i, cnt, is_set, trouble = 0, rv = 0;
1728     size_t         len;
1729     struct flag_table *fp, *ftbl = NULL;
1730     struct flag_screen flag_screen;
1731     static char   *flag_screen_text1[] = {
1732 	N_("    Set desired flags for current message below.  An 'X' means set"),
1733 	N_("    it, and a ' ' means to unset it.  Choose \"Exit\" when finished."),
1734 	NULL
1735     };
1736 
1737     static char   *flag_screen_text2[] = {
1738 	N_("    Set desired flags below for selected messages.  A '?' means to"),
1739 	N_("    leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1740 	N_("    to unset it.  Use the \"Return\" key to toggle, and choose"),
1741 	N_("    \"Exit\" when finished."),
1742 	NULL
1743     };
1744 
1745     static struct  flag_table default_ftbl[] = {
1746 	{N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1747 	{N_("New"),	  h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1748 	{N_("Answered"),  h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1749 	{N_("Forwarded"),  h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1750 	{N_("Deleted"),   h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1751 	{NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1752     };
1753 
1754     /* Only check for dead stream for now.  Should check permanent flags
1755      * eventually
1756      */
1757     if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1758       return rv;
1759 
1760     if(sp_io_error_on_stream(state->mail_stream)){
1761 	sp_set_io_error_on_stream(state->mail_stream, 0);
1762 	pine_mail_check(state->mail_stream);		/* forces write */
1763 	return rv;
1764     }
1765 
1766 go_again:
1767     answer = NULL;
1768     user_defined_flags = 0;
1769     mailbox_flags = 0;
1770     mc = NULL;
1771     trouble = 0;
1772     ftbl = NULL;
1773 
1774     /* count how large ftbl will be */
1775     for(cnt = 0; default_ftbl[cnt].name; cnt++)
1776       ;
1777 
1778     /* add user flags */
1779     for(kw = ps_global->keywords; kw; kw = kw->next){
1780 	if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1781 	    user_defined_flags++;
1782 	    cnt++;
1783 	}
1784     }
1785 
1786     /*
1787      * Add mailbox flags that aren't user-defined flags.
1788      * Don't consider it if it matches either one of our defined
1789      * keywords or one of our defined nicknames for a keyword.
1790      */
1791     for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1792 	char *q;
1793 
1794 	q = stream_to_user_flag_name(state->mail_stream, i);
1795 	if(q && q[0]){
1796 	    for(kw = ps_global->keywords; kw; kw = kw->next){
1797 		if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1798 		  break;
1799 	    }
1800 	}
1801 
1802 	if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1803 	    mailbox_flags++;
1804 	    cnt++;
1805 	}
1806     }
1807 
1808     cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1809 
1810     /* set up ftbl, first the system flags */
1811     ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1812     memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1813     for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1814 	fp->name = cpystr(default_ftbl[i].name);
1815 	fp->help = default_ftbl[i].help;
1816 	fp->flag = default_ftbl[i].flag;
1817 	fp->set  = default_ftbl[i].set;
1818 	fp->ukn  = default_ftbl[i].ukn;
1819     }
1820 
1821     if(user_defined_flags){
1822 	fp->flag = F_COMMENT;
1823 	fp->name = cpystr("");
1824 	fp++;
1825 	fp->flag = F_COMMENT;
1826 	len = strlen(_("User-defined Keywords from Setup/Config"));
1827 	fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1828 	snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1829 	fp++;
1830     }
1831 
1832     /* then the user-defined keywords */
1833     if(user_defined_flags)
1834       for(kw = ps_global->keywords; kw; kw = kw->next){
1835 	if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1836 	     || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1837 	    fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1838 	    fp->keyword = cpystr(kw->kw ? kw->kw : "");
1839 	    if(kw->nick && kw->kw){
1840 		size_t l;
1841 
1842 		l = strlen(kw->kw)+2;
1843 		fp->comment = (char *) fs_get((l+1) * sizeof(char));
1844 		snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1845 		fp->comment[l] = '\0';
1846 	    }
1847 
1848 	    fp->help = h_flag_user_flag;
1849 	    fp->flag = F_KEYWORD;
1850 	    fp->set  = 0;
1851 	    fp->ukn  = 0;
1852 	    fp++;
1853 	}
1854       }
1855 
1856     if(mailbox_flags){
1857 	fp->flag = F_COMMENT;
1858 	fp->name = cpystr("");
1859 	fp++;
1860 	fp->flag = F_COMMENT;
1861 	len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1862 	fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1863 	snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1864 	fp++;
1865     }
1866 
1867     /* then the extra mailbox-defined keywords */
1868     if(mailbox_flags)
1869       for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1870 	char *q;
1871 
1872 	q = stream_to_user_flag_name(state->mail_stream, i);
1873 	if(q && q[0]){
1874 	    for(kw = ps_global->keywords; kw; kw = kw->next){
1875 		if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1876 		  break;
1877 	    }
1878 	}
1879 
1880 	if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1881 	    fp->name = cpystr(q);
1882 	    fp->keyword = cpystr(q);
1883 	    fp->help = h_flag_user_flag;
1884 	    fp->flag = F_KEYWORD;
1885 	    fp->set  = 0;
1886 	    fp->ukn  = 0;
1887 	    fp++;
1888 	}
1889       }
1890 
1891     flag_screen.flag_table  = &ftbl;
1892     flag_screen.explanation = screen_text;
1893 
1894     if(MCMD_ISAGG(aopt)){
1895 	if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1896 	    free_flag_table(&ftbl);
1897 	    return rv;
1898 	}
1899 
1900 	exp = flag_screen_text2;
1901 	for(fp = ftbl; fp->name; fp++){
1902 	    fp->set = CMD_FLAG_UNKN;		/* set to unknown */
1903 	    fp->ukn = TRUE;
1904 	}
1905     }
1906     else if(state->mail_stream
1907 	    && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1908 	    && rawno <= state->mail_stream->nmsgs
1909 	    && (mc = mail_elt(state->mail_stream, rawno))){
1910 	exp = flag_screen_text1;
1911 	for(fp = &ftbl[0]; fp->name; fp++){
1912 	    fp->ukn = 0;
1913 	    if(fp->flag == F_KEYWORD){
1914 		/* see if this keyword is defined for this message */
1915 		fp->set = CMD_FLAG_CLEAR;
1916 		if(user_flag_is_set(state->mail_stream,
1917 				    rawno, fp->keyword))
1918 		  fp->set = CMD_FLAG_SET;
1919 	    }
1920 	    else if(fp->flag == F_FWD){
1921 		/* see if forwarded keyword is defined for this message */
1922 		fp->set = CMD_FLAG_CLEAR;
1923 		if(user_flag_is_set(state->mail_stream,
1924 				    rawno, FORWARDED_FLAG))
1925 		  fp->set = CMD_FLAG_SET;
1926 	    }
1927 	    else if(fp->flag != F_COMMENT)
1928 	      fp->set = ((fp->flag == F_SEEN && !mc->seen)
1929 		         || (fp->flag == F_DEL && mc->deleted)
1930 		         || (fp->flag == F_FLAG && mc->flagged)
1931 		         || (fp->flag == F_ANS && mc->answered))
1932 			  ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1933 	}
1934     }
1935     else{
1936 	q_status_message(SM_ORDER | SM_DING, 3, 4,
1937 			 _("Error accessing message data"));
1938 	free_flag_table(&ftbl);
1939 	return rv;
1940     }
1941 
1942     if(directly_to_maint_screen)
1943       goto the_maint_screen;
1944 
1945 #ifdef _WINDOWS
1946     if (mswin_usedialog ()) {
1947 	if (!os_flagmsgdialog (&ftbl[0])){
1948 	    free_flag_table(&ftbl);
1949 	    return rv;
1950 	}
1951     }
1952     else
1953 #endif
1954     {
1955 	int keyword_shortcut = 0;
1956 
1957 	if(!use_maint_screen){
1958 	    /*
1959 	     * We're going to call cmd_flag_prompt(). We need
1960 	     * to decide whether or not to offer the keyword setting
1961 	     * shortcut. We'll offer it if the user has the feature
1962 	     * enabled AND there are some possible keywords that could
1963 	     * be set.
1964 	     */
1965 	    if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1966 		for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1967 		    if(fp->flag == F_KEYWORD){
1968 			int first_char;
1969 			ESCKEY_S *tp;
1970 
1971 			first_char = (fp->name && fp->name[0])
1972 					? fp->name[0] : -2;
1973 			if(isascii(first_char) && isupper(first_char))
1974 			  first_char = tolower((unsigned char) first_char);
1975 
1976 			for(tp=flag_text_opt; tp->ch != -1; tp++)
1977 			  if(tp->ch == first_char)
1978 			    break;
1979 
1980 			if(tp->ch == -1)
1981 			  keyword_shortcut++;
1982 		    }
1983 		}
1984 	    }
1985 
1986 	    use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1987 						keyword_shortcut);
1988 	}
1989 
1990 the_maint_screen:
1991 	if(use_maint_screen){
1992 	    for(p = &screen_text[0]; *exp; p++, exp++)
1993 	      *p = *exp;
1994 
1995 	    *p = NULL;
1996 
1997 	    directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1998 	}
1999     }
2000 
2001     /* reacquire the elt pointer */
2002     mc = (state->mail_stream
2003 	  && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
2004 	  && rawno <= state->mail_stream->nmsgs)
2005 	  ? mail_elt(state->mail_stream, rawno) : NULL;
2006 
2007     for(fp = ftbl; mc && fp->name; fp++){
2008 	flags = -1;
2009 	switch(fp->flag){
2010 	  case F_SEEN:
2011 	    if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
2012 	       || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2013 		flagit = "\\SEEN";
2014 		if(fp->set){
2015 		    flags     = 0L;
2016 		    unflagged = F_SEEN;
2017 		}
2018 		else{
2019 		    flags     = ST_SET;
2020 		    unflagged = F_UNSEEN;
2021 		}
2022 	    }
2023 
2024 	    break;
2025 
2026 	  case F_ANS:
2027 	    if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
2028 	       || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2029 		flagit = "\\ANSWERED";
2030 		if(fp->set){
2031 		    flags     = ST_SET;
2032 		    unflagged = F_UNANS;
2033 		}
2034 		else{
2035 		    flags     = 0L;
2036 		    unflagged = F_ANS;
2037 		}
2038 	    }
2039 
2040 	    break;
2041 
2042 	  case F_DEL:
2043 	    if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
2044 	       || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2045 		flagit = "\\DELETED";
2046 		if(fp->set){
2047 		    flags     = ST_SET;
2048 		    unflagged = F_UNDEL;
2049 		}
2050 		else{
2051 		    flags     = 0L;
2052 		    unflagged = F_DEL;
2053 		}
2054 	    }
2055 
2056 	    break;
2057 
2058 	  case F_FLAG:
2059 	    if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2060 	       || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2061 		flagit = "\\FLAGGED";
2062 		if(fp->set){
2063 		    flags     = ST_SET;
2064 		    unflagged = F_UNFLAG;
2065 		}
2066 		else{
2067 		    flags     = 0L;
2068 		    unflagged = F_FLAG;
2069 		}
2070 	    }
2071 
2072 	    break;
2073 
2074 	  case F_FWD :
2075 	    if(!MCMD_ISAGG(aopt)){
2076 		/* see if forwarded is defined for this message */
2077 		is_set = CMD_FLAG_CLEAR;
2078 		if(user_flag_is_set(state->mail_stream,
2079 				    mn_m2raw(msgmap, mn_get_cur(msgmap)),
2080 				    FORWARDED_FLAG))
2081 		  is_set = CMD_FLAG_SET;
2082 	    }
2083 
2084 	    if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2085 	       || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2086 		flagit = FORWARDED_FLAG;
2087 		if(fp->set){
2088 		    flags     = ST_SET;
2089 		    unflagged = F_UNFWD;
2090 		}
2091 		else{
2092 		    flags     = 0L;
2093 		    unflagged = F_FWD;
2094 		}
2095 	    }
2096 
2097 	    break;
2098 
2099 	  case F_KEYWORD:
2100 	    if(!MCMD_ISAGG(aopt)){
2101 		/* see if this keyword is defined for this message */
2102 		is_set = CMD_FLAG_CLEAR;
2103 		if(user_flag_is_set(state->mail_stream,
2104 				    mn_m2raw(msgmap, mn_get_cur(msgmap)),
2105 				    fp->keyword))
2106 		  is_set = CMD_FLAG_SET;
2107 	    }
2108 
2109 	    if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2110 	       || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2111 		flagit = fp->keyword;
2112 		keyword_array[0] = fp->keyword;
2113 		keyword_array[1] = NULL;
2114 		if(fp->set){
2115 		    flags     = ST_SET;
2116 		    unflagged = F_UNKEYWORD;
2117 		}
2118 		else{
2119 		    flags     = 0L;
2120 		    unflagged = F_KEYWORD;
2121 		}
2122 	    }
2123 
2124 	    break;
2125 
2126 	  default:
2127 	    break;
2128 	}
2129 
2130 	flagged = 0L;
2131 	if(flags >= 0L
2132 	   && (seq = currentf_sequence(state->mail_stream, msgmap,
2133 				       unflagged, &flagged, unflagged & F_DEL,
2134 				       (fp->flag == F_KEYWORD
2135 				        && unflagged == F_KEYWORD)
2136 					 ? keyword_array : NULL,
2137 				       (fp->flag == F_KEYWORD
2138 				        && unflagged == F_UNKEYWORD)
2139 					 ? keyword_array : NULL))){
2140 	    /*
2141 	     * For user keywords, we may have to create the flag in
2142 	     * the folder if it doesn't already exist and we are setting
2143 	     * it (as opposed to clearing it). Mail_flag will
2144 	     * do that for us, but it's failure isn't very friendly
2145 	     * error-wise. So we try to make it a little smoother.
2146 	     */
2147 	    if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2148 	       || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2149 	           && i < NUSERFLAGS))
2150 	      mail_flag(state->mail_stream, seq, flagit, flags);
2151 	    else{
2152 		/* trouble, see if we can add the user flag */
2153 		if(state->mail_stream->kwd_create)
2154 		  mail_flag(state->mail_stream, seq, flagit, flags);
2155 		else{
2156 		    trouble++;
2157 
2158 		    if(some_user_flags_defined(state->mail_stream))
2159 		      q_status_message(SM_ORDER, 3, 4,
2160 			       _("No more keywords allowed in this folder!"));
2161 		    else if(fp->flag == F_FWD)
2162 		      q_status_message(SM_ORDER, 3, 4,
2163 				   _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2164 		    else
2165 		      q_status_message(SM_ORDER, 3, 4,
2166 				   _("Cannot add keywords for this folder"));
2167 		}
2168 	    }
2169 
2170 	    fs_give((void **) &seq);
2171 	    if(flagged && !trouble){
2172 		snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2173 			(fp->set) ? "F" : "Unf",
2174 			MCMD_ISAGG(aopt) ? " " : "",
2175 			MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2176 			(MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2177 			  ? " (of " : "",
2178 			(MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2179 			  ? comatose(mn_total_cur(msgmap)) : "",
2180 			(MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2181 			  ? ")" : "",
2182 			MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2183 			MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2184 			fp->name);
2185 		tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2186 		q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2187 		rv++;
2188 	    }
2189 	}
2190     }
2191 
2192     free_flag_table(&ftbl);
2193 
2194     if(directly_to_maint_screen)
2195       goto go_again;
2196 
2197     if(MCMD_ISAGG(aopt))
2198       restore_selected(msgmap);
2199 
2200     if(!answer)
2201       q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2202 
2203     return rv;
2204 }
2205 
2206 
2207 /*----------------------------------------------------------------------
2208    Offer concise status line flag prompt
2209 
2210   Args: state --  Various satate info
2211         flags -- flags to offer setting
2212 
2213  Result: TRUE if flag to set specified in flags struct or FALSE otw
2214 
2215  ----*/
2216 int
cmd_flag_prompt(struct pine * state,struct flag_screen * flags,int allow_keyword_shortcuts)2217 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2218 {
2219     int			r, setflag = 1, first_char;
2220     struct flag_table  *fp;
2221     ESCKEY_S           *ek;
2222     char               *ftext, *ftext_not;
2223     static char *flag_text =
2224   N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2225     static char *flag_text_ak =
2226   N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2227     static char *flag_text_not =
2228   N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2229     static char *flag_text_ak_not =
2230   N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2231 
2232     if(allow_keyword_shortcuts){
2233 	int       cnt = 0;
2234 	ESCKEY_S *dp, *sp, *tp;
2235 
2236 	for(sp=flag_text_opt; sp->ch != -1; sp++)
2237 	  cnt++;
2238 
2239 	for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2240 	  if(fp->flag == F_KEYWORD)
2241 	    cnt++;
2242 
2243 	/* set up an ESCKEY_S list which includes invisible keys for keywords */
2244 	ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2245 	memset(ek, 0, (cnt+1) * sizeof(*ek));
2246 	for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2247 	  *dp = *sp;
2248 
2249 	for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2250 	    if(fp->flag == F_KEYWORD){
2251 		first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2252 		if(isascii(first_char) && isupper(first_char))
2253 		  first_char = tolower((unsigned char) first_char);
2254 
2255 		/*
2256 		 * Check to see if an earlier keyword in the list, or one of
2257 		 * the builtin system letters already uses this character.
2258 		 * If so, the first one wins.
2259 		 */
2260 		for(tp=ek; tp->ch != 0; tp++)
2261 		  if(tp->ch == first_char)
2262 		    break;
2263 
2264 		if(tp->ch != 0)
2265 		  continue;		/* skip it, already used that char */
2266 
2267 		dp->ch    = first_char;
2268 		dp->rval  = first_char;
2269 		dp->name  = "";
2270 		dp->label = "";
2271 		dp++;
2272 	    }
2273 	}
2274 
2275 	dp->ch = -1;
2276 	ftext = _(flag_text_ak);
2277 	ftext_not = _(flag_text_ak_not);
2278     }
2279     else{
2280 	ek = flag_text_opt;
2281 	ftext = _(flag_text);
2282 	ftext_not = _(flag_text_not);
2283     }
2284 
2285     while(1){
2286 	r = radio_buttons(setflag ? ftext : ftext_not,
2287 			  -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2288 			  NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2289 	/*
2290 	 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2291 	 * being used otherwise. The keywords use up all the possible
2292 	 * letters, so a negative number is good, but it has to be different
2293 	 * from other negative return values.
2294 	 */
2295 	if(r == SEQ_EXCEPTION-1)	/* ol'cancelrooney */
2296 	  return(TRUE);
2297 	else if(r == 10)		/* return and goto flag screen */
2298 	  return(FALSE);
2299 	else if(r == '!')		/* flip intention */
2300 	  setflag = !setflag;
2301 	else
2302 	  break;
2303     }
2304 
2305     for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2306 	if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2307 	    if((r == 'n' && fp->flag == F_SEEN)
2308 	       || (r == '*' && fp->flag == F_FLAG)
2309 	       || (r == 'd' && fp->flag == F_DEL)
2310 	       || (r == 'f' && fp->flag == F_FWD)
2311 	       || (r == 'a' && fp->flag == F_ANS)){
2312 		fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2313 		break;
2314 	    }
2315 	}
2316 	else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2317 	    first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2318 	    if(isascii(first_char) && isupper(first_char))
2319 	      first_char = tolower((unsigned char) first_char);
2320 
2321 	    if(r == first_char){
2322 		fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2323 		break;
2324 	    }
2325 	}
2326     }
2327 
2328     if(ek != flag_text_opt)
2329       fs_give((void **) &ek);
2330 
2331     return(TRUE);
2332 }
2333 
2334 
2335 /*
2336  * (*ft) is an array of flag_table entries.
2337  */
2338 void
free_flag_table(struct flag_table ** ft)2339 free_flag_table(struct flag_table **ft)
2340 {
2341     struct flag_table *fp;
2342 
2343     if(ft && *ft){
2344 	for(fp = (*ft); fp->name; fp++){
2345 	    if(fp->name)
2346 	      fs_give((void **) &fp->name);
2347 
2348 	    if(fp->keyword)
2349 	      fs_give((void **) &fp->keyword);
2350 
2351 	    if(fp->comment)
2352 	      fs_give((void **) &fp->comment);
2353 	}
2354 
2355 	fs_give((void **) ft);
2356     }
2357 }
2358 
2359 
2360 /*----------------------------------------------------------------------
2361    Execute REPLY message command
2362 
2363   Args: state --  Various satate info
2364         msgmap --  map of c-client to local message numbers
2365 
2366  Result: reply sent or not
2367 
2368  ----*/
2369 int
cmd_reply(struct pine * state,MSGNO_S * msgmap,int aopt,ACTION_S * role)2370 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2371 {
2372     int rv = 0;
2373 
2374     if(any_messages(msgmap, NULL, "to Reply to")){
2375 	if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2376 	  return rv;
2377 
2378 	rv = reply(state, role);
2379 
2380 	if(MCMD_ISAGG(aopt))
2381 	  restore_selected(msgmap);
2382 
2383 	state->mangled_screen = 1;
2384     }
2385 
2386     return rv;
2387 }
2388 
2389 
2390 /*----------------------------------------------------------------------
2391    Execute FORWARD message command
2392 
2393   Args: state --  Various satate info
2394         msgmap --  map of c-client to local message numbers
2395 
2396  Result: selected message[s] forwarded or not
2397 
2398  ----*/
2399 int
cmd_forward(struct pine * state,MSGNO_S * msgmap,int aopt,ACTION_S * role)2400 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2401 {
2402     int rv = 0;
2403 
2404     if(any_messages(msgmap, NULL, "to Forward")){
2405 	if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2406 	  return rv;
2407 
2408 	rv = forward(state, role);
2409 
2410 	if(MCMD_ISAGG(aopt))
2411 	  restore_selected(msgmap);
2412 
2413 	state->mangled_screen = 1;
2414     }
2415 
2416     return rv;
2417 }
2418 
2419 
2420 /*----------------------------------------------------------------------
2421    Execute BOUNCE message command
2422 
2423   Args:  state --  Various satate info
2424         msgmap --  map of c-client to local message numbers
2425 	  aopt --  aggregate options
2426 
2427  Result: selected message[s] bounced or not
2428 
2429  ----*/
2430 int
cmd_bounce(struct pine * state,MSGNO_S * msgmap,int aopt,ACTION_S * role)2431 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2432 {
2433     int rv = 0;
2434 
2435     if(any_messages(msgmap, NULL, "to Bounce")){
2436 	long i;
2437 	if(MCMD_ISAGG(aopt)){
2438 	   if(!pseudo_selected(state->mail_stream, msgmap))
2439 	      return rv;
2440 	}
2441 	else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2442 	        && get_lflag(state->mail_stream, msgmap,
2443 			mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2444 	       q_status_message(SM_ORDER | SM_DING, 3, 4,
2445 			 _("WARNING: non-selected message is being bounced!"));
2446 	else if (i > 1L
2447 		&& get_lflag(state->mail_stream, msgmap,
2448 			mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2449 	       q_status_message(SM_ORDER | SM_DING, 3, 4,
2450 			 _("WARNING: not bouncing all selected messages!"));
2451 
2452 	rv = bounce(state, role);
2453 
2454 	if(MCMD_ISAGG(aopt))
2455 	  restore_selected(msgmap);
2456 
2457 	state->mangled_footer = 1;
2458     }
2459 
2460     return rv;
2461 }
2462 
2463 
2464 /*----------------------------------------------------------------------
2465    Execute save message command: prompt for folder and call function to save
2466 
2467   Args: screen_line    --  Line on the screen to prompt on
2468         message        --  The MESSAGECACHE entry of message to save
2469 
2470  Result: The folder lister can be called to make selection; mangled screen set
2471 
2472    This does the prompting for the folder name to save to, possibly calling
2473  up the folder display for selection of folder by user.
2474  ----*/
2475 int
cmd_save(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,int aopt,CmdWhere in_index)2476 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2477 {
2478     char	      newfolder[MAILTMPLEN], nmsgs[32], *nick;
2479     int		      we_cancel = 0, rv = 0, save_flags;
2480     long	      i, raw;
2481     CONTEXT_S	     *cntxt = NULL;
2482     ENVELOPE	     *e = NULL;
2483     SaveDel           del = DontAsk;
2484     SavePreserveOrder pre = DontAskPreserve;
2485 
2486     dprint((4, "\n - saving message -\n"));
2487 
2488     if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2489       return rv;
2490 
2491     state->ugly_consider_advancing_bit = 0;
2492     if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2493        && msgno_any_deletedparts(stream, msgmap)
2494        && want_to(_("Saved copy will NOT include entire message!  Continue"),
2495 		  'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2496 	restore_selected(msgmap);
2497 	cmd_cancelled("Save message");
2498 	return rv;
2499     }
2500 
2501     raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2502 
2503     if(mn_total_cur(msgmap) <= 1L){
2504 	snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2505 	nmsgs[sizeof(nmsgs)-1] = '\0';
2506 	e = pine_mail_fetchstructure(stream, raw, NULL);
2507 	if(!e) {
2508 	    q_status_message(SM_ORDER | SM_DING, 3, 4,
2509 			     _("Can't save message.  Error accessing folder"));
2510 	    restore_selected(msgmap);
2511 	    return rv;
2512 	}
2513     }
2514     else{
2515 	snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2516 	nmsgs[sizeof(nmsgs)-1] = '\0';
2517 
2518 	/* e is just used to get a default save folder from the first msg */
2519 	e = pine_mail_fetchstructure(stream,
2520 				     mn_m2raw(msgmap, mn_first_cur(msgmap)),
2521 				     NULL);
2522     }
2523 
2524     del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2525 	     ? Del : NoDel;
2526     if(mn_total_cur(msgmap) > 1L)
2527       pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2528     else
2529       pre = DontAskPreserve;
2530 
2531     if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2532 		   raw, NULL, &del, &pre)){
2533 
2534 	if(ps_global && ps_global->ttyo){
2535 	    blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2536 	    ps_global->mangled_footer = 1;
2537 	}
2538 
2539 	save_flags = SV_FIX_DELS;
2540 	if(pre == RetPreserve)
2541 	  save_flags |= SV_PRESERVE;
2542 	if(del == RetDel)
2543 	  save_flags |= SV_DELETE;
2544 	if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2545 	  save_flags |= SV_INBOXWOCNTXT;
2546 
2547 	we_cancel = busy_cue(_("Saving"), NULL, 1);
2548 	i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2549 	if(we_cancel)
2550 	  cancel_busy_cue(0);
2551 
2552 	if(i == mn_total_cur(msgmap)){
2553 	    rv++;
2554 	    if(mn_total_cur(msgmap) <= 1L){
2555 		int need, avail = ps_global->ttyo->screen_cols - 2;
2556 		int lennick, lenfldr;
2557 
2558 		if(cntxt
2559 		   && ps_global->context_list->next
2560 		   && context_isambig(newfolder)){
2561 		    lennick = MIN(strlen(cntxt->nickname), 500);
2562 		    lenfldr = MIN(strlen(newfolder), 500);
2563 		    need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2564 			   lenfldr + lennick;
2565 		    if(need > avail){
2566 			if(lennick > 10){
2567 			    need -= MIN(lennick-10, need-avail);
2568 			    lennick -= MIN(lennick-10, need-avail);
2569 			}
2570 
2571 			if(need > avail && lenfldr > 10)
2572 			  lenfldr -= MIN(lenfldr-10, need-avail);
2573 		    }
2574 
2575 		    snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2576 			    "Message %s copied to \"%s\" in <%s>",
2577 			    long2string(mn_get_cur(msgmap)),
2578 			    short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2579 				      lenfldr, MidDots),
2580 			    short_str(cntxt->nickname,
2581 				      (char *)(tmp_20k_buf+2000), 1000,
2582 				      lennick, EndDots));
2583 		    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2584 		}
2585 		else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2586 		    snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2587 			    "Message %s copied to \"%s\"",
2588 			    long2string(mn_get_cur(msgmap)),
2589 			    nick);
2590 		    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2591 		}
2592 		else{
2593 		    char *f = " folder";
2594 
2595 		    lenfldr = MIN(strlen(newfolder), 500);
2596 		    need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2597 			   lenfldr;
2598 		    if(need > avail){
2599 			need -= strlen(f);
2600 			f = "";
2601 			if(need > avail && lenfldr > 10)
2602 			  lenfldr -= MIN(lenfldr-10, need-avail);
2603 		    }
2604 
2605 		    snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2606 			    "Message %s copied to%s \"%s\"",
2607 			    long2string(mn_get_cur(msgmap)), f,
2608 			    short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2609 				      lenfldr, MidDots));
2610 		    tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2611 		}
2612 	    }
2613 	    else{
2614 		snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2615 		      comatose(mn_total_cur(msgmap)));
2616 		tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2617 	    }
2618 
2619 	    if(del == RetDel){
2620 		strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2621 		tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2622 	    }
2623 
2624 	    q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2625 
2626 	    if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2627 		if(sp_new_mail_count(stream))
2628 		  process_filter_patterns(stream, msgmap,
2629 					  sp_new_mail_count(stream));
2630 
2631 		mn_inc_cur(stream, msgmap,
2632 			   (in_index == View && THREADING()
2633 			    && sp_viewing_a_thread(stream))
2634 			     ? MH_THISTHD
2635 			     : (in_index == View)
2636 			       ? MH_ANYTHD : MH_NONE);
2637 	    }
2638 
2639 	    state->ugly_consider_advancing_bit = 1;
2640 	}
2641     }
2642 
2643     if(MCMD_ISAGG(aopt))			/* straighten out fakes */
2644       restore_selected(msgmap);
2645 
2646     if(del == RetDel)
2647       update_titlebar_status();			/* make sure they see change */
2648 
2649     return rv;
2650 }
2651 
2652 
2653 void
role_compose(struct pine * state)2654 role_compose(struct pine *state)
2655 {
2656     int action;
2657 
2658     if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2659 	PAT_STATE  pstate;
2660 
2661 	if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2662 	    action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2663 				   -FOOTER_ROWS(state), choose_action,
2664 				   'c', 'x', h_role_compose, RB_NORM);
2665 	}
2666 	else{
2667 	    q_status_message(SM_ORDER, 0, 3,
2668 			 _("No roles available. Use Setup/Rules to add roles."));
2669 	    return;
2670 	}
2671     }
2672     else
2673       action = 'c';
2674 
2675     if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2676 	ACTION_S *role = NULL;
2677 	void    (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2678 
2679 	redraw = state->redrawer;
2680 	state->redrawer = NULL;
2681 	prev_screen = state->prev_screen;
2682 	role = NULL;
2683 	state->next_screen = SCREEN_FUN_NULL;
2684 
2685 	/* Setup role */
2686 	if(role_select_screen(state, &role,
2687 			      action == 'f' ? MC_FORWARD :
2688 			       action == 'r' ? MC_REPLY :
2689 			        action == 'b' ? MC_BOUNCE :
2690 				 action == 'c' ? MC_COMPOSE : 0) < 0){
2691 	    cmd_cancelled(action == 'f' ? _("Forward") :
2692 			  action == 'r' ? _("Reply") :
2693 			   action == 'c' ? _("Composition") : _("Bounce"));
2694 	    state->next_screen = prev_screen;
2695 	    state->redrawer = redraw;
2696 	    state->mangled_screen = 1;
2697 	}
2698 	else{
2699 	    /*
2700 	     * If default role was selected (NULL) we need to make
2701 	     * up a role which won't do anything, but will cause
2702 	     * compose_mail to think there's already a role so that
2703 	     * it won't try to confirm the default.
2704 	     */
2705 	    if(role)
2706 	      role = combine_inherited_role(role);
2707 	    else{
2708 		role = (ACTION_S *) fs_get(sizeof(*role));
2709 		memset((void *) role, 0, sizeof(*role));
2710 		role->nick = cpystr("Default Role");
2711 	    }
2712 
2713 	    state->redrawer = NULL;
2714 	    switch(action){
2715 	      case 'c':
2716 		compose_mail(NULL, NULL, role, NULL, NULL);
2717 		break;
2718 
2719 	      case 'r':
2720 		(void) reply(state, role);
2721 		break;
2722 
2723 	      case 'f':
2724 		(void) forward(state, role);
2725 		break;
2726 
2727 	      case 'b':
2728 		(void) bounce(state, role);
2729 		break;
2730 	    }
2731 
2732 	    if(role)
2733 	      free_action(&role);
2734 
2735 	    state->next_screen = prev_screen;
2736 	    state->redrawer = redraw;
2737 	    state->mangled_screen = 1;
2738 	}
2739     }
2740 }
2741 
2742 
2743 /*----------------------------------------------------------------------
2744    Do the dirty work of prompting the user for a folder name
2745 
2746   Args:
2747         nfldr should be a buffer at least MAILTMPLEN long
2748 	dela -- a pointer to a SaveDel. If it is
2749 	  DontAsk on input, don't offer Delete prompt
2750 	  Del     on input, offer Delete command with default of Delete
2751 	  NoDel                                                NoDelete
2752 	  RetDel and RetNoDel are return values
2753 
2754 
2755  Result:
2756 
2757  ----*/
2758 int
save_prompt(struct pine * state,CONTEXT_S ** cntxt,char * nfldr,size_t len_nfldr,char * nmsgs,ENVELOPE * env,long int rawmsgno,char * section,SaveDel * dela,SavePreserveOrder * prea)2759 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2760 	    char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2761 	    SaveDel *dela, SavePreserveOrder *prea)
2762 {
2763     int		      rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2764     int		      delindex = 0, preindex = 0, r;
2765     char	      prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2766     char              *buf = tmp_20k_buf;
2767     char              shortbuf[200];
2768     char              *folder;
2769     HelpType	      help;
2770     SaveDel           del = DontAsk;
2771     SavePreserveOrder pre = DontAskPreserve;
2772     char             *deltext = NULL;
2773     static HISTORY_S *history = NULL;
2774     CONTEXT_S	     *tc;
2775     ESCKEY_S	      ekey[10];
2776 
2777     if(!cntxt)
2778       alpine_panic("no context ptr in save_prompt");
2779 
2780     init_hist(&history, HISTSIZE);
2781 
2782     if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2783       return(0);		/* message expunged! */
2784 
2785     /* how many context's can be saved to... */
2786     for(tc = state->context_list; tc; tc = tc->next)
2787       if(!NEWS_TEST(tc))
2788         saveable_count++;
2789 
2790     /* set up extra command option keys */
2791     rc = 0;
2792     ekey[rc].ch      = ctrl('T');
2793     ekey[rc].rval    = 2;
2794     ekey[rc].name    = "^T";
2795     /* TRANSLATORS: command means go to Folders list */
2796     ekey[rc++].label = N_("To Fldrs");
2797 
2798     if(saveable_count > 1){
2799 	ekey[rc].ch      = ctrl('P');
2800 	ekey[rc].rval    = 10;
2801 	ekey[rc].name    = "^P";
2802 	ekey[rc++].label = N_("Prev Collection");
2803 
2804 	ekey[rc].ch      = ctrl('N');
2805 	ekey[rc].rval    = 11;
2806 	ekey[rc].name    = "^N";
2807 	ekey[rc++].label = N_("Next Collection");
2808     }
2809 
2810     if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2811 	ekey[rc].ch      = TAB;
2812 	ekey[rc].rval    = 12;
2813 	ekey[rc].name    = "TAB";
2814 	/* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2815 	ekey[rc++].label = N_("Complete");
2816     }
2817 
2818     if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2819 	ekey[rc].ch      = ctrl('X');
2820 	ekey[rc].rval    = 14;
2821 	ekey[rc].name    = "^X";
2822 	/* TRANSLATORS: list all the matches */
2823 	ekey[rc++].label = N_("ListMatches");
2824     }
2825 
2826     if(dela && (*dela == NoDel || *dela == Del)){
2827 	ekey[rc].ch      = ctrl('R');
2828 	ekey[rc].rval    = 15;
2829 	ekey[rc].name    = "^R";
2830 	delindex = rc++;
2831 	del = *dela;
2832     }
2833 
2834     if(prea && (*prea == NoPreserve || *prea == Preserve)){
2835 	ekey[rc].ch      = ctrl('W');
2836 	ekey[rc].rval    = 16;
2837 	ekey[rc].name    = "^W";
2838 	preindex = rc++;
2839 	pre = *prea;
2840     }
2841 
2842     if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2843 	ekey[rc].ch      = KEY_UP;
2844 	ekey[rc].rval    = 10;
2845 	ekey[rc].name    = "";
2846 	ekey[rc++].label = "";
2847 
2848 	ekey[rc].ch      = KEY_DOWN;
2849 	ekey[rc].rval    = 11;
2850 	ekey[rc].name    = "";
2851 	ekey[rc++].label = "";
2852     }
2853     else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2854 	ekey[rc].ch      = KEY_UP;
2855 	ekey[rc].rval    = 30;
2856 	ekey[rc].name    = "";
2857 	ku = rc;
2858 	ekey[rc++].label = "";
2859 
2860 	ekey[rc].ch      = KEY_DOWN;
2861 	ekey[rc].rval    = 31;
2862 	ekey[rc].name    = "";
2863 	ekey[rc++].label = "";
2864     }
2865 
2866     ekey[rc].ch = -1;
2867 
2868     *nfldr = '\0';
2869     help = NO_HELP;
2870     while(!done){
2871 	/* only show collection number if more than one available */
2872 	if(ps_global->context_list->next)
2873 	  snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2874 		  deltext ? deltext : "",
2875 		  nmsgs,
2876 		  short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2877 		  strsquish(buf, SIZEOF_20KBUF, folder, 25));
2878 	else
2879 	  snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2880 		  deltext ? deltext : "",
2881 		  nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2882 
2883 	prompt[sizeof(prompt)-1] = '\0';
2884 
2885 	/*
2886 	 * If the prompt won't fit, try removing deltext.
2887 	 */
2888 	if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2889 	    if(ps_global->context_list->next)
2890 	      snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2891 		      nmsgs,
2892 		      short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2893 		      strsquish(buf, SIZEOF_20KBUF, folder, 25));
2894 	    else
2895 	      snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2896 		      nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2897 
2898 	    prompt[sizeof(prompt)-1] = '\0';
2899 	}
2900 
2901 	/*
2902 	 * If the prompt still won't fit, remove the extra info contained
2903 	 * in nmsgs.
2904 	 */
2905 	if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2906 	    if(ps_global->context_list->next)
2907 	      snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2908 		      short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2909 		      strsquish(buf, SIZEOF_20KBUF, folder, 25));
2910 	    else
2911 	      snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2912 		      strsquish(buf, SIZEOF_20KBUF, folder, 25));
2913 
2914 	    prompt[sizeof(prompt)-1] = '\0';
2915 	}
2916 
2917 	if(del != DontAsk)
2918 	  ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2919 
2920 	if(pre != DontAskPreserve)
2921 	  ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2922 
2923 	if(ku >= 0){
2924 	    if(items_in_hist(history) > 1){
2925 		ekey[ku].name  = HISTORY_UP_KEYNAME;
2926 		ekey[ku].label = HISTORY_KEYLABEL;
2927 		ekey[ku+1].name  = HISTORY_DOWN_KEYNAME;
2928 		ekey[ku+1].label = HISTORY_KEYLABEL;
2929 	    }
2930 	    else{
2931 		ekey[ku].name  = "";
2932 		ekey[ku].label = "";
2933 		ekey[ku+1].name  = "";
2934 		ekey[ku+1].label = "";
2935 	    }
2936 	}
2937 
2938 	flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2939 	rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2940 			      prompt, ekey, help, &flags);
2941 
2942 	switch(rc){
2943 	  case -1 :
2944 	    q_status_message(SM_ORDER | SM_DING, 3, 3,
2945 			     _("Error reading folder name"));
2946 	    done--;
2947 	    break;
2948 
2949 	  case 0 :
2950 	    removing_trailing_white_space(nfldr);
2951 	    removing_leading_white_space(nfldr);
2952 
2953 	    if(*nfldr || *folder){
2954 		char *p, *name, *fullname = NULL;
2955 		int   exists, breakout = FALSE;
2956 
2957 		if(!*nfldr){
2958 		    strncpy(nfldr, folder, len_nfldr-1);
2959 		    nfldr[len_nfldr-1] = '\0';
2960 		}
2961 
2962 		save_hist(history, nfldr, 0, (void *) *cntxt);
2963 
2964 		if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2965 		    name = nfldr;
2966 
2967 		if(update_folder_spec(expanded, sizeof(expanded), name)){
2968 		    strncpy(name = nfldr, expanded, len_nfldr-1);
2969 		    nfldr[len_nfldr-1] = '\0';
2970 		}
2971 
2972 		exists = folder_name_exists(*cntxt, name, &fullname);
2973 
2974 		if(exists == FEX_ERROR){
2975 		    q_status_message1(SM_ORDER, 0, 3,
2976 				      _("Problem accessing folder \"%s\""),
2977 				      nfldr);
2978 		    done--;
2979 		}
2980 		else{
2981 		    if(fullname){
2982 			strncpy(name = nfldr, fullname, len_nfldr-1);
2983 			nfldr[len_nfldr-1] = '\0';
2984 			fs_give((void **) &fullname);
2985 			breakout = TRUE;
2986 		    }
2987 
2988 		    if(exists & FEX_ISFILE){
2989 			done++;
2990 		    }
2991 		    else if((exists & FEX_ISDIR)){
2992 			char	   tmp[MAILTMPLEN];
2993 
2994 			tc = *cntxt;
2995 			if(breakout){
2996 			    CONTEXT_S *fake_context;
2997 			    size_t	   l;
2998 
2999 			    strncpy(tmp, name, sizeof(tmp));
3000 			    tmp[sizeof(tmp)-2-1] = '\0';
3001 			    if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
3002 				if(l < sizeof(tmp)){
3003 				    tmp[l] = tc->dir->delim;
3004 				    strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
3005 				}
3006 			    }
3007 			    else
3008 			      strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
3009 
3010 			    tmp[sizeof(tmp)-1] = '\0';
3011 
3012 			    fake_context = new_context(tmp, 0);
3013 			    nfldr[0] = '\0';
3014 			    done = display_folder_list(&fake_context, nfldr,
3015 						       1, folders_for_save);
3016 			    free_context(&fake_context);
3017 			}
3018 			else if(tc->dir->delim
3019 				&& (p = strrindex(name, tc->dir->delim))
3020 				&& *(p+1) == '\0')
3021 			  done = display_folder_list(cntxt, nfldr,
3022 						     1, folders_for_save);
3023 			else{
3024 			    q_status_message1(SM_ORDER, 3, 3,
3025 				      _("\"%s\" is a directory"), name);
3026 			    if(tc->dir->delim
3027 			       && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3028 				strncpy(tmp, name, sizeof(tmp));
3029 				tmp[sizeof(tmp)-1] = '\0';
3030 				snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3031 			    }
3032 			}
3033 		    }
3034 		    else{			/* Doesn't exist, create! */
3035 			if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3036 			    strncpy(name = nfldr, fullname, len_nfldr-1);
3037 			    nfldr[len_nfldr-1] = '\0';
3038 			    fs_give((void **) &fullname);
3039 			}
3040 
3041 			switch(create_for_save(*cntxt, name)){
3042 			  case 1 :		/* success */
3043 			    done++;
3044 			    break;
3045 			  case 0 :		/* error */
3046 			  case -1 :		/* declined */
3047 			    done--;
3048 			    break;
3049 			}
3050 		    }
3051 		}
3052 
3053 		break;
3054 	    }
3055 	    /* else fall thru like they cancelled */
3056 
3057 	  case 1 :
3058 	    cmd_cancelled("Save message");
3059 	    done--;
3060 	    break;
3061 
3062 	  case 2 :
3063 	    r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3064 
3065 	    if(r)
3066 	      done++;
3067 
3068 	    break;
3069 
3070 	  case 3 :
3071 	    helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3072 	    ps_global->mangled_screen = 1;
3073 	    break;
3074 
3075 	  case 4 :				/* redraw */
3076 	    break;
3077 
3078 	  case 10 :				/* previous collection */
3079 	    for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3080 	      if(!NEWS_TEST(tc))
3081 		break;
3082 
3083 	    if(!tc){
3084 		CONTEXT_S *tc2;
3085 
3086 		for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3087 		  if(!NEWS_TEST(tc2))
3088 		    tc = tc2;
3089 	    }
3090 
3091 	    *cntxt = tc;
3092 	    break;
3093 
3094 	  case 11 :				/* next collection */
3095 	    tc = (*cntxt);
3096 
3097 	    do
3098 	      if(((*cntxt) = (*cntxt)->next) == NULL)
3099 		(*cntxt) = ps_global->context_list;
3100 	    while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3101 	    break;
3102 
3103 	  case 12 :				/* file name completion */
3104 	    if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3105 		if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3106 		    r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3107 		    if(r)
3108 		      done++;			/* bingo! */
3109 		    else
3110 		      rc = 0;			/* burn last_rc */
3111 		}
3112 		else
3113 		  Writechar(BELL, 0);
3114 	    }
3115 
3116 	    break;
3117 
3118 	  case 14 :				/* file name completion */
3119 	    r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3120 	    if(r)
3121 	      done++;			/* bingo! */
3122 	    else
3123 	      rc = 0;			/* burn last_rc */
3124 
3125 	    break;
3126 
3127 	  case 15 :			/* Delete / No Delete */
3128 	    del = (del == NoDel) ? Del : NoDel;
3129 	    deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3130 	    break;
3131 
3132 	  case 16 :			/* Preserve Order or not */
3133 	    pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3134 	    break;
3135 
3136 	  case 30 :
3137 	    if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3138 		strncpy(nfldr, p, len_nfldr);
3139 		nfldr[len_nfldr-1] = '\0';
3140 		if(history->hist[history->curindex])
3141 		  *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3142 	    }
3143 	    else
3144 	      Writechar(BELL, 0);
3145 
3146 	    break;
3147 
3148 	  case 31 :
3149 	    if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3150 		strncpy(nfldr, p, len_nfldr);
3151 		nfldr[len_nfldr-1] = '\0';
3152 		if(history->hist[history->curindex])
3153 		  *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3154 	    }
3155 	    else
3156 	      Writechar(BELL, 0);
3157 
3158 	    break;
3159 
3160 	  default :
3161 	    alpine_panic("Unhandled case");
3162 	    break;
3163 	}
3164 
3165 	last_rc = rc;
3166     }
3167 
3168     ps_global->mangled_footer = 1;
3169 
3170     if(done < 0)
3171       return(0);
3172 
3173     if(*nfldr){
3174 	strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3175 	ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3176 	if(*cntxt)
3177 	  ps_global->last_save_context = *cntxt;
3178     }
3179     else{
3180 	strncpy(nfldr, folder, len_nfldr-1);
3181 	nfldr[len_nfldr-1] = '\0';
3182     }
3183 
3184     /* nickname?  Copy real name to nfldr */
3185     if(*cntxt
3186        && context_isambig(nfldr)
3187        && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3188 	strncpy(nfldr, p, len_nfldr-1);
3189 	nfldr[len_nfldr-1] = '\0';
3190     }
3191 
3192     if(dela && (*dela == NoDel || *dela == Del))
3193       *dela = (del == NoDel) ? RetNoDel : RetDel;
3194 
3195     if(prea && (*prea == NoPreserve || *prea == Preserve))
3196       *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3197 
3198     return(1);
3199 }
3200 
3201 
3202 /*----------------------------------------------------------------------
3203    Prompt user before implicitly creating a folder for saving
3204 
3205   Args: context - context to create folder in
3206 	folder  - folder name to create
3207 
3208  Result: 1 on proceed, -1 on decline, 0 on error
3209 
3210  ----*/
3211 int
create_for_save_prompt(CONTEXT_S * context,char * folder,int sequence_sensitive)3212 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3213 {
3214     if(context && ps_global->context_list->next && context_isambig(folder)){
3215 	if(context->use & CNTXT_INCMNG){
3216 	    snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3217 		     _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3218 		     folder, (strlen(folder) > 15) ? "..." : "");
3219 	    q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3220 	    return(0);		/* error */
3221 	}
3222 
3223 	snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3224 		 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3225 		 folder, (strlen(folder) > 15) ? "..." : "",
3226 		 context->nickname,
3227 		 (strlen(context->nickname) > 15) ? "..." : "");
3228     }
3229     else
3230       snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3231 	       _("Folder \"%.40s%s\" doesn't exist.  Create"),
3232 	       folder, strlen(folder) > 40 ? "..." : "");
3233 
3234     if(want_to(tmp_20k_buf, 'y', 'n',
3235 	       NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3236 	cmd_cancelled("Save message");
3237 	return(-1);
3238     }
3239 
3240     return(1);
3241 }
3242 
3243 
3244 
3245 /*----------------------------------------------------------------------
3246     Expunge messages from current folder
3247 
3248     Args: state -- pointer to struct holding a bunch of pine state
3249 	 msgmap -- table mapping msg nums to c-client sequence nums
3250 	  qline -- screen line to ask questions on
3251 	    agg -- boolean indicating we're to operate on aggregate set
3252 
3253  Result:
3254  ----*/
3255 int
cmd_expunge(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,int agg)3256 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3257 {
3258     long del_count, prefilter_del_count = 0;
3259     int we_cancel = 0, rv = 0;
3260     char prompt[MAX_SCREEN_COLS+1];
3261     char *sequence;
3262     COLOR_PAIR   *lastc = NULL;
3263 
3264     dprint((2, "\n - expunge -\n"));
3265 
3266     del_count = 0;
3267 
3268     sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3269 
3270     if(MCMD_ISAGG(agg)){
3271 	long i;
3272 	MESSAGECACHE *mc;
3273 	for(i = 1L; i <= stream->nmsgs; i++){
3274 	    if((mc = mail_elt(stream, i)) != NULL
3275 		&& mc->sequence && mc->deleted)
3276 		del_count++;
3277 	}
3278 	if(del_count == 0){
3279 	    q_status_message(SM_ORDER, 0, 4,
3280 			 _("No selected messages are deleted"));
3281 	    return 0;
3282 	}
3283     } else {
3284       if(!any_messages(msgmap, NULL, "to Expunge"))
3285          return rv;
3286     }
3287 
3288     if(IS_NEWS(stream) && stream->rdonly){
3289 	if(!MCMD_ISAGG(agg))
3290 	  del_count = count_flagged(stream, F_DEL);
3291 	if(del_count > 0L){
3292 	    state->mangled_footer = 1;		/* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3293 	    snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3294 		    plural(del_count), MAX_SCREEN_COLS+1-40,
3295 		    pretty_fn(state->cur_folder));
3296 	    prompt[sizeof(prompt)-1] = '\0';
3297 	    if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3298 	       || (F_ON(F_AUTO_EXPUNGE, state)
3299 		   && (state->context_current
3300 		       && (state->context_current->use & CNTXT_INCMNG))
3301 		   && context_isambig(state->cur_folder))
3302 	       || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3303 
3304 		if(F_ON(F_NEWS_CROSS_DELETE, state))
3305 		  cross_delete_crossposts(stream);
3306 
3307 		msgno_exclude_deleted(stream, msgmap, sequence);
3308 		clear_index_cache(stream, 0);
3309 
3310 		/*
3311 		 * This is kind of surprising at first. For most sort
3312 		 * orders, if the whole set is sorted, then any subset
3313 		 * is also sorted. Not so for threaded sorts.
3314 		 */
3315 		if(SORT_IS_THREADED(msgmap))
3316 		  refresh_sort(stream, msgmap, SRT_NON);
3317 
3318 		state->mangled_body = 1;
3319 		state->mangled_header = 1;
3320 		q_status_message2(SM_ORDER, 0, 4,
3321 				  "%s message%s excluded",
3322 				  long2string(del_count),
3323 				  plural(del_count));
3324 	    }
3325 	    else
3326 	      any_messages(NULL, NULL, "Excluded");
3327 	}
3328 	else
3329 	  any_messages(NULL, "deleted", "to Exclude");
3330 
3331 	return del_count;
3332     }
3333     else if(READONLY_FOLDER(stream)){
3334 	q_status_message(SM_ORDER, 0, 4,
3335 			 _("Can't expunge. Folder is read-only"));
3336 	return del_count;
3337     }
3338 
3339     if(!MCMD_ISAGG(agg)){
3340       prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3341       mail_expunge_prefilter(stream, MI_NONE);
3342       del_count = count_flagged(stream, F_DEL|F_NOFILT);
3343     }
3344 
3345     if(del_count != 0){
3346 	int ret;
3347 	unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3348 						/* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3349 	snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3350 		plural(del_count), MAX_SCREEN_COLS+1-40,
3351 		pretty_fn((char *) fname));
3352 	if(fname) fs_give((void **)&fname);
3353 	prompt[sizeof(prompt)-1] = '\0';
3354 	state->mangled_footer = 1;
3355 
3356 	if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3357 	   || (F_ON(F_AUTO_EXPUNGE, state)
3358 	       && ((!strucmp(state->cur_folder,state->inbox_name))
3359 		   || (state->context_current->use & CNTXT_INCMNG))
3360 	       && context_isambig(state->cur_folder))
3361 	   || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3362 	  ret = 'y';
3363 
3364 	if(ret == 'x')
3365 	  cmd_cancelled("Expunge");
3366 
3367 	if(ret != 'y')
3368 	  return 0;
3369     }
3370 
3371     dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3372 	      mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3373 
3374     lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3375 			    state->VAR_TITLE_BACK_COLOR,
3376 			    PSC_REV|PSC_RET);
3377 
3378     PutLine0(0, 0, "**");			/* indicate delay */
3379 
3380     if(lastc){
3381 	(void)pico_set_colorp(lastc, PSC_NONE);
3382 	free_color_pair(&lastc);
3383     }
3384 
3385     MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3386     fflush(stdout);
3387 
3388     we_cancel = busy_cue(_("Expunging"), NULL, 1);
3389 
3390     if(cmd_expunge_work(stream, msgmap, sequence))
3391       state->mangled_body = 1;
3392 
3393     if(sequence)
3394       fs_give((void **)&sequence);
3395 
3396     if(we_cancel)
3397       cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3398 
3399     lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3400 			    state->VAR_TITLE_BACK_COLOR,
3401 			    PSC_REV|PSC_RET);
3402     PutLine0(0, 0, "  ");			/* indicate delay's over */
3403 
3404     if(lastc){
3405 	(void)pico_set_colorp(lastc, PSC_NONE);
3406 	free_color_pair(&lastc);
3407     }
3408 
3409     fflush(stdout);
3410 
3411     if(sp_expunge_count(stream) > 0){
3412 	/*
3413 	 * This is kind of surprising at first. For most sort
3414 	 * orders, if the whole set is sorted, then any subset
3415 	 * is also sorted. Not so for threaded sorts.
3416 	 */
3417 	if(SORT_IS_THREADED(msgmap))
3418 	  refresh_sort(stream, msgmap, SRT_NON);
3419     }
3420     else{
3421 	if(del_count){
3422 	  unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3423 	  q_status_message1(SM_ORDER, 0, 3,
3424 			    _("No messages expunged from folder \"%s\""),
3425 			    pretty_fn((char *) fname));
3426 	  if(fname) fs_give((void **)&fname);
3427 	}
3428 	else if(!prefilter_del_count)
3429 	  q_status_message(SM_ORDER, 0, 3,
3430 		     _("No messages marked deleted.  No messages expunged."));
3431     }
3432     return del_count;
3433 }
3434 
3435 
3436 /*----------------------------------------------------------------------
3437     Expunge_and_close callback to prompt user for confirmation
3438 
3439     Args: stream -- folder's stream
3440 	  folder -- name of folder containing folders
3441 	 deleted -- number of del'd msgs
3442 
3443  Result: 'y' to continue with expunge
3444  ----*/
3445 int
expunge_prompt(MAILSTREAM * stream,char * folder,long int deleted)3446 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3447 {
3448     long  max_folder;
3449     int	  charcnt = 0;
3450     char  prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3451     char *short_folder_name;
3452 
3453     if(deleted == 1)
3454       charcnt = 1;
3455     else{
3456 	snprintf(temp, sizeof(temp), "%ld", deleted);
3457 	charcnt = strlen(temp)+1;
3458     }
3459 
3460     max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3461     strncpy(temp, folder, sizeof(temp));
3462     temp[sizeof(temp)-1] = '\0';
3463     short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3464 
3465     if(IS_NEWS(stream))
3466       snprintf(prompt_b, sizeof(prompt_b),
3467 	       "Delete %s%ld message%s from \"%s\"",
3468 	       (deleted > 1L) ? "all " : "", deleted,
3469 	       plural(deleted), short_folder_name);
3470     else
3471       snprintf(prompt_b, sizeof(prompt_b),
3472 	       "Expunge the %ld deleted message%s from \"%s\"",
3473 	       deleted, deleted == 1 ? "" : "s",
3474 	       short_folder_name);
3475 
3476     return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3477 }
3478 
3479 
3480 /*
3481  * This is used with multiple append saves. Call it once before
3482  * the series of appends with SSCP_INIT and once after all are
3483  * done with SSCP_END. In between, it is called automatically
3484  * from save_fetch_append or save_fetch_append_cb when we need
3485  * to ask the user if he or she wants to continue even though
3486  * announced message size doesn't match the actual message size.
3487  * As of 2008-02-29 the gmail IMAP server has these size mismatches
3488  * on a regular basis even though the data is ok.
3489  */
3490 int
save_size_changed_prompt(long msgno,int flags)3491 save_size_changed_prompt(long msgno, int flags)
3492 {
3493     int ret;
3494     char prompt[100];
3495     static int remember_the_yes = 0;
3496     static int possible_corruption = 0;
3497     static ESCKEY_S save_size_opts[] = {
3498 	{'y', 'y', "Y", "Yes"},
3499 	{'n', 'n', "N", "No"},
3500 	{'a', 'a', "A", "yes to All"},
3501 	{-1, 0, NULL, NULL}
3502     };
3503 
3504     if(F_ON(F_IGNORE_SIZE, ps_global))
3505       return 'y';
3506 
3507     if(flags & SSCP_INIT || flags & SSCP_END){
3508 	if(flags & SSCP_END && possible_corruption)
3509 	  q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3510 
3511 	remember_the_yes = 0;
3512 	possible_corruption = 0;
3513 	ps_global->noshow_error = 0;
3514 	ps_global->noshow_warn = 0;
3515 	return(0);
3516     }
3517 
3518     if(remember_the_yes){
3519 	snprintf(prompt, sizeof(prompt),
3520 		 "Message to save shrank! (msg # %ld): Continuing", msgno);
3521 	q_status_message(SM_ORDER, 0, 3, prompt);
3522 	display_message('x');
3523 	return(remember_the_yes);
3524     }
3525 
3526     snprintf(prompt, sizeof(prompt),
3527 	     "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3528     ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3529 			'n', 0, h_save_size_changed, RB_NORM|RB_NO_NEWMAIL);
3530 
3531     switch(ret){
3532       case 'a':
3533 	remember_the_yes = 'y';
3534 	possible_corruption++;
3535 	return(remember_the_yes);
3536 
3537       case 'y':
3538 	possible_corruption++;
3539 	return('y');
3540 
3541       default:
3542 	possible_corruption = 0;
3543 	ps_global->noshow_error = 1;
3544 	ps_global->noshow_warn = 1;
3545 	break;
3546     }
3547 
3548     return('n');
3549 }
3550 
3551 
3552 /*----------------------------------------------------------------------
3553     Expunge_and_close callback that happens once the decision to expunge
3554      and close has been made and before expunging and closing begins
3555 
3556 
3557     Args: stream -- folder's stream
3558 	  folder -- name of folder containing folders
3559 	 deleted -- number of del'd msgs
3560 
3561  Result: 'y' to continue with expunge
3562  ----*/
3563 void
expunge_and_close_begins(int flags,char * folder)3564 expunge_and_close_begins(int flags, char *folder)
3565 {
3566     if(!(flags & EC_NO_CLOSE)){
3567 	unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3568 	q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3569 	flush_status_messages(1);
3570 	if(fname) fs_give((void **)&fname);
3571     }
3572 }
3573 
3574 
3575 /*----------------------------------------------------------------------
3576     Export a message to a plain file in users home directory
3577 
3578     Args: state -- pointer to struct holding a bunch of pine state
3579 	 msgmap -- table mapping msg nums to c-client sequence nums
3580 	  qline -- screen line to ask questions on
3581 	    agg -- boolean indicating we're to operate on aggregate set
3582 
3583  Result:
3584  ----*/
3585 int
cmd_export(struct pine * state,MSGNO_S * msgmap,int qline,int aopt)3586 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3587 {
3588     char      filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3589     char      nmsgs[80];
3590     int       r, leading_nl, failure = 0, orig_errno = 0, rflags = GER_NONE;
3591     int       flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3592     ENVELOPE *env;
3593     MESSAGECACHE *mc;
3594     BODY     *b;
3595     long      i, count = 0L, start_of_append = 0, rawno;
3596     gf_io_t   pc;
3597     STORE_S  *store;
3598     struct variable *vars = state ? ps_global->vars : NULL;
3599     ESCKEY_S export_opts[5];
3600     static HISTORY_S *history = NULL;
3601 
3602     if(ps_global->restricted){
3603 	q_status_message(SM_ORDER, 0, 3,
3604 	    "Alpine demo can't export messages to files");
3605 	return rv;
3606     }
3607 
3608     if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3609       return rv;
3610 
3611     export_opts[i = 0].ch  = ctrl('T');
3612     export_opts[i].rval	   = 10;
3613     export_opts[i].name	   = "^T";
3614     export_opts[i++].label = N_("To Files");
3615 
3616 #if	!defined(DOS) && !defined(MAC) && !defined(OS2)
3617     if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3618 	export_opts[i].ch      = ctrl('V');
3619 	export_opts[i].rval    = 12;
3620 	export_opts[i].name    = "^V";
3621 	/* TRANSLATORS: this is an abbreviation for Download Messages */
3622 	export_opts[i++].label = N_("Downld Msg");
3623     }
3624 #endif	/* !(DOS || MAC) */
3625 
3626     if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3627 	export_opts[i].ch      =  ctrl('I');
3628 	export_opts[i].rval    = 11;
3629 	export_opts[i].name    = "TAB";
3630 	export_opts[i++].label = N_("Complete");
3631     }
3632 
3633 #if	0
3634     /* Commented out since it's not yet support! */
3635     if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3636 	export_opts[i].ch      = ctrl('X');
3637 	export_opts[i].rval    = 14;
3638 	export_opts[i].name    = "^X";
3639 	export_opts[i++].label = N_("ListMatches");
3640     }
3641 #endif
3642 
3643     /*
3644      * If message has attachments, add a toggle that will allow the user
3645      * to save all of the attachments to a single directory, using the
3646      * names provided with the attachments or part names. What we'll do is
3647      * export the message as usual, and then export the attachments into
3648      * a subdirectory that did not exist before. The subdir will be named
3649      * something based on the name of the file being saved to, but a
3650      * unique, new name.
3651      */
3652     if(!MCMD_ISAGG(aopt)
3653        && state->mail_stream
3654        && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3655        && rawno <= state->mail_stream->nmsgs
3656        && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3657        && b
3658        && b->type == TYPEMULTIPART
3659        && b->subtype
3660        && strucmp(b->subtype, "ALTERNATIVE") != 0){
3661 	PART *part;
3662 
3663 	part = b->nested.part;	/* 1st part */
3664 	if(part && part->next)
3665 	  flags |= GE_ALLPARTS;
3666     }
3667 
3668     export_opts[i].ch = -1;
3669     filename[0] = '\0';
3670 
3671     if(mn_total_cur(msgmap) <= 1L){
3672       snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3673       nmsgs[sizeof(nmsgs)-1] = '\0';
3674     }
3675     else{
3676       snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3677       nmsgs[sizeof(nmsgs)-1] = '\0';
3678     }
3679 
3680     r = get_export_filename(state, filename, NULL, full_filename,
3681 			    sizeof(filename), nmsgs, "EXPORT",
3682 			    export_opts, &rflags, qline, flags, &history);
3683 
3684     if(r < 0){
3685 	switch(r){
3686 	  case -1:
3687 	    cmd_cancelled("Export message");
3688 	    break;
3689 
3690 	  case -2:
3691 	    q_status_message1(SM_ORDER, 0, 2,
3692 			      _("Can't export to file outside of %s"),
3693 			      VAR_OPER_DIR);
3694 	    break;
3695 	}
3696 
3697 	goto fini;
3698     }
3699 #if	!defined(DOS) && !defined(MAC) && !defined(OS2)
3700     else if(r == 12){			/* Download */
3701 	char     cmd[MAXPATH], *tfp = NULL;
3702 	int	     next = 0;
3703 	PIPE_S  *syspipe;
3704 	STORE_S *so;
3705 	gf_io_t  pc;
3706 
3707 	if(ps_global->restricted){
3708 	    q_status_message(SM_ORDER | SM_DING, 3, 3,
3709 			     "Download disallowed in restricted mode");
3710 	    goto fini;
3711 	}
3712 
3713 	err = NULL;
3714 	tfp = temp_nam(NULL, "pd");
3715 	build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3716 			 ps_global->VAR_DOWNLOAD_CMD, tfp);
3717 	dprint((1, "Download cmd called: \"%s\"\n", cmd));
3718 	if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3719 	    gf_set_so_writec(&pc, so);
3720 
3721 	    for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3722 	      if(!(state->mail_stream
3723 		 && (rawno = mn_m2raw(msgmap, i)) > 0L
3724 		 && rawno <= state->mail_stream->nmsgs
3725 		 && (mc = mail_elt(state->mail_stream, rawno))
3726 		 && mc->valid))
3727 	        mc = NULL;
3728 
3729 	      if(!(env = pine_mail_fetchstructure(state->mail_stream,
3730 						  mn_m2raw(msgmap, i), &b))
3731 		 || !bezerk_delimiter(env, mc, pc, next++)
3732 		 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3733 				    env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3734 		  q_status_message(SM_ORDER | SM_DING, 3, 3,
3735 			   err = "Error writing tempfile for download");
3736 		  break;
3737 	      }
3738 	    }
3739 
3740 	    gf_clear_so_writec(so);
3741 	    if(so_give(&so)){			/* close file */
3742 		if(!err)
3743 		  err = "Error writing tempfile for download";
3744 	    }
3745 
3746 	    if(!err){
3747 		if((syspipe = open_system_pipe(cmd, NULL, NULL,
3748 					      PIPE_USER | PIPE_RESET,
3749 					      0, pipe_callback, pipe_report_error)) != NULL)
3750 		  (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3751 		else
3752 		  q_status_message(SM_ORDER | SM_DING, 3, 3,
3753 				err = _("Error running download command"));
3754 	    }
3755 	}
3756 	else
3757 	  q_status_message(SM_ORDER | SM_DING, 3, 3,
3758 			 err = "Error building temp file for download");
3759 
3760 	if(tfp){
3761 	    our_unlink(tfp);
3762 	    fs_give((void **)&tfp);
3763 	}
3764 
3765 	if(!err)
3766 	  q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3767 
3768 	goto fini;
3769     }
3770 #endif	/* !(DOS || MAC) */
3771 
3772 
3773     if(rflags & GER_APPEND)
3774       leading_nl = 1;
3775     else
3776       leading_nl = 0;
3777 
3778     dprint((5, "Opening file \"%s\" for export\n",
3779 	   full_filename ? full_filename : "?"));
3780 
3781     if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3782         q_status_message2(SM_ORDER | SM_DING, 3, 4,
3783 		      /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3784 		      _("Error opening file \"%s\" to export message: %s"),
3785                           full_filename, error_description(errno));
3786 	goto fini;
3787     }
3788     else
3789       gf_set_so_writec(&pc, store);
3790 
3791     err = NULL;
3792     for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3793 	env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3794 				       &b);
3795 	if(!env) {
3796 	    err = _("Can't export message. Error accessing mail folder");
3797 	    failure = 1;
3798 	    break;
3799 	}
3800 
3801         if(!(state->mail_stream
3802 	   && (rawno = mn_m2raw(msgmap, i)) > 0L
3803 	   && rawno <= state->mail_stream->nmsgs
3804 	   && (mc = mail_elt(state->mail_stream, rawno))
3805 	   && mc->valid))
3806 	  mc = NULL;
3807 
3808 	start_of_append = so_tell(store);
3809 	if(!bezerk_delimiter(env, mc, pc, leading_nl)
3810 	   || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3811 			      FM_NEW_MESS | FM_NOWRAP, pc)){
3812 	    orig_errno = errno;		/* save in case things are really bad */
3813 	    failure    = 1;		/* pop out of here */
3814 	    break;
3815 	}
3816 
3817 	leading_nl = 1;
3818     }
3819 
3820     gf_clear_so_writec(store);
3821     if(so_give(&store))				/* release storage */
3822       failure++;
3823 
3824     if(failure){
3825 	our_truncate(full_filename, (off_t)start_of_append);
3826 	if(err){
3827 	    dprint((1, "FAILED Export: fetch(%ld): %s\n",
3828 		       i, err ? err : "?"));
3829 	    q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3830 	}
3831 	else{
3832 	    dprint((1, "FAILED Export: file \"%s\" : %s\n",
3833 		       full_filename ? full_filename : "?",
3834 		       error_description(orig_errno)));
3835 	    q_status_message2(SM_ORDER | SM_DING, 3, 4,
3836 			      /* TRANSLATORS: Error exporting to <filename>: <error text> */
3837 			      _("Error exporting to \"%s\" : %s"),
3838 			      filename, error_description(orig_errno));
3839 	}
3840     }
3841     else{
3842 	if(rflags & GER_ALLPARTS && full_filename[0]){
3843 	    char dir[MAXPATH+1];
3844 	    char  lfile[MAXPATH+1];
3845 	    int  ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3846 	    ATTACH_S *a;
3847 
3848 	    /*
3849 	     * Now we want to save all of the attachments to a subdirectory.
3850 	     * To make it easier for us and probably easier for the user, and
3851 	     * to prevent the user from shooting himself in the foot, we
3852 	     * make a new subdirectory so that we can't possibly step on
3853 	     * any existing files, and we don't need any interaction with the
3854 	     * user while saving.
3855 	     *
3856 	     * We'll just use the directory name full_filename.d or if that
3857 	     * already exists and isn't empty, we'll try adding a suffix to
3858 	     * that until we get something to use.
3859 	     */
3860 
3861 	    if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3862 		q_status_message1(SM_ORDER | SM_DING, 3, 4,
3863 			  _("Can't save attachments, filename too long: %s"),
3864 			  full_filename);
3865 		goto fini;
3866 	    }
3867 
3868 	    ok = 0;
3869 	    snprintf(dir, sizeof(dir), "%.*s.d", MAXPATH-2, full_filename);
3870 	    dir[sizeof(dir)-1] = '\0';
3871 
3872 	    do {
3873 		tries++;
3874 		switch(r = is_writable_dir(dir)){
3875 		  case 0:		/* exists and is a writable dir */
3876 		    /*
3877 		     * We could figure out if it is empty and use it in
3878 		     * that case, but that sounds like a lot of work, so
3879 		     * just fall through to default.
3880 		     */
3881 
3882 		  default:
3883 		    if(strlen(full_filename) + strlen(".d") + 1 +
3884 		       1 + strlen(long2string((long) tries)) > sizeof(dir)){
3885 			q_status_message(SM_ORDER | SM_DING, 3, 4,
3886 					      "Problem saving attachments");
3887 			goto fini;
3888 		    }
3889 
3890 		    snprintf(dir, sizeof(dir), "%.*s.d_%s", MAXPATH- (int) strlen(long2string((long) tries))-3, full_filename,
3891 			    long2string((long) tries));
3892 		    dir[sizeof(dir)-1] = '\0';
3893 		    break;
3894 
3895 		  case 3:		/* doesn't exist, that's good! */
3896 		    /* make new directory */
3897 		    ok++;
3898 		    break;
3899 		}
3900 	    } while(!ok && tries < 1000);
3901 
3902 	    if(tries >= 1000){
3903 		q_status_message(SM_ORDER | SM_DING, 3, 4,
3904 					      _("Problem saving attachments"));
3905 		goto fini;
3906 	    }
3907 
3908 	    /* create the new directory */
3909 	    if(our_mkdir(dir, 0700)){
3910 		q_status_message2(SM_ORDER | SM_DING, 3, 4,
3911 		      _("Problem saving attachments: %s: %s"), dir,
3912 		      error_description(errno));
3913 		goto fini;
3914 	    }
3915 
3916 	    if(!(state->mail_stream
3917 	         && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3918 	         && rawno <= state->mail_stream->nmsgs
3919 	         && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3920 	         && b)){
3921 		q_status_message(SM_ORDER | SM_DING, 3, 4,
3922 					      _("Problem reading message"));
3923 		goto fini;
3924 	    }
3925 
3926 	    zero_atmts(state->atmts);
3927 	    describe_mime(b, "", 1, 1, 0, 0);
3928 
3929 	    a = state->atmts;
3930 	    if(a && a->description)		/* skip main body part */
3931 	      a++;
3932 
3933 	    for(; a->description != NULL; a++){
3934 		/* skip over these parts of the message */
3935 		if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3936 		  continue;
3937 
3938 		lfile[0] = '\0';
3939 		(void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3940 
3941 		if(lfile[0] == '\0'){		/* MAXPATH + 1 = sizeof(lfile) */
3942 		  snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3943 			  a->number ? a->number : "?");
3944 		  lfile[sizeof(lfile)-1] = '\0';
3945 		}
3946 
3947 		if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3948 							    > sizeof(filename)){
3949 		    dprint((2,
3950 			   "FAILED Att Export: name too long: %s\n",
3951 			   dir, S_FILESEP, lfile));
3952 		    errs++;
3953 		    continue;
3954 		}
3955 
3956 		/* although files are being saved in a unique directory, there is
3957 		 * no guarantee that attachment names have unique names, so we have
3958 		 * to make sure that we are not constantly rewriting the same file name
3959 		 * over and over. In order to avoid this we test if the file already exists,
3960 		 * and if so, we write a counter name in the file name, just before the
3961 		 * extension of the file, and separate it with an underscore.
3962 		 */
3963 		snprintf(filename, sizeof(filename), "%.*s%.*s%.*s", (int) strlen(dir), dir,
3964 			(int) strlen(S_FILESEP), S_FILESEP,
3965 			MAXPATH - (int) strlen(dir) - (int) strlen(S_FILESEP), lfile);
3966 		filename[sizeof(filename)-1] = '\0';
3967 		while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3968 		   char *ext, count[MAXPATH+1];
3969 		   unsigned long total;
3970 		   snprintf(count, sizeof(count), "%d", counter);
3971 		   if((ext = strrchr(lfile, '.')) != NULL)
3972 		      *ext = '\0';
3973 		   total = strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(count) + 3
3974 				+ (ext ? strlen(ext+1) : 0);
3975 		   if(total > sizeof(filename)){
3976 		      dprint((2,
3977 			   "FAILED Att Export: name too long: %s\n",
3978 			   dir, S_FILESEP, lfile));
3979 		      errs++;
3980 		      continue;
3981 		   }
3982 		   snprintf(filename, sizeof(filename), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
3983 			(int) strlen(dir), dir, (int) strlen(S_FILESEP), S_FILESEP,
3984 			(int) strlen(lfile), lfile,
3985 			ext ? 1 : 0, ext ? "_" : "",
3986 			(int) strlen(count), counter++,
3987 			ext ? 1 : 0, ext ? "." : "",
3988 			ext ? (int) (sizeof(filename) - total) : 0,
3989 			ext ? ext+1 : "");
3990 		   filename[sizeof(filename)-1] = '\0';
3991 		}
3992 
3993 		if(write_attachment_to_file(state->mail_stream, rawno,
3994 					    a, GER_NONE, filename) == 1)
3995 		  saved++;
3996 		else
3997 		  errs++;
3998 	    }
3999 
4000 	    if(errs){
4001 		if(saved)
4002 		  q_status_message1(SM_ORDER, 3, 3,
4003 			"Errors saving some attachments, %s attachments saved",
4004 			long2string((long) saved));
4005 		else
4006 		  q_status_message(SM_ORDER, 3, 3,
4007 			_("Problems saving attachments"));
4008 	    }
4009 	    else{
4010 		if(saved)
4011 		  q_status_message2(SM_ORDER, 0, 3,
4012 			/* TRANSLATORS: Saved <how many> attachments to <directory name> */
4013 			_("Saved %s attachments to %s"),
4014 			long2string((long) saved), dir);
4015 		else
4016 		  q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4017 	    }
4018 	}
4019 	else if(mn_total_cur(msgmap) > 1L)
4020 	  q_status_message4(SM_ORDER,0,3,
4021 			    "%s message%s %s to file \"%s\"",
4022 			    long2string(count), plural(count),
4023 			    rflags & GER_OVER
4024 			      ? "overwritten"
4025 			      : rflags & GER_APPEND ? "appended" : "exported",
4026 			    filename);
4027 	else
4028 	  q_status_message3(SM_ORDER,0,3,
4029 			    "Message %s %s to file \"%s\"",
4030 			    long2string(mn_get_cur(msgmap)),
4031 			    rflags & GER_OVER
4032 			      ? "overwritten"
4033 			      : rflags & GER_APPEND ? "appended" : "exported",
4034 			    filename);
4035 	rv++;
4036     }
4037 
4038   fini:
4039     if(MCMD_ISAGG(aopt))
4040       restore_selected(msgmap);
4041 
4042     return rv;
4043 }
4044 
4045 
4046 /*
4047  * Ask user what file to export to. Export from srcstore to that file.
4048  *
4049  * Args     ps -- pine struct
4050  *     srctext -- pointer to source text
4051  *     srctype -- type of that source text
4052  *  prompt_msg -- see get_export_filename
4053  *  lister_msg --      "
4054  *
4055  * Returns: != 0 : error
4056  *             0 : ok
4057  */
4058 int
simple_export(struct pine * ps,void * srctext,SourceType srctype,char * prompt_msg,char * lister_msg)4059 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4060 {
4061     int r = 1, rflags = GER_NONE;
4062     char     filename[MAXPATH+1], full_filename[MAXPATH+1];
4063     STORE_S *store = NULL;
4064     struct variable *vars = ps ? ps->vars : NULL;
4065     static HISTORY_S *history = NULL;
4066     static ESCKEY_S simple_export_opts[] = {
4067 	{ctrl('T'), 10, "^T", N_("To Files")},
4068 	{-1, 0, NULL, NULL},
4069 	{-1, 0, NULL, NULL}};
4070 
4071     if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4072 	simple_export_opts[r].ch    =  ctrl('I');
4073 	simple_export_opts[r].rval  = 11;
4074 	simple_export_opts[r].name  = "TAB";
4075 	simple_export_opts[r].label = N_("Complete");
4076     }
4077 
4078     if(!srctext){
4079 	q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4080 	r = -3;
4081 	goto fini;
4082     }
4083 
4084     simple_export_opts[++r].ch = -1;
4085     filename[0] = '\0';
4086     full_filename[0] = '\0';
4087 
4088     r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4089 			    prompt_msg, lister_msg, simple_export_opts, &rflags,
4090 			    -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4091 
4092     if(r < 0)
4093       goto fini;
4094     else if(!full_filename[0]){
4095 	r = -1;
4096 	goto fini;
4097     }
4098 
4099     dprint((5, "Opening file \"%s\" for export\n",
4100 	   full_filename ? full_filename : "?"));
4101 
4102     if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4103 	char *pipe_err;
4104 	gf_io_t pc, gc;
4105 
4106 	gf_set_so_writec(&pc, store);
4107 	gf_set_readc(&gc, srctext, (srctype == CharStar)
4108 					? strlen((char *)srctext)
4109 					: 0L,
4110 		     srctype, 0);
4111 	gf_filter_init();
4112 	if((pipe_err = gf_pipe(gc, pc)) != NULL){
4113 	    q_status_message2(SM_ORDER | SM_DING, 3, 3,
4114 			      /* TRANSLATORS: Problem saving to <filename>: <error text> */
4115 			      _("Problem saving to \"%s\": %s"),
4116 			      filename, pipe_err);
4117 	    r = -3;
4118 	}
4119 	else
4120 	  r = 0;
4121 
4122 	gf_clear_so_writec(store);
4123 	if(so_give(&store)){
4124 	    q_status_message2(SM_ORDER | SM_DING, 3, 3,
4125 			      _("Problem saving to \"%s\": %s"),
4126 			      filename, error_description(errno));
4127 	    r = -3;
4128 	}
4129     }
4130     else{
4131 	q_status_message2(SM_ORDER | SM_DING, 3, 4,
4132 			  _("Error opening file \"%s\" for export: %s"),
4133 			  full_filename, error_description(errno));
4134 	r = -3;
4135     }
4136 
4137 fini:
4138     switch(r){
4139       case  0:
4140 	/* overloading full_filename */
4141 	snprintf(full_filename, sizeof(full_filename), "%c%s",
4142 		(prompt_msg && prompt_msg[0])
4143 		  ? (islower((unsigned char)prompt_msg[0])
4144 		    ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4145 		  : 'T',
4146 	        (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4147 	full_filename[sizeof(full_filename)-1] = '\0';
4148 	q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4149 			  full_filename,
4150 			  rflags & GER_OVER
4151 			      ? "overwritten"
4152 			      : rflags & GER_APPEND ? "appended" : "exported",
4153 			  filename);
4154 	break;
4155 
4156       case -1:
4157 	cmd_cancelled("Export");
4158 	break;
4159 
4160       case -2:
4161 	q_status_message1(SM_ORDER, 0, 2,
4162 	    _("Can't export to file outside of %s"), VAR_OPER_DIR);
4163 	break;
4164     }
4165 
4166     ps->mangled_footer = 1;
4167     return(r);
4168 }
4169 
4170 
4171 /*
4172  * Ask user what file to export to.
4173  *
4174  *       filename -- On input, this is the filename to start with. On exit,
4175  *                   this is the filename chosen. (but this isn't used)
4176  *       deefault -- This is the default value if user hits return. The
4177  *                   prompt will have [deefault] added to it automatically.
4178  *  full_filename -- This is the full filename on exit.
4179  *            len -- Minimum length of _both_ filename and full_filename.
4180  *     prompt_msg -- Message to insert in prompt.
4181  *     lister_msg -- Message to insert in file_lister.
4182  *           opts -- Key options.
4183  *                      There is a tangled relationship between the callers
4184  *                      and this routine as far as opts are concerned. Some
4185  *                      of the opts are handled here. In particular, r == 3,
4186  *                      r == 10, r == 11, and r == 13 are all handled here.
4187  *                      Don't use those values unless you want what happens
4188  *                      here. r == 12 and others are handled by the caller.
4189  *         rflags -- Return flags
4190  *                     GER_OVER      - overwrite of existing file
4191  *                     GER_APPEND    - append of existing file
4192  *                      else file did not exist before
4193  *
4194  *                     GER_ALLPARTS  - AllParts toggle was turned on
4195  *
4196  *          qline -- Command line to prompt on.
4197  *          flags -- Logically OR'd flags
4198  *                     GE_IS_EXPORT     - The command was an Export command
4199  *                                        so the prompt should include
4200  *                                        EXPORT:.
4201  *                     GE_SEQ_SENSITIVE - The command that got us here is
4202  *                                        sensitive to sequence number changes
4203  *                                        caused by unsolicited expunges.
4204  *                     GE_NO_APPEND     - We will not allow append to an
4205  *                                        existing file, only removal of the
4206  *                                        file if it exists.
4207  *                     GE_IS_IMPORT     - We are selecting for reading.
4208  *                                        No overwriting or checking for
4209  *                                        existence at all. Don't use this
4210  *                                        together with GE_NO_APPEND.
4211  *                     GE_ALLPARTS      - Turn on AllParts toggle.
4212  *                     GE_BINARY        - Turn on Binary toggle.
4213  *
4214  *  Returns:  -1  cancelled
4215  *            -2  prohibited by VAR_OPER_DIR
4216  *            -3  other error, already reported here
4217  *             0  ok
4218  *            12  user chose 12 command from opts
4219  */
4220 int
get_export_filename(struct pine * ps,char * filename,char * deefault,char * full_filename,size_t len,char * prompt_msg,char * lister_msg,ESCKEY_S * optsarg,int * rflags,int qline,int flags,HISTORY_S ** history)4221 get_export_filename(struct pine *ps, char *filename, char *deefault,
4222 		    char *full_filename, size_t len, char *prompt_msg,
4223 		    char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4224 		    int qline, int flags, HISTORY_S **history)
4225 {
4226     char      dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4227     char      precolon[MAXPATH+1], postcolon[MAXPATH+1];
4228     char      filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4229     int       l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4230     int       allparts = 0, binary = 0;
4231     char      prompt_buf[400];
4232     char      def[500];
4233     ESCKEY_S *opts = NULL;
4234     struct variable *vars = ps->vars;
4235     static HISTORY_S *dir_hist = NULL;
4236     static char *last;
4237     int pos, hist_len = 0;
4238 
4239 
4240     /* we will fake a history with the ps_global->VAR_HISTORY variable
4241      * We fake that we combine this variable into a history variable
4242      * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4243      * by looking at the variable pos.
4244      */
4245     if(ps_global->VAR_HISTORY != NULL)
4246       for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4247 			&& ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4248 	;
4249 
4250     pos = hist_len + items_in_hist(dir_hist);
4251 
4252     if(flags & GE_ALLPARTS || history || dir_hist){
4253 	/*
4254 	 * Copy the opts and add one to the end of the list.
4255 	 */
4256 	for(i = 0; optsarg[i].ch != -1; i++)
4257 	  ;
4258 
4259 	if(dir_hist || hist_len > 0)
4260 	  i += 2;
4261 
4262 	if(history)
4263 	  i += dir_hist || hist_len > 0 ? 2 : 4;
4264 
4265 	if(flags & GE_ALLPARTS)
4266 	  i++;
4267 
4268 	if(flags & GE_BINARY)
4269 	  i++;
4270 
4271 	opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4272 	memset(opts, 0, (i+1) * sizeof(*opts));
4273 
4274 	for(i = 0; optsarg[i].ch != -1; i++){
4275 	    opts[i].ch = optsarg[i].ch;
4276 	    opts[i].rval = optsarg[i].rval;
4277 	    opts[i].name = optsarg[i].name;	/* no need to make a copy */
4278 	    opts[i].label = optsarg[i].label;	/* " */
4279 	}
4280 
4281 	if(flags & GE_ALLPARTS){
4282 	    allparts = i;
4283 	    opts[i].ch      = ctrl('P');
4284 	    opts[i].rval    = 13;
4285 	    opts[i].name    = "^P";
4286 	    /* TRANSLATORS: Export all attachment parts */
4287 	    opts[i++].label = N_("AllParts");
4288 	}
4289 
4290 	if(flags & GE_BINARY){
4291 	    binary = i;
4292 	    opts[i].ch      = ctrl('R');
4293 	    opts[i].rval    = 15;
4294 	    opts[i].name    = "^R";
4295 	    opts[i++].label = N_("Binary");
4296 	}
4297 
4298 	rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4299                                                   SIZEOF_20KBUF, filename);
4300 #ifndef _WINDOWS
4301 	/* In the Windows operating system we always return the UTF8 encoded name */
4302 	if(strcmp(tmp_20k_buf, filename)){
4303 	    opts[i].ch      = ctrl('N');
4304 	    opts[i].rval    = 40;
4305 	    opts[i].name    = "^N";
4306 	    opts[i++].label = "Name UTF8";
4307 	}
4308 #else
4309         strncpy(filename, tmp_20k_buf, len);
4310         filename[len-1] = '\0';
4311 #endif	/* _WINDOWS */
4312 
4313 	if(dir_hist || hist_len > 0){
4314 	    opts[i].ch      = ctrl('Y');
4315 	    opts[i].rval    = 32;
4316 	    opts[i].name    = "";
4317 	    kp = i;
4318 	    opts[i++].label = "";
4319 
4320 	    opts[i].ch      = ctrl('V');
4321 	    opts[i].rval    = 33;
4322 	    opts[i].name    = "";
4323 	    opts[i++].label = "";
4324 	}
4325 
4326 	if(history){
4327 	    opts[i].ch      = KEY_UP;
4328 	    opts[i].rval    = 30;
4329 	    opts[i].name    = "";
4330 	    ku = i;
4331 	    opts[i++].label = "";
4332 
4333 	    opts[i].ch      = KEY_DOWN;
4334 	    opts[i].rval    = 31;
4335 	    opts[i].name    = "";
4336 	    opts[i++].label = "";
4337 	}
4338 
4339 	opts[i].ch = -1;
4340 
4341 	if(history)
4342 	  init_hist(history, HISTSIZE);
4343 	init_hist(&dir_hist, HISTSIZE);	/* reset history to the end */
4344     }
4345     else
4346       opts = optsarg;
4347 
4348     if(rflags)
4349       *rflags = GER_NONE;
4350 
4351     if(F_ON(F_USE_CURRENT_DIR, ps))
4352       dir[0] = '\0';
4353     else if(VAR_OPER_DIR){
4354       strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4355       dir[sizeof(dir)-1] = '\0';
4356     }
4357 #if	defined(DOS) || defined(OS2)
4358     else if(VAR_FILE_DIR){
4359       strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4360       dir[sizeof(dir)-1] = '\0';
4361     }
4362 #endif
4363     else{
4364 	dir[0] = '~';
4365 	dir[1] = '\0';
4366 	homedir=1;
4367     }
4368     strncpy(orig_dir, dir, sizeof(orig_dir));
4369     orig_dir[sizeof(orig_dir)-1] = '\0';
4370 
4371     postcolon[0] = '\0';
4372     strncpy(precolon, dir, sizeof(precolon));
4373     precolon[sizeof(precolon)-1] = '\0';
4374     if(deefault){
4375 	strncpy(def, deefault, sizeof(def)-1);
4376 	def[sizeof(def)-1] = '\0';
4377 	removing_leading_and_trailing_white_space(def);
4378     }
4379     else
4380       def[0] = '\0';
4381 
4382     avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4383 
4384     /*---------- Prompt the user for the file name -------------*/
4385     while(1){
4386 	int  oeflags;
4387 	char dirb[50], fileb[50];
4388 	int  l1, l2, l3, l4, l5, needed;
4389 	char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4390 
4391 	snprintf(p1, sizeof(p1), "%sCopy ",
4392 		(flags & GE_IS_EXPORT) ? "EXPORT: " :
4393 		  (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4394 	p1[sizeof(p1)-1] = '\0';
4395 	l1 = strlen(p1);
4396 
4397 	strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4398 	p2[sizeof(p2)-1] = '\0';
4399 	l2 = strlen(p2);
4400 
4401 	if(rflags && *rflags & GER_ALLPARTS)
4402 	  p3 = " (and atts)";
4403 	else
4404 	  p3 = "";
4405 
4406 	l3 = strlen(p3);
4407 
4408 	snprintf(p4, sizeof(p4), " %s file%s%s",
4409 		(flags & GE_IS_IMPORT) ? "from" : "to",
4410 		is_absolute_path(filename) ? "" : " in ",
4411 		is_absolute_path(filename) ? "" :
4412 		  (!dir[0] ? "current directory"
4413 			   : (dir[0] == '~' && !dir[1]) ? "home directory"
4414 				     : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4415 	p4[sizeof(p4)-1] = '\0';
4416 	l4 = strlen(p4);
4417 
4418 	snprintf(p5, sizeof(p5), "%s%s%s: ",
4419 		*def ? " [" : "",
4420 		*def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4421 		*def ? "]" : "");
4422 	p5[sizeof(p5)-1] = '\0';
4423 	l5 = strlen(p5);
4424 
4425 	if((needed = l1+l2+l3+l4+l5-avail) > 0){
4426 	    snprintf(p4, sizeof(p4), " %s file%s%s",
4427 		    (flags & GE_IS_IMPORT) ? "from" : "to",
4428 		    is_absolute_path(filename) ? "" : " in ",
4429 		    is_absolute_path(filename) ? "" :
4430 		      (!dir[0] ? "current dir"
4431 			       : (dir[0] == '~' && !dir[1]) ? "home dir"
4432 					 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4433 	    p4[sizeof(p4)-1] = '\0';
4434 	    l4 = strlen(p4);
4435 	}
4436 
4437 	if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4438 	    snprintf(p5, sizeof(p5), "%s%s%s: ",
4439 		    *def ? " [" : "",
4440 		    *def ? short_str(def,fileb,sizeof(fileb),
4441 				     MAX(15,l5-5-needed),EndDots) : "",
4442 		    *def ? "]" : "");
4443 	    p5[sizeof(p5)-1] = '\0';
4444 	    l5 = strlen(p5);
4445 	}
4446 
4447 	if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4448 
4449 	    /*
4450 	     * 14 is about the shortest we can make this, because there are
4451 	     * fixed length strings of length 14 coming in here.
4452 	     */
4453 	    p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4454 	    if(p != p2){
4455 		strncpy(p2, p, sizeof(p2)-1);
4456 		p2[sizeof(p2)-1] = '\0';
4457 	    }
4458 
4459 	    l2 = strlen(p2);
4460 	}
4461 
4462 	if((needed = l1+l2+l3+l4+l5-avail) > 0){
4463 	    strncpy(p1, "Copy ", sizeof(p1)-1);
4464 	    p1[sizeof(p1)-1] = '\0';
4465 	    l1 = strlen(p1);
4466 	}
4467 
4468 	if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4469 	    snprintf(p5, sizeof(p5), "%s%s%s: ",
4470 		    *def ? " [" : "",
4471 		    *def ? short_str(def,fileb, sizeof(fileb),
4472 				     MAX(10,l5-5-needed),EndDots) : "",
4473 		    *def ? "]" : "");
4474 	    p5[sizeof(p5)-1] = '\0';
4475 	    l5 = strlen(p5);
4476 	}
4477 
4478 	if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4479 	    if(needed <= l3 - strlen(" (+ atts)"))
4480 	      p3 = " (+ atts)";
4481 	    else if(needed <= l3 - strlen(" (atts)"))
4482 	      p3 = " (atts)";
4483 	    else if(needed <= l3 - strlen(" (+)"))
4484 	      p3 = " (+)";
4485 	    else if(needed <= l3 - strlen("+"))
4486 	      p3 = "+";
4487 	    else
4488 	      p3 = "";
4489 
4490 	    l3 = strlen(p3);
4491 	}
4492 
4493 	snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4494 	prompt_buf[sizeof(prompt_buf)-1] = '\0';
4495 
4496 	if(kp >= 0){
4497 	   if(items_in_hist(dir_hist) > 0 || hist_len > 0){  /* any directories */
4498 	      opts[kp].name  = "^Y";
4499 	      opts[kp].label = "Prev Dir";
4500 	      opts[kp+1].name  = "^V";
4501 	      opts[kp+1].label = "Next Dir";
4502 	   }
4503 	   else{
4504 	      opts[kp].name  = "";
4505 	      opts[kp].label = "";
4506 	      opts[kp+1].name  = "";
4507 	      opts[kp+1].label = "";
4508 	   }
4509 	}
4510 
4511 	if(ku >= 0){
4512 	    if(items_in_hist(*history) > 0){
4513 		opts[ku].name  = HISTORY_UP_KEYNAME;
4514 		opts[ku].label = HISTORY_KEYLABEL;
4515 		opts[ku+1].name  = HISTORY_DOWN_KEYNAME;
4516 		opts[ku+1].label = HISTORY_KEYLABEL;
4517 	    }
4518 	    else{
4519 		opts[ku].name  = "";
4520 		opts[ku].label = "";
4521 		opts[ku+1].name  = "";
4522 		opts[ku+1].label = "";
4523 	    }
4524 	}
4525 
4526 	oeflags = OE_APPEND_CURRENT |
4527 		  ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4528 	r = optionally_enter(filename, qline, 0, len, prompt_buf,
4529 			     opts, NO_HELP, &oeflags);
4530 
4531         dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4532         /*--- Help ----*/
4533 	if(r == 3){
4534 	    /*
4535 	     * Helps may not be right if you add another caller or change
4536 	     * things. Check it out.
4537 	     */
4538 	    if(flags & GE_IS_IMPORT)
4539 	      helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4540 	    else if(flags & GE_ALLPARTS)
4541 	      helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4542 	    else
4543 	      helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4544 
4545 	    ps->mangled_screen = 1;
4546 
4547 	    continue;
4548         }
4549 	else if(r == 10 || r == 11){	/* Browser or File Completion */
4550 	    if(filename[0]=='~'){
4551 	      if(filename[1] == C_FILESEP && filename[2]!='\0'){
4552 		precolon[0] = '~';
4553 		precolon[1] = '\0';
4554 		for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4555 		  filename[i] = filename[i+2];
4556 		filename[i] = '\0';
4557 		strncpy(dir, precolon, sizeof(dir)-1);
4558 		dir[sizeof(dir)-1] = '\0';
4559 	      }
4560 	      else if(filename[1]=='\0' ||
4561 		 (filename[1] == C_FILESEP && filename[2] == '\0')){
4562 		precolon[0] = '~';
4563 		precolon[1] = '\0';
4564 		filename[0] = '\0';
4565 		strncpy(dir, precolon, sizeof(dir)-1);
4566 		dir[sizeof(dir)-1] = '\0';
4567 	      }
4568 	    }
4569 	    else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4570 	      if(homedir){
4571 		precolon[0] = '~';
4572 		precolon[1] = '\0';
4573 		strncpy(dir, precolon, sizeof(dir)-1);
4574 		dir[sizeof(dir)-1] = '\0';
4575 	      }
4576 	      else{
4577 		precolon[0] = '\0';
4578 		dir[0] = '\0';
4579 	      }
4580 	    }
4581 	    l = MAXPATH;
4582 	    dir2[0] = '\0';
4583 	    strncpy(tmp, filename, sizeof(tmp)-1);
4584 	    tmp[sizeof(tmp)-1] = '\0';
4585 	    if(*tmp && is_absolute_path(tmp))
4586 	      fnexpand(tmp, sizeof(tmp));
4587 	    if(strncmp(tmp,postcolon, strlen(postcolon)))
4588 	      postcolon[0] = '\0';
4589 
4590 	    if(*tmp && (fn = last_cmpnt(tmp))){
4591 	        l -= fn - tmp;
4592 		strncpy(filename2, fn, sizeof(filename2)-1);
4593 		filename2[sizeof(filename2)-1] = '\0';
4594 		if(is_absolute_path(tmp)){
4595 		    strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4596 		    dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4597 #ifdef _WINDOWS
4598 		    if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4599 		      dir2[2] = '\\';
4600 		      dir2[3] = '\0';
4601 		    }
4602 #endif
4603 		    strncpy(postcolon, dir2, sizeof(postcolon)-1);
4604 		    postcolon[sizeof(postcolon)-1] = '\0';
4605 		    precolon[0] = '\0';
4606 		}
4607 		else{
4608 		    char *p = NULL;
4609 		    /*
4610 		     * Just building the directory name in dir2,
4611 		     * full_filename is overloaded.
4612 		     */
4613 		    snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4614 		    full_filename[len-1] = '\0';
4615 		    strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4616 		    postcolon[sizeof(postcolon)-1] = '\0';
4617 		    build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4618 					     : (dir[0] == '~' && !dir[1])
4619 					       ? ps->home_dir
4620 					       : dir,
4621 			       full_filename, sizeof(dir2));
4622 		    if(p)
4623 		      free(p);
4624 		}
4625 	    }
4626 	    else{
4627 		if(is_absolute_path(tmp)){
4628 		    strncpy(dir2, tmp, sizeof(dir2)-1);
4629 		    dir2[sizeof(dir2)-1] = '\0';
4630 #ifdef _WINDOWS
4631 		    if(dir2[2]=='\0' && dir2[1]==':'){
4632 		      dir2[2]='\\';
4633 		      dir2[3]='\0';
4634 		      strncpy(postcolon,dir2,sizeof(postcolon)-1);
4635 		      postcolon[sizeof(postcolon)-1] = '\0';
4636 		    }
4637 #endif
4638 		    filename2[0] = '\0';
4639 		    precolon[0] = '\0';
4640 		}
4641 		else{
4642 		    strncpy(filename2, tmp, sizeof(filename2)-1);
4643 		    filename2[sizeof(filename2)-1] = '\0';
4644 		    if(!dir[0]){
4645 		       if(getcwd(dir2, sizeof(dir2)) == NULL)
4646 			  alpine_panic(_("getcwd() call failed at get_export_filename"));
4647 		    }
4648 		    else if(dir[0] == '~' && !dir[1]){
4649 			strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4650 			dir2[sizeof(dir2)-1] = '\0';
4651 		    }
4652 		    else{
4653 			strncpy(dir2, dir, sizeof(dir2)-1);
4654 			dir2[sizeof(dir2)-1] = '\0';
4655 		    }
4656 
4657 		    postcolon[0] = '\0';
4658 		}
4659 	    }
4660 
4661 	    build_path(full_filename, dir2, filename2, len);
4662 	    if(!strcmp(full_filename, dir2))
4663 	      filename2[0] = '\0';
4664 	    if(full_filename[strlen(full_filename)-1] == C_FILESEP
4665 	       && isdir(full_filename,NULL,NULL)){
4666 	      if(strlen(full_filename) == 1)
4667 		strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4668 	      else if(filename2[0])
4669 		strncpy(postcolon, filename2, sizeof(postcolon)-1);
4670 	      postcolon[sizeof(postcolon)-1] = '\0';
4671 	      strncpy(dir2, full_filename, sizeof(dir2)-1);
4672 	      dir2[sizeof(dir2)-1] = '\0';
4673 	      filename2[0] = '\0';
4674 	    }
4675 #ifdef _WINDOWS  /* use full_filename even if not a valid directory */
4676 	    else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4677 	      strncpy(postcolon, filename2, sizeof(postcolon)-1);
4678 	      postcolon[sizeof(postcolon)-1] = '\0';
4679 	      strncpy(dir2, full_filename, sizeof(dir2)-1);
4680 	      dir2[sizeof(dir2)-1] = '\0';
4681 	      filename2[0] = '\0';
4682 	    }
4683 #endif
4684 	    if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4685 	       && strcmp(dir2+1, ":\\"))
4686 	      /* last condition to prevent stripping of '\\'
4687 		 in windows partition */
4688 	      dir2[strlen(dir2)-1] = '\0';
4689 
4690 	    if(r == 10){			/* File Browser */
4691 		r = file_lister(lister_msg ? lister_msg : "EXPORT",
4692 				dir2, sizeof(dir2), filename2, sizeof(filename2),
4693                                 TRUE,
4694 				(flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4695 #ifdef _WINDOWS
4696 /* Windows has a special "feature" in which entering the file browser will
4697    change the working directory if the directory is changed at all (even
4698    clicking "Cancel" will change the working directory).
4699 */
4700 		if(F_ON(F_USE_CURRENT_DIR, ps))
4701 		  (void)getcwd(dir2,sizeof(dir2));
4702 #endif
4703 		if(isdir(dir2,NULL,NULL)){
4704 		  strncpy(precolon, dir2, sizeof(precolon)-1);
4705 		  precolon[sizeof(precolon)-1] = '\0';
4706 		}
4707 		strncpy(postcolon, filename2, sizeof(postcolon)-1);
4708 		postcolon[sizeof(postcolon)-1] = '\0';
4709 		if(r == 1){
4710 		    build_path(full_filename, dir2, filename2, len);
4711 		    if(isdir(full_filename, NULL, NULL)){
4712 			strncpy(dir, full_filename, sizeof(dir)-1);
4713 			dir[sizeof(dir)-1] = '\0';
4714 			filename[0] = '\0';
4715 		    }
4716 		    else{
4717 			fn = last_cmpnt(full_filename);
4718 			strncpy(dir, full_filename,
4719 				MIN(fn - full_filename, sizeof(dir)-1));
4720 			dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4721 			if(fn - full_filename > 1)
4722 			  dir[fn - full_filename - 1] = '\0';
4723 		    }
4724 
4725 		    if(!strcmp(dir, ps->home_dir)){
4726 			dir[0] = '~';
4727 			dir[1] = '\0';
4728 		    }
4729 
4730 		    strncpy(filename, fn, len-1);
4731 		    filename[len-1] = '\0';
4732 		}
4733 	    }
4734 	    else{				/* File Completion */
4735 	      if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4736 		  Writechar(BELL, 0);
4737 	      strncat(postcolon, filename2,
4738 		      sizeof(postcolon)-1-strlen(postcolon));
4739 	      postcolon[sizeof(postcolon)-1] = '\0';
4740 
4741 	      was_abs_path = is_absolute_path(filename);
4742 
4743 	      if(!strcmp(dir, ps->home_dir)){
4744 		dir[0] = '~';
4745 		dir[1] = '\0';
4746 	      }
4747 	    }
4748 	    strncpy(filename, postcolon, len-1);
4749 	    filename[len-1] = '\0';
4750 	    strncpy(dir, precolon, sizeof(dir)-1);
4751 	    dir[sizeof(dir)-1] = '\0';
4752 
4753 	    if(filename[0] == '~' && !filename[1]){
4754 		dir[0] = '~';
4755 		dir[1] = '\0';
4756 		filename[0] = '\0';
4757 	    }
4758 
4759 	    continue;
4760 	}
4761 	else if(r == 12){	/* Download, caller handles it */
4762 	    ret = r;
4763 	    goto done;
4764 	}
4765 	else if(r == 13){	/* toggle AllParts bit */
4766 	    if(rflags){
4767 		if(*rflags & GER_ALLPARTS){
4768 		    *rflags &= ~GER_ALLPARTS;
4769 		    opts[allparts].label = N_("AllParts");
4770 		}
4771 		else{
4772 		    *rflags |=  GER_ALLPARTS;
4773 		    /* opposite of All Parts, No All Parts */
4774 		    opts[allparts].label = N_("NoAllParts");
4775 		}
4776 	    }
4777 
4778 	    continue;
4779 	}
4780 #if	0
4781 	else if(r == 14){	/* List file names matching partial? */
4782 	    continue;
4783 	}
4784 #endif
4785 	else if(r == 15){	/* toggle Binary bit */
4786 	    if(rflags){
4787 		if(*rflags & GER_BINARY){
4788 		    *rflags &= ~GER_BINARY;
4789 		    opts[binary].label = N_("Binary");
4790 		}
4791 		else{
4792 		    *rflags |=  GER_BINARY;
4793 		    opts[binary].label = N_("No Binary");
4794 		}
4795 	    }
4796 
4797 	    continue;
4798 	}
4799         else if(r == 1){	/* Cancel */
4800 	    ret = -1;
4801 	    goto done;
4802         }
4803         else if(r == 4){
4804 	    continue;
4805 	}
4806 	else if(r >= 30 && r <= 33){
4807 	    char *p = NULL;
4808 
4809 	    if(r == 30 || r == 31){
4810 	      if(history){
4811 		if(r == 30)
4812 		   p = get_prev_hist(*history, filename, 0, NULL);
4813 		else if (r == 31)
4814 		   p = get_next_hist(*history, filename, 0, NULL);
4815 	      }
4816 	    }
4817 
4818 	    if(r == 32 || r == 33){
4819 	      int nitems = items_in_hist(dir_hist);
4820 	      if(dir_hist || hist_len > 0){
4821 		if(r == 32){
4822 		   if(pos > 0)
4823 		     p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4824 		   else p = last;
4825 		}
4826 		else if (r == 33){
4827 		   if(pos < hist_len + nitems)
4828 		     p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4829 		}
4830 		if(p == NULL || *p == '\0')
4831 		   p = orig_dir;
4832 	      }
4833 	    }
4834 	    last = p;		/* save it! */
4835 
4836 	    if(p != NULL && *p != '\0'){
4837 		if(r == 30 || r == 31){
4838 		  if((fn = last_cmpnt(p)) != NULL){
4839 		    strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4840 		    dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4841 		    if(fn - p > 1)
4842 		      dir[fn - p - 1] = '\0';
4843 		    strncpy(filename, fn, len-1);
4844 		    filename[len-1] = '\0';
4845 		  }
4846 		} else {	/* r == 32 || r == 33 */
4847 		  strncpy(dir, p, sizeof(dir)-1);
4848 		  dir[sizeof(dir)-1] = '\0';
4849 		}
4850 
4851 		if(!strcmp(dir, ps->home_dir)){
4852 		    dir[0] = '~';
4853 		    dir[1] = '\0';
4854 		}
4855 	    }
4856 	    else
4857 	      Writechar(BELL, 0);
4858 	    continue;
4859 	}
4860 #ifndef _WINDOWS
4861 	else if(r == 40){
4862 	    rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4863                                                   SIZEOF_20KBUF, filename);
4864 	    strncpy(filename, tmp_20k_buf, len);
4865 	    filename[len-1] = '\0';
4866 	    continue;
4867 	}
4868 #endif /* _WINDOWS */
4869 	else if(r != 0){
4870 	    Writechar(BELL, 0);
4871 	    continue;
4872 	}
4873 
4874         removing_leading_and_trailing_white_space(filename);
4875 
4876 	if(!*filename){
4877 	    if(!*def){		/* Cancel */
4878 		ret = -1;
4879 		goto done;
4880 	    }
4881 
4882 	    strncpy(filename, def, len-1);
4883 	    filename[len-1] = '\0';
4884 	}
4885 
4886 #if	defined(DOS) || defined(OS2)
4887 	if(is_absolute_path(filename)){
4888 	    fixpath(filename, len);
4889 	}
4890 #else
4891 	if(filename[0] == '~'){
4892 	    if(fnexpand(filename, len) == NULL){
4893 		char *p = strindex(filename, '/');
4894 		if(p != NULL)
4895 		  *p = '\0';
4896 		q_status_message1(SM_ORDER | SM_DING, 3, 3,
4897 			  _("Error expanding file name: \"%s\" unknown user"),
4898 			      filename);
4899 		continue;
4900 	    }
4901 	}
4902 #endif
4903 
4904 	if(is_absolute_path(filename)){
4905 	    strncpy(full_filename, filename, len-1);
4906 	    full_filename[len-1] = '\0';
4907 	}
4908 	else{
4909 	    if(!dir[0])
4910 	      build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4911 			 filename, len);
4912 	    else if(dir[0] == '~' && !dir[1])
4913 	      build_path(full_filename, ps->home_dir, filename, len);
4914 	    else
4915 	      build_path(full_filename, dir, filename, len);
4916 	}
4917 
4918         if((ill = filter_filename(full_filename, &fatal,
4919 				  ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4920 	    if(fatal){
4921 		q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4922 		continue;
4923 	    }
4924 	    else{
4925 /* BUG: we should beep when the key's pressed rather than bitch later */
4926 		/* Warn and ask for confirmation. */
4927 		snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'.  %s anyway",
4928 			ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4929 		prompt_buf[sizeof(prompt_buf)-1] = '\0';
4930 		if(want_to(prompt_buf, 'n', 0, NO_HELP,
4931 		  ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4932 		  continue;
4933 	    }
4934 	}
4935 
4936 	break;		/* Must have got an OK file name */
4937     }
4938 
4939     if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4940 	ret = -2;
4941 	goto done;
4942     }
4943 
4944     if(!can_access(full_filename, ACCESS_EXISTS)){
4945 	int rbflags;
4946 	static ESCKEY_S access_opts[] = {
4947 	    /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4948 	       a file or append to the end of the file */
4949 	    {'o', 'o', "O", N_("Overwrite")},
4950 	    {'a', 'a', "A", N_("Append")},
4951 	    {-1, 0, NULL, NULL}};
4952 
4953 	rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4954 
4955 	if(flags & GE_NO_APPEND){
4956 	    r = strlen(filename);
4957 	    snprintf(prompt_buf, sizeof(prompt_buf),
4958 		   /* TRANSLATORS: asking user whether to overwrite a file or not,
4959 		      File <filename> already exists. Overwrite it ? */
4960 		   _("File \"%s%s\" already exists. Overwrite it "),
4961 		   (r > 20) ? "..." : "",
4962 		   filename + ((r > 20) ? r - 20 : 0));
4963 	    prompt_buf[sizeof(prompt_buf)-1] = '\0';
4964 	    if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4965 		if(rflags)
4966 		  *rflags |= GER_OVER;
4967 
4968 		if(our_unlink(full_filename) < 0){
4969 		    q_status_message2(SM_ORDER | SM_DING, 3, 5,
4970 				  /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4971 				  _("Cannot remove old %s: %s"),
4972 				  full_filename, error_description(errno));
4973 		}
4974 	    }
4975 	    else{
4976 		ret = -1;
4977 		goto done;
4978 	    }
4979 	}
4980 	else if(!(flags & GE_IS_IMPORT)){
4981 	    r = strlen(filename);
4982 	    snprintf(prompt_buf, sizeof(prompt_buf),
4983 		   /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4984 		   _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4985 		   (r > 20) ? "..." : "",
4986 		   filename + ((r > 20) ? r - 20 : 0));
4987 	    prompt_buf[sizeof(prompt_buf)-1] = '\0';
4988 	    switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4989 				 access_opts, 'a', 'x', NO_HELP, rbflags)){
4990 	      case 'o' :
4991 		if(rflags)
4992 		  *rflags |= GER_OVER;
4993 
4994 		if(our_truncate(full_filename, (off_t)0) < 0)
4995 		  /* trouble truncating, but we'll give it a try anyway */
4996 		  q_status_message2(SM_ORDER | SM_DING, 3, 5,
4997 				  /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4998 				  _("Warning: Cannot truncate old %s: %s"),
4999 				  full_filename, error_description(errno));
5000 		break;
5001 
5002 	      case 'a' :
5003 		if(rflags)
5004 		  *rflags |= GER_APPEND;
5005 
5006 		break;
5007 
5008 	      case 'x' :
5009 	      default :
5010 		ret = -1;
5011 		goto done;
5012 	    }
5013 	}
5014     }
5015 
5016 done:
5017     if(history && ret == 0){
5018       save_hist(*history, full_filename, 0, NULL);
5019       strncpy(tmp, full_filename, MAXPATH);
5020       tmp[MAXPATH] = '\0';
5021       if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5022 	*fn = '\0';
5023       else
5024 	tmp[0] = '\0';
5025       if(tmp[0])
5026         save_hist(dir_hist, tmp, 0, NULL);
5027     }
5028 
5029     if(opts && opts != optsarg)
5030       fs_give((void **) &opts);
5031 
5032     return(ret);
5033 }
5034 
5035 
5036 /*----------------------------------------------------------------------
5037   parse the config'd upload/download command
5038 
5039   Args: cmd -- buffer to return command fit for shellin'
5040 	prefix --
5041 	cfg_str --
5042 	fname -- file name to build into the command
5043 
5044   Returns: pointer to cmd_str buffer or NULL on real bad error
5045 
5046   NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5047 	cfg_str is written to standard out right before a successful
5048 	return of this function.  The call immediately following this
5049 	function darn well better be the shell exec...
5050  ----*/
5051 char *
build_updown_cmd(char * cmd,size_t cmdlen,char * prefix,char * cfg_str,char * fname)5052 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5053 {
5054     char *p;
5055     int   fname_found = 0;
5056 
5057     if(prefix && *prefix){
5058 	/* loop thru replacing all occurrences of _FILE_ */
5059 	p = strncpy(cmd, prefix, cmdlen);
5060 	cmd[cmdlen-1] = '\0';
5061 	while((p = strstr(p, "_FILE_")))
5062 	  rplstr(p, cmdlen-(p-cmd), 6, fname);
5063 
5064 	fputs(cmd, stdout);
5065     }
5066 
5067     /* loop thru replacing all occurrences of _FILE_ */
5068     p = strncpy(cmd, cfg_str, cmdlen);
5069     cmd[cmdlen-1] = '\0';
5070     while((p = strstr(p, "_FILE_"))){
5071 	rplstr(p, cmdlen-(p-cmd), 6, fname);
5072 	fname_found = 1;
5073     }
5074 
5075     if(!fname_found)
5076       snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5077 
5078     cmd[cmdlen-1] = '\0';
5079 
5080     dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5081 	   cmd ? cmd : "?"));
5082     return(cmd);
5083 }
5084 
5085 
5086 /*----------------------------------------------------------------------
5087   Write a berzerk format message delimiter using the given putc function
5088 
5089     Args: e -- envelope of message to write
5090 	  pc -- function to use
5091 
5092     Returns: TRUE if we could write it, FALSE if there was a problem
5093 
5094     NOTE: follows delimiter with OS-dependent newline
5095  ----*/
5096 int
bezerk_delimiter(ENVELOPE * env,MESSAGECACHE * mc,gf_io_t pc,int leading_newline)5097 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5098 {
5099     MESSAGECACHE telt;
5100     time_t       when;
5101     char        *p;
5102 
5103     /* write "[\n]From mailbox[@host] " */
5104     if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5105 	 && gf_puts("From ", pc)
5106 	 && gf_puts((env && env->from) ? env->from->mailbox
5107 				       : "the-concourse-on-high", pc)
5108 	 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5109 	 && gf_puts((env && env->from && env->from->host) ? env->from->host
5110 							  : "", pc)
5111 	 && (*pc)(' ')))
5112       return(0);
5113 
5114     if(mc && mc->valid)
5115       when = mail_longdate(mc);
5116     else if(env && env->date && env->date[0]
5117 	    && mail_parse_date(&telt,env->date))
5118       when = mail_longdate(&telt);
5119     else
5120       when = time(0);
5121 
5122     p = ctime(&when);
5123 
5124     while(p && *p && *p != '\n')	/* write date */
5125       if(!(*pc)(*p++))
5126 	return(0);
5127 
5128     if(!gf_puts(NEWLINE, pc))		/* write terminating newline */
5129       return(0);
5130 
5131     return(1);
5132 }
5133 
5134 
5135 /*----------------------------------------------------------------------
5136       Execute command to jump to a given message number
5137 
5138     Args: qline -- Line to ask question on
5139 
5140   Result: returns true if the use selected a new message, false otherwise
5141 
5142  ----*/
5143 long
jump_to(MSGNO_S * msgmap,int qline,UCS first_num,SCROLL_S * sparms,CmdWhere in_index)5144 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5145 {
5146     char     jump_num_string[80], *j, prompt[70];
5147     HelpType help;
5148     int      rc;
5149     static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5150 				      /* TRANSLATORS: go to First Message */
5151 				      {ctrl('Y'), 10, "^Y", N_("First Msg")},
5152 				      {ctrl('V'), 11, "^V", N_("Last Msg")},
5153 				      {-1, 0, NULL, NULL} };
5154 
5155     dprint((4, "\n - jump_to -\n"));
5156 
5157 #ifdef DEBUG
5158     if(sparms && sparms->jump_is_debug)
5159       return(get_level(qline, first_num, sparms));
5160 #endif
5161 
5162     if(!any_messages(msgmap, NULL, "to Jump to"))
5163       return(0L);
5164 
5165     if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5166 	jump_num_string[0] = first_num;
5167 	jump_num_string[1] = '\0';
5168     }
5169     else
5170       jump_num_string[0] = '\0';
5171 
5172     if(mn_total_cur(msgmap) > 1L){
5173 	snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5174 		comatose(mn_total_cur(msgmap)));
5175 	prompt[sizeof(prompt)-1] = '\0';
5176 	if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5177 	  return(0L);
5178     }
5179 
5180     snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5181 						    ? "Thread"
5182 						    : "Message");
5183     prompt[sizeof(prompt)-1] = '\0';
5184 
5185     help = NO_HELP;
5186     while(1){
5187 	int flags = OE_APPEND_CURRENT;
5188 
5189         rc = optionally_enter(jump_num_string, qline, 0,
5190                               sizeof(jump_num_string), prompt,
5191                               jump_to_key, help, &flags);
5192         if(rc == 3){
5193             help = help == NO_HELP
5194 			? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5195 			: NO_HELP;
5196             continue;
5197         }
5198 	else if(rc == 10 || rc == 11){
5199 	    char warning[100];
5200 	    long closest;
5201 
5202 	    closest = closest_jump_target(rc == 10 ? 1L
5203 					  : ((in_index == ThrdIndx)
5204 					     ? msgmap->max_thrdno
5205 					     : mn_get_total(msgmap)),
5206 					  ps_global->mail_stream,
5207 					  msgmap, 0,
5208 					  in_index, warning, sizeof(warning));
5209 	    /* ignore warning */
5210 	    return(closest);
5211 	}
5212 
5213 	/*
5214 	 * If we take out the *jump_num_string nonempty test in this if
5215 	 * then the closest_jump_target routine will offer a jump to the
5216 	 * last message. However, it is slow because you have to wait for
5217 	 * the status message and it is annoying for people who hit J command
5218 	 * by mistake and just want to hit return to do nothing, like has
5219 	 * always worked. So the test is there for now. Hubert 2002-08-19
5220 	 *
5221 	 * Jumping to first/last message is now possible through ^Y/^V
5222 	 * commands above. jpf 2002-08-21
5223 	 * (and through "end" hubert 2006-07-07)
5224 	 */
5225         if(rc == 0 && *jump_num_string != '\0'){
5226 	    removing_leading_and_trailing_white_space(jump_num_string);
5227             for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5228 	      ;
5229 
5230 	    if(*j != '\0'){
5231 		if(!strucmp("end", j))
5232 		  return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5233 
5234 	        q_status_message(SM_ORDER | SM_DING, 2, 2,
5235                            _("Invalid number entered. Use only digits 0-9"));
5236 		jump_num_string[0] = '\0';
5237 	    }
5238 	    else{
5239 		char warning[100];
5240 		long closest, jump_num;
5241 
5242 		if(*jump_num_string)
5243 		  jump_num = atol(jump_num_string);
5244 		else
5245 		  jump_num = -1L;
5246 
5247 		warning[0] = '\0';
5248 		closest = closest_jump_target(jump_num, ps_global->mail_stream,
5249 					      msgmap,
5250 					      *jump_num_string ? 0 : 1,
5251 					      in_index, warning, sizeof(warning));
5252 		if(warning[0])
5253 		  q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5254 
5255 		if(closest == jump_num)
5256 		  return(jump_num);
5257 
5258 		if(closest == 0L)
5259 		  jump_num_string[0] = '\0';
5260 		else
5261 		  strncpy(jump_num_string, long2string(closest),
5262 			  sizeof(jump_num_string));
5263             }
5264 
5265             continue;
5266 	}
5267 
5268         if(rc != 4)
5269           break;
5270     }
5271 
5272     return(0L);
5273 }
5274 
5275 
5276 /*
5277  * cmd_delete_action - handle msgno advance and such after single message deletion
5278  */
5279 char *
cmd_delete_action(struct pine * state,MSGNO_S * msgmap,CmdWhere in_index)5280 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5281 {
5282     int	  opts;
5283     long  msgno;
5284     char *rv = NULL;
5285 
5286     msgno = mn_get_cur(msgmap);
5287     advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5288 
5289     if(IS_NEWS(state->mail_stream)
5290        || ((state->context_current->use & CNTXT_INCMNG)
5291 	   && context_isambig(state->cur_folder))){
5292 
5293 	opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5294 	if(in_index == View)
5295 	  opts &= ~NSF_SKIP_CHID;
5296 
5297 	(void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5298 	if(!(opts & NSF_FLAG_MATCH)){
5299 	    char nextfolder[MAXPATH];
5300 
5301 	    strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5302 	    nextfolder[sizeof(nextfolder)-1] = '\0';
5303 	    rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5304 			     state->context_current, NULL, NULL)
5305 		   ? ".  Press TAB for next folder."
5306 		   : ".  No more folders to TAB to.";
5307 	}
5308     }
5309 
5310     return(rv);
5311 }
5312 
5313 
5314 /*
5315  * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5316  */
5317 char *
cmd_delete_index(struct pine * state,MSGNO_S * msgmap)5318 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5319 {
5320     return(cmd_delete_action(state, msgmap,MsgIndx));
5321 }
5322 
5323 /*
5324  * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5325  */
5326 char *
cmd_delete_view(struct pine * state,MSGNO_S * msgmap)5327 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5328 {
5329     return(cmd_delete_action(state, msgmap, View));
5330 }
5331 
5332 
5333 void
advance_cur_after_delete(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,CmdWhere in_index)5334 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5335 {
5336     long new_msgno, msgno;
5337     int  opts;
5338 
5339     new_msgno = msgno = mn_get_cur(msgmap);
5340     opts = NSF_TRUST_FLAGS;
5341 
5342     if(F_ON(F_DEL_SKIPS_DEL, state)){
5343 
5344 	if(THREADING() && sp_viewing_a_thread(stream))
5345 	  opts |= NSF_SKIP_CHID;
5346 
5347 	new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5348     }
5349     else{
5350 	mn_inc_cur(stream, msgmap,
5351 		   (in_index == View && THREADING()
5352 		    && sp_viewing_a_thread(stream))
5353 		     ? MH_THISTHD
5354 		     : (in_index == View)
5355 		       ? MH_ANYTHD : MH_NONE);
5356 	new_msgno = mn_get_cur(msgmap);
5357 	if(new_msgno != msgno)
5358 	  opts |= NSF_FLAG_MATCH;
5359     }
5360 
5361     /*
5362      * Viewing_a_thread is the complicated case because we want to ignore
5363      * other threads at first and then look in other threads if we have to.
5364      * By ignoring other threads we also ignore collapsed partial threads
5365      * in our own thread.
5366      */
5367     if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5368 	long rawno, orig_thrdno;
5369 	PINETHRD_S *thrd, *topthrd = NULL;
5370 
5371 	rawno = mn_m2raw(msgmap, msgno);
5372 	thrd  = fetch_thread(stream, rawno);
5373 	if(thrd && thrd->top)
5374 	  topthrd = fetch_thread(stream, thrd->top);
5375 
5376 	orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5377 
5378 	opts = NSF_TRUST_FLAGS;
5379 	new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5380 
5381 	/*
5382 	 * If we got a match, new_msgno may be a message in
5383 	 * a different thread from the one we are viewing, or it could be
5384 	 * in a collapsed part of this thread.
5385 	 */
5386 	if(opts & NSF_FLAG_MATCH){
5387 	    int         ret;
5388 	    char        pmt[128];
5389 
5390 	    topthrd = NULL;
5391 	    thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5392 	    if(thrd && thrd->top)
5393 	      topthrd = fetch_thread(stream, thrd->top);
5394 
5395 	    /*
5396 	     * If this match is in the same thread we're already in
5397 	     * then we're done, else we have to ask the user and maybe
5398 	     * switch threads.
5399 	     */
5400 	    if(!(orig_thrdno > 0L && topthrd
5401 		 && topthrd->thrdno == orig_thrdno)){
5402 
5403 		if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5404 		    if(in_index == View)
5405 		      snprintf(pmt, sizeof(pmt),
5406 			     "View message in thread number %.10s",
5407 			     topthrd ? comatose(topthrd->thrdno) : "?");
5408 		    else
5409 		      snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5410 			     topthrd ? comatose(topthrd->thrdno) : "?");
5411 
5412 		    ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5413 		}
5414 		else
5415 		  ret = 'y';
5416 
5417 		if(ret == 'y'){
5418 		    unview_thread(state, stream, msgmap);
5419 		    mn_set_cur(msgmap, new_msgno);
5420 		    if(THRD_AUTO_VIEW()
5421 		       && (count_lflags_in_thread(stream, topthrd, msgmap,
5422 						  MN_NONE) == 1)
5423 		       && view_thread(state, stream, msgmap, 1)){
5424 			if(current_index_state)
5425 			  msgmap->top_after_thrd = current_index_state->msg_at_top;
5426 
5427 			state->view_skipped_index = 1;
5428 			state->next_screen = mail_view_screen;
5429 		    }
5430 		    else{
5431 			view_thread(state, stream, msgmap, 1);
5432 			if(current_index_state)
5433 			  msgmap->top_after_thrd = current_index_state->msg_at_top;
5434 
5435 			state->next_screen = SCREEN_FUN_NULL;
5436 		    }
5437 		}
5438 		else
5439 		  new_msgno = msgno;	/* stick with original */
5440 	    }
5441 	}
5442     }
5443 
5444     mn_set_cur(msgmap, new_msgno);
5445     if(in_index != View)
5446       adjust_cur_to_visible(stream, msgmap);
5447 }
5448 
5449 
5450 #ifdef DEBUG
5451 long
get_level(int qline,UCS first_num,SCROLL_S * sparms)5452 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5453 {
5454     char     debug_num_string[80], *j, prompt[70];
5455     HelpType help;
5456     int      rc;
5457     long     debug_num;
5458 
5459     if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5460 	debug_num_string[0] = first_num;
5461 	debug_num_string[1] = '\0';
5462 	debug_num = atol(debug_num_string);
5463 	*(int *)(sparms->proc.data.p) = debug_num;
5464 	q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5465 			  comatose(debug_num));
5466 	return(1L);
5467     }
5468     else
5469       debug_num_string[0] = '\0';
5470 
5471     snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5472     prompt[sizeof(prompt)-1] = '\0';
5473 
5474     help = NO_HELP;
5475     while(1){
5476 	int flags = OE_APPEND_CURRENT;
5477 
5478         rc = optionally_enter(debug_num_string, qline, 0,
5479                               sizeof(debug_num_string), prompt,
5480                               NULL, help, &flags);
5481         if(rc == 3){
5482             help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5483             continue;
5484         }
5485 
5486         if(rc == 0){
5487 	    removing_leading_and_trailing_white_space(debug_num_string);
5488             for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5489 	      ;
5490 
5491 	    if(*j != '\0'){
5492 	        q_status_message(SM_ORDER | SM_DING, 2, 2,
5493                            _("Invalid number entered. Use only digits 0-9"));
5494 		debug_num_string[0] = '\0';
5495 	    }
5496 	    else{
5497 		debug_num = atol(debug_num_string);
5498 		if(debug_num < 0)
5499 	          q_status_message(SM_ORDER | SM_DING, 2, 2,
5500 				   _("Number should be >= 0"));
5501 		else if(debug_num > MAX(debug,9))
5502 	          q_status_message1(SM_ORDER | SM_DING, 2, 2,
5503 				   _("Maximum is %s"), comatose(MAX(debug,9)));
5504 		else{
5505 		    *(int *)(sparms->proc.data.p) = debug_num;
5506 		    q_status_message1(SM_ORDER, 0, 3,
5507 				      "Show debug <= level %s",
5508 				      comatose(debug_num));
5509 		    return(1L);
5510 		}
5511             }
5512 
5513             continue;
5514 	}
5515 
5516         if(rc != 4)
5517           break;
5518     }
5519 
5520     return(0L);
5521 }
5522 #endif /* DEBUG */
5523 
5524 
5525 /*
5526  * Returns the message number closest to target that isn't hidden.
5527  * Make warning at least 100 chars.
5528  * A return of 0 means there is no message to jump to.
5529  */
5530 long
closest_jump_target(long int target,MAILSTREAM * stream,MSGNO_S * msgmap,int no_target,CmdWhere in_index,char * warning,size_t warninglen)5531 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5532 {
5533     long i, start, closest = 0L;
5534     char buf[80];
5535     long maxnum;
5536 
5537     warning[0] = '\0';
5538     maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5539 
5540     if(no_target){
5541 	target = maxnum;
5542 	start = 1L;
5543 	snprintf(warning, warninglen,  "No %s number entered, jump to end? ",
5544 		(in_index == ThrdIndx) ? "thread" : "message");
5545 	warning[warninglen-1] = '\0';
5546     }
5547     else if(target < 1L)
5548       start = 1L - target;
5549     else if(target > maxnum)
5550       start = target - maxnum;
5551     else
5552       start = 1L;
5553 
5554     if(target > 0L && target <= maxnum)
5555       if(in_index == ThrdIndx
5556 	 || !msgline_hidden(stream, msgmap, target, 0))
5557 	return(target);
5558 
5559     for(i = start; target+i <= maxnum || target-i > 0L; i++){
5560 
5561 	if(target+i > 0L && target+i <= maxnum &&
5562 	   (in_index == ThrdIndx
5563 	    || !msgline_hidden(stream, msgmap, target+i, 0))){
5564 	    closest = target+i;
5565 	    break;
5566 	}
5567 
5568 	if(target-i > 0L && target-i <= maxnum &&
5569 	   (in_index == ThrdIndx
5570 	    || !msgline_hidden(stream, msgmap, target-i, 0))){
5571 	    closest = target-i;
5572 	    break;
5573 	}
5574     }
5575 
5576     strncpy(buf, long2string(closest), sizeof(buf));
5577     buf[sizeof(buf)-1] = '\0';
5578 
5579     if(closest == 0L)
5580       strncpy(warning, "Nothing to jump to", warninglen);
5581     else if(target < 1L)
5582       snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5583 	      (in_index == ThrdIndx) ? "Thread" : "Message",
5584 	      long2string(target), buf);
5585     else if(target > maxnum)
5586       snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5587 	      (in_index == ThrdIndx) ? "Thread" : "Message",
5588 	      long2string(target), buf);
5589     else if(!no_target)
5590       snprintf(warning, warninglen,
5591 	"Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5592 	long2string(target), buf);
5593 
5594     warning[warninglen-1] = '\0';
5595 
5596     return(closest);
5597 }
5598 
5599 
5600 /*----------------------------------------------------------------------
5601      Prompt for folder name to open, expand the name and return it
5602 
5603    Args: qline      -- Screen line to prompt on
5604          allow_list -- if 1, allow ^T to bring up collection lister
5605 
5606  Result: returns the folder name or NULL
5607          pine structure mangled_footer flag is set
5608          may call the collection lister in which case mangled screen will be set
5609 
5610  This prompts the user for the folder to open, possibly calling up
5611 the collection lister if the user types ^T.
5612 ----------------------------------------------------------------------*/
5613 char *
broach_folder(int qline,int allow_list,int * notrealinbox,CONTEXT_S ** context)5614 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5615 {
5616     HelpType	help;
5617     static char newfolder[MAILTMPLEN];
5618     char        expanded[MAXPATH+1],
5619                 prompt[MAX_SCREEN_COLS+1],
5620                *last_folder, *p;
5621     unsigned char *f1, *f2, *f3;
5622     static HISTORY_S *history = NULL;
5623     CONTEXT_S  *tc, *tc2;
5624     ESCKEY_S    ekey[9];
5625     int		rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5626 
5627     /*
5628      * the idea is to provide a clue for the context the file name
5629      * will be saved in (if a non-imap names is typed), and to
5630      * only show the previous if it was also in the same context
5631      */
5632     help	   = NO_HELP;
5633     *expanded	   = '\0';
5634     *newfolder	   = '\0';
5635     last_folder	   = NULL;
5636     if(notrealinbox)
5637       (*notrealinbox) = 1;
5638 
5639     init_hist(&history, HISTSIZE);
5640 
5641     tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5642 
5643     /* set up extra command option keys */
5644     rc = 0;
5645     ekey[rc].ch	     = (allow_list) ? ctrl('T') : 0 ;
5646     ekey[rc].rval    = (allow_list) ? 2 : 0;
5647     ekey[rc].name    = (allow_list) ? "^T" : "";
5648     ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5649 
5650     if(ps_global->context_list->next){
5651 	ekey[rc].ch      = ctrl('P');
5652 	ekey[rc].rval    = 10;
5653 	ekey[rc].name    = "^P";
5654 	ekey[rc++].label = N_("Prev Collection");
5655 
5656 	ekey[rc].ch      = ctrl('N');
5657 	ekey[rc].rval    = 11;
5658 	ekey[rc].name    = "^N";
5659 	ekey[rc++].label = N_("Next Collection");
5660     }
5661 
5662     ekey[rc].ch      = ctrl('W');
5663     ekey[rc].rval    = 17;
5664     ekey[rc].name    = "^W";
5665     ekey[rc++].label = N_("INBOX");
5666 
5667     if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5668 	ekey[rc].ch      = TAB;
5669 	ekey[rc].rval    = 12;
5670 	ekey[rc].name    = "TAB";
5671 	ekey[rc++].label = N_("Complete");
5672     }
5673 
5674     if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5675 	ekey[rc].ch      = ctrl('X');
5676 	ekey[rc].rval    = 14;
5677 	ekey[rc].name    = "^X";
5678 	ekey[rc++].label = N_("ListMatches");
5679     }
5680 
5681     if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5682 	ekey[rc].ch      = KEY_UP;
5683 	ekey[rc].rval    = 10;
5684 	ekey[rc].name    = "";
5685 	ekey[rc++].label = "";
5686 
5687 	ekey[rc].ch      = KEY_DOWN;
5688 	ekey[rc].rval    = 11;
5689 	ekey[rc].name    = "";
5690 	ekey[rc++].label = "";
5691     }
5692     else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5693 	ekey[rc].ch      = KEY_UP;
5694 	ekey[rc].rval    = 30;
5695 	ekey[rc].name    = "";
5696 	ku = rc;
5697 	ekey[rc++].label = "";
5698 
5699 	ekey[rc].ch      = KEY_DOWN;
5700 	ekey[rc].rval    = 31;
5701 	ekey[rc].name    = "";
5702 	ekey[rc++].label = "";
5703     }
5704 
5705     ekey[rc].ch = -1;
5706 
5707     while(!done) {
5708 	/*
5709 	 * Figure out next default value for this context.  The idea
5710 	 * is that in each context the last folder opened is cached.
5711 	 * It's up to pick it out and display it.  This is fine
5712 	 * and dandy if we've currently got the inbox open, BUT
5713 	 * if not, make the inbox the default the first time thru.
5714 	 */
5715 	if(!inbox){
5716 	    last_folder = ps_global->inbox_name;
5717 	    inbox = 1;		/* pretend we're in inbox from here on out */
5718 	}
5719 	else
5720 	  last_folder = (ps_global->last_unambig_folder[0])
5721 			  ? ps_global->last_unambig_folder
5722 			  : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5723 
5724 	if(last_folder){		/* MAXPATH + 1 = sizeof(expanded) */
5725 	  unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5726 	  snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5727 					fname ? (char *) fname : last_folder);
5728 	  if(fname) fs_give((void **)&fname);
5729 	}
5730 	else
5731 	  *expanded = '\0';
5732 
5733 	expanded[sizeof(expanded)-1] = '\0';
5734 
5735 	/* only show collection number if more than one available */
5736 	if(ps_global->context_list->next)	/* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5737 	  snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5738 		    NEWS_TEST(tc) ? "news group" : "folder",
5739 		    tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5740 		    *expanded ? " " : "");
5741 	else					/* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5742 	  snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5743 		  *expanded ? " " : "");
5744 
5745 	prompt[sizeof(prompt)-1] = '\0';
5746 
5747 	if(utf8_width(prompt) > MAXPROMPT){
5748 	    if(ps_global->context_list->next)	/* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5749 	      snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5750 			tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5751 			*expanded ? " " : "");
5752 	    else				/* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5753 	      snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5754 		      *expanded ? " " : "");
5755 
5756 	    prompt[sizeof(prompt)-1] = '\0';
5757 
5758 	    if(utf8_width(prompt) > MAXPROMPT){
5759 		if(ps_global->context_list->next)	/* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5760 		  snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5761 			    tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5762 			    *expanded ? " " : "");
5763 		else				/* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5764 		  snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5765 			  *expanded ? " " : "");
5766 
5767 		prompt[sizeof(prompt)-1] = '\0';
5768 	    }
5769 	}
5770 
5771 	if(ku >= 0){
5772 	    if(items_in_hist(history) > 1){
5773 		ekey[ku].name  = HISTORY_UP_KEYNAME;
5774 		ekey[ku].label = HISTORY_KEYLABEL;
5775 		ekey[ku+1].name  = HISTORY_DOWN_KEYNAME;
5776 		ekey[ku+1].label = HISTORY_KEYLABEL;
5777 	    }
5778 	    else{
5779 		ekey[ku].name  = "";
5780 		ekey[ku].label = "";
5781 		ekey[ku+1].name  = "";
5782 		ekey[ku+1].label = "";
5783 	    }
5784 	}
5785 
5786 	/* is there any other way to do this? The point is that we
5787 	 * are trying to hide mutf7 from the user, and use the utf8
5788 	 * equivalent. So we create a variable f to take place of
5789 	 * newfolder, including content and size. f2 is copy of f1
5790 	 * that has to freed. Sigh!
5791 	 */
5792 	f3 = (unsigned char *) cpystr(newfolder);
5793 	f1 = fs_get(sizeof(newfolder));
5794 	f2 = folder_name_decoded(f3);
5795 	if(f3) fs_give((void **)&f3);
5796 	strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5797 	f1[sizeof(newfolder)-1] = '\0';
5798 	if(f2) fs_give((void **)&f2);
5799 
5800 	flags = OE_APPEND_CURRENT;
5801         rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5802 			      (char *) prompt, ekey, help, &flags);
5803 
5804 	f2 = folder_name_encoded(f1);
5805 	strncpy(newfolder, (char *)f2, sizeof(newfolder));
5806 	if(f1) fs_give((void **)&f1);
5807 	if(f2) fs_give((void **)&f2);
5808 
5809 	ps_global->mangled_footer = 1;
5810 
5811 	switch(rc){
5812 	  case -1 :				/* o_e says error! */
5813 	    q_status_message(SM_ORDER | SM_DING, 3, 3,
5814 			     _("Error reading folder name"));
5815 	    return(NULL);
5816 
5817 	  case 0 :				/* o_e says normal entry */
5818 	    removing_trailing_white_space(newfolder);
5819 	    removing_leading_white_space(newfolder);
5820 
5821 	    if(*newfolder){
5822 		char *name, *fullname = NULL;
5823 		int   exists, breakout = 0;
5824 
5825 		save_hist(history, newfolder, 0, tc);
5826 
5827 		if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5828 					   FN_WHOLE_NAME)))
5829 		  name = newfolder;
5830 
5831 		if(update_folder_spec(expanded, sizeof(expanded), name)){
5832 		    strncpy(name = newfolder, expanded, sizeof(newfolder));
5833 		    newfolder[sizeof(newfolder)-1] = '\0';
5834 		}
5835 
5836 		exists = folder_name_exists(tc, name, &fullname);
5837 
5838 		if(fullname){
5839 		    strncpy(name = newfolder, fullname, sizeof(newfolder));
5840 		    newfolder[sizeof(newfolder)-1] = '\0';
5841 		    fs_give((void **) &fullname);
5842 		    breakout = TRUE;
5843 		}
5844 
5845 		/*
5846 		 * if we know the things a folder, open it.
5847 		 * else if we know its a directory, visit it.
5848 		 * else we're not sure (it either doesn't really
5849 		 * exist or its unLISTable) so try opening it anyway
5850 		 */
5851 		if(exists & FEX_ISFILE){
5852 		    done++;
5853 		    break;
5854 		}
5855 		else if((exists & FEX_ISDIR)){
5856 		    if(breakout){
5857 			CONTEXT_S *fake_context;
5858 			char	   tmp[MAILTMPLEN];
5859 			size_t	   l;
5860 
5861 			strncpy(tmp, name, sizeof(tmp));
5862 			tmp[sizeof(tmp)-2-1] = '\0';
5863 			if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5864 			    if(l < sizeof(tmp)){
5865 				tmp[l] = tc->dir->delim;
5866 				strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5867 			    }
5868 			}
5869 			else
5870 			  strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5871 
5872 			tmp[sizeof(tmp)-1] = '\0';
5873 
5874 			fake_context = new_context(tmp, 0);
5875 			newfolder[0] = '\0';
5876 			done = display_folder_list(&fake_context, newfolder,
5877 						   1, folders_for_goto);
5878 			free_context(&fake_context);
5879 			break;
5880 		    }
5881 		    else if(!(tc->use & CNTXT_INCMNG)){
5882 			done = display_folder_list(&tc, newfolder,
5883 						   1, folders_for_goto);
5884 			break;
5885 		    }
5886 		}
5887 		else if((exists & FEX_ERROR)){
5888 		    q_status_message1(SM_ORDER, 0, 3,
5889 				      _("Problem accessing folder \"%s\""),
5890 				      newfolder);
5891 		    return(NULL);
5892 		}
5893 		else{
5894 		    done++;
5895 		    break;
5896 		}
5897 
5898 		if(exists == FEX_ERROR)
5899 		  q_status_message1(SM_ORDER, 0, 3,
5900 				    _("Problem accessing folder \"%s\""),
5901 				    newfolder);
5902 		else if(tc->use & CNTXT_INCMNG)
5903 		  q_status_message1(SM_ORDER, 0, 3,
5904 				    _("Can't find Incoming Folder: %s"),
5905 				    newfolder);
5906 		else if(context_isambig(newfolder))
5907 		  q_status_message2(SM_ORDER, 0, 3,
5908 				    _("Can't find folder \"%s\" in %s"),
5909 				    newfolder, (void *) tc->nickname);
5910 		else
5911 		  q_status_message1(SM_ORDER, 0, 3,
5912 				    _("Can't find folder \"%s\""),
5913 				    newfolder);
5914 
5915 		return(NULL);
5916 	    }
5917 	    else if(last_folder){
5918 		if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5919 		   && !strucmp(last_folder, ps_global->inbox_name)
5920 		   && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5921 		              ? ps_global->context_list->next : ps_global->context_list)){
5922 		    if(notrealinbox)
5923 		      (*notrealinbox) = 0;
5924 
5925 		    tc = ps_global->context_list;
5926 		}
5927 
5928 		strncpy(newfolder, last_folder, sizeof(newfolder));
5929 		newfolder[sizeof(newfolder)-1] = '\0';
5930 		save_hist(history, newfolder, 0, tc);
5931 		done++;
5932 		break;
5933 	    }
5934 	    /* fall thru like they cancelled */
5935 
5936 	  case 1 :				/* o_e says user cancel */
5937 	    cmd_cancelled("Open folder");
5938 	    return(NULL);
5939 
5940 	  case 2 :				/* o_e says user wants list */
5941 	    r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5942 	    if(r)
5943 	      done++;
5944 
5945 	    break;
5946 
5947 	  case 3 :				/* o_e says user wants help */
5948 	    help = help == NO_HELP ? h_oe_broach : NO_HELP;
5949 	    break;
5950 
5951 	  case 4 :				/* redraw */
5952 	    break;
5953 
5954 	  case 10 :				/* Previous collection */
5955 	    tc2 = ps_global->context_list;
5956 	    while(tc2->next && tc2->next != tc)
5957 	      tc2 = tc2->next;
5958 
5959 	    tc = tc2;
5960 	    break;
5961 
5962 	  case 11 :				/* Next collection */
5963 	    tc = (tc->next) ? tc->next : ps_global->context_list;
5964 	    break;
5965 
5966 	  case 12 :				/* file name completion */
5967 	    if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5968 		if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5969 		    r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5970 		    if(r)
5971 		      done++;			/* bingo! */
5972 		    else
5973 		      rc = 0;			/* burn last_rc */
5974 		}
5975 		else
5976 		  Writechar(BELL, 0);
5977 	    }
5978 
5979 	    break;
5980 
5981 	  case 14 :				/* file name completion */
5982 	    r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5983 	    if(r)
5984 	      done++;			/* bingo! */
5985 	    else
5986 	      rc = 0;			/* burn last_rc */
5987 
5988 	    break;
5989 
5990 	  case 17 :				/* GoTo INBOX */
5991 	    done++;
5992 	    strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5993 	    newfolder[sizeof(newfolder)-1] = '\0';
5994 	    if(notrealinbox)
5995 	      (*notrealinbox) = 0;
5996 
5997 	    tc = ps_global->context_list;
5998 	    save_hist(history, newfolder, 0, tc);
5999 
6000 	    break;
6001 
6002 	  case 30 :
6003 	    if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
6004 		strncpy(newfolder, p, sizeof(newfolder));
6005 		newfolder[sizeof(newfolder)-1] = '\0';
6006 		if(history->hist[history->curindex])
6007 		  tc = history->hist[history->curindex]->cntxt;
6008 	    }
6009 	    else
6010 	      Writechar(BELL, 0);
6011 
6012 	    break;
6013 
6014 	  case 31 :
6015 	    if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6016 		strncpy(newfolder, p, sizeof(newfolder));
6017 		newfolder[sizeof(newfolder)-1] = '\0';
6018 		if(history->hist[history->curindex])
6019 		  tc = history->hist[history->curindex]->cntxt;
6020 	    }
6021 	    else
6022 	      Writechar(BELL, 0);
6023 
6024 	    break;
6025 
6026 	  default :
6027 	    alpine_panic("Unhandled case");
6028 	    break;
6029 	}
6030 
6031 	last_rc = rc;
6032     }
6033 
6034     dprint((2, "broach folder, name entered \"%s\"\n",
6035 	   newfolder ? newfolder : "?"));
6036 
6037     /*-- Just check that we can expand this. It gets done for real later --*/
6038     strncpy(expanded, newfolder, sizeof(expanded));
6039     expanded[sizeof(expanded)-1] = '\0';
6040 
6041     if(!expand_foldername(expanded, sizeof(expanded))) {
6042         dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6043     	  expanded ? expanded : "?"));
6044         return(NULL);
6045     }
6046 
6047     *context = tc;
6048     return(newfolder);
6049 }
6050 
6051 
6052 /*----------------------------------------------------------------------
6053     Check to see if user wants to reopen dead stream.
6054 
6055   Args: ps --
6056 	reopenp --
6057 
6058  Result:  1 if the folder was successfully updatedn
6059           0 if not necessary
6060 
6061   ----*/
6062 int
ask_mailbox_reopen(struct pine * ps,int * reopenp)6063 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6064 {
6065     if(((ps->mail_stream->dtb
6066 	 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6067 	     || (ps->mail_stream->rdonly
6068 		 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6069 	&& (ps->reopen_rule == REOPEN_ASK_ASK_Y
6070 	    || ps->reopen_rule == REOPEN_ASK_ASK_N
6071 	    || ps->reopen_rule == REOPEN_ASK_NO_Y
6072 	    || ps->reopen_rule == REOPEN_ASK_NO_N))
6073        || ((ps->mail_stream->dtb
6074 	    && ps->mail_stream->rdonly
6075 	    && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6076 	   && (ps->reopen_rule == REOPEN_YES_ASK_Y
6077 	       || ps->reopen_rule == REOPEN_YES_ASK_N
6078 	       || ps->reopen_rule == REOPEN_ASK_ASK_Y
6079 	       || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6080 	int deefault;
6081 
6082 	switch(ps->reopen_rule){
6083 	  case REOPEN_YES_ASK_Y:
6084 	  case REOPEN_ASK_ASK_Y:
6085 	  case REOPEN_ASK_NO_Y:
6086 	    deefault = 'y';
6087 	    break;
6088 
6089 	  default:
6090 	    deefault = 'n';
6091 	    break;
6092 	}
6093 
6094 	switch(want_to("Re-open folder to check for new messages", deefault,
6095 		       'x', h_reopen_folder, WT_NORM)){
6096 	  case 'y':
6097 	    (*reopenp)++;
6098 	    break;
6099 
6100 	  case 'x':
6101 	    return(-1);
6102 	}
6103     }
6104 
6105     return(0);
6106 }
6107 
6108 
6109 
6110 /*----------------------------------------------------------------------
6111     Check to see if user input is in form of old c-client mailbox speck
6112 
6113   Args: old --
6114 	new --
6115 
6116  Result:  1 if the folder was successfully updatedn
6117           0 if not necessary
6118 
6119   ----*/
6120 int
update_folder_spec(char * new,size_t newlen,char * old)6121 update_folder_spec(char *new, size_t newlen, char *old)
6122 {
6123     char *p, *orignew;
6124     int	  nntp = 0;
6125 
6126     orignew = new;
6127     if(*(p = old) == '*')		/* old form? */
6128       old++;
6129 
6130     if(*old == '{')			/* copy host spec */
6131       do
6132 	switch(*new = *old++){
6133 	  case '\0' :
6134 	    return(FALSE);
6135 
6136 	  case '/' :
6137 	    if(!struncmp(old, "nntp", 4))
6138 	      nntp++;
6139 
6140 	    break;
6141 
6142 	  default :
6143 	    break;
6144 	}
6145       while(*new++ != '}' && (new-orignew) < newlen-1);
6146 
6147     if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6148 	/*
6149 	 * OK, some heuristics here.  If it looks like a newsgroup
6150 	 * then we plunk it into the #news namespace else we
6151 	 * assume that they're trying to get at a #public folder...
6152 	 */
6153 	for(p = old;
6154 	    *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6155 	    p++)
6156 	  ;
6157 
6158 	sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6159 	strncpy(new, old, newlen-(new-orignew));
6160 	return(TRUE);
6161     }
6162 
6163     orignew[newlen-1] = '\0';
6164 
6165     return(FALSE);
6166 }
6167 
6168 
6169 /*----------------------------------------------------------------------
6170     Open the requested folder in the requested context
6171 
6172     Args: state -- usual pine state struct
6173 	  newfolder -- folder to open
6174 	  new_context -- folder context might live in
6175 	  stream -- candidate for recycling
6176 
6177    Result: New folder open or not (if error), and we're set to
6178 	   enter the index screen.
6179  ----*/
6180 void
visit_folder(struct pine * state,char * newfolder,CONTEXT_S * new_context,MAILSTREAM * stream,long unsigned int flags)6181 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6182 	     MAILSTREAM *stream, long unsigned int flags)
6183 {
6184     dprint((9, "visit_folder(%s, %s)\n",
6185 	       newfolder ? newfolder : "?",
6186 	       (new_context && new_context->context)
6187 	         ? new_context->context : "(NULL)"));
6188 
6189     if(ps_global && ps_global->ttyo){
6190 	blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6191 	ps_global->mangled_footer = 1;
6192     }
6193 
6194     if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6195 			flags) >= 0
6196        || !sp_flagged(state->mail_stream, SP_LOCKED))
6197       state->next_screen = mail_index_screen;
6198     else
6199       state->next_screen = folder_screen;
6200 }
6201 
6202 
6203 /*----------------------------------------------------------------------
6204   Move read messages from folder if listed in archive
6205 
6206   Args:
6207 
6208   ----*/
6209 int
read_msg_prompt(long int n,char * f)6210 read_msg_prompt(long int n, char *f)
6211 {
6212     char buf[MAX_SCREEN_COLS+1];
6213 
6214     snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6215     buf[sizeof(buf)-1] = '\0';
6216     return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6217 }
6218 
6219 
6220 /*----------------------------------------------------------------------
6221     Print current message[s] or folder index
6222 
6223     Args: state -- pointer to struct holding a bunch of pine state
6224 	 msgmap -- table mapping msg nums to c-client sequence nums
6225 	   aopt -- aggregate options
6226        in_index -- boolean indicating we're called from Index Screen
6227 
6228  Filters the original header and sends stuff to printer
6229   ---*/
6230 int
cmd_print(struct pine * state,MSGNO_S * msgmap,int aopt,CmdWhere in_index)6231 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6232 {
6233     char      prompt[250];
6234     long      i, msgs, rawno;
6235     int	      next = 0, do_index = 0, rv = 0;
6236     ENVELOPE *e;
6237     BODY     *b;
6238     MESSAGECACHE *mc;
6239 
6240     if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6241       return rv;
6242 
6243     msgs = mn_total_cur(msgmap);
6244 
6245     if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6246 	char m[10];
6247 	int  ans;
6248 	static ESCKEY_S prt_opts[] = {
6249 	    {'i', 'i', "I", N_("Index")},
6250 	    {'m', 'm', "M", NULL},
6251 	    {-1, 0, NULL, NULL}};
6252 
6253 	if(in_index == ThrdIndx){
6254 	    /* TRANSLATORS: This is a question, Print Index ? */
6255 	    if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6256 	      ans = 'i';
6257 	    else
6258 	      ans = 'x';
6259 	}
6260 	else{
6261 	    snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6262 	    m[sizeof(m)-1] = '\0';
6263 	    prt_opts[1].label = m;
6264 	    snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6265 		(aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6266 		(aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6267 	    prompt[sizeof(prompt)-1] = '\0';
6268 
6269 	    ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6270 				NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6271 	}
6272 
6273 	switch(ans){
6274 	  case 'x' :
6275 	    cmd_cancelled("Print");
6276 	    if(MCMD_ISAGG(aopt))
6277 	      restore_selected(msgmap);
6278 
6279 	    return rv;
6280 
6281 	  case 'i':
6282 	    do_index = 1;
6283 	    break;
6284 
6285 	  default :
6286 	  case 'm':
6287 	    break;
6288 	}
6289     }
6290 
6291     if(do_index)
6292       snprintf(prompt, sizeof(prompt), "%sFolder Index",
6293 	      (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6294     else if(msgs > 1L)
6295       snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6296     else
6297       snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6298 
6299     prompt[sizeof(prompt)-1] = '\0';
6300 
6301     if(open_printer(prompt) < 0){
6302 	if(MCMD_ISAGG(aopt))
6303 	  restore_selected(msgmap);
6304 
6305 	return rv;
6306     }
6307 
6308     if(do_index){
6309 	TITLE_S *tc;
6310 
6311 	tc = format_titlebar();
6312 
6313 	/* Print titlebar... */
6314 	print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6315 	/* then all the index members... */
6316 	if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6317 	  q_status_message(SM_ORDER | SM_DING, 3, 3,
6318 			   _("Error printing folder index"));
6319         else
6320 	  rv++;
6321     }
6322     else{
6323 	rv++;
6324         for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6325 	    if(next && F_ON(F_AGG_PRINT_FF, state))
6326 	      if(!print_char(FORMFEED)){
6327 		rv = 0;
6328 	        break;
6329 	      }
6330 
6331 	    if(!(state->mail_stream
6332 	       && (rawno = mn_m2raw(msgmap, i)) > 0L
6333 	       && rawno <= state->mail_stream->nmsgs
6334 	       && (mc = mail_elt(state->mail_stream, rawno))
6335 	       && mc->valid))
6336 	      mc = NULL;
6337 
6338 	    if(!(e=pine_mail_fetchstructure(state->mail_stream,
6339 					    mn_m2raw(msgmap,i),
6340 					    &b))
6341 	       || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6342 		   && !bezerk_delimiter(e, mc, print_char, next))
6343 	       || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6344 				  e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6345 				  print_char)){
6346 	        q_status_message(SM_ORDER | SM_DING, 3, 3,
6347 			       _("Error printing message"));
6348 		rv = 0;
6349 	        break;
6350 	    }
6351         }
6352     }
6353 
6354     close_printer();
6355 
6356     if(MCMD_ISAGG(aopt))
6357       restore_selected(msgmap);
6358 
6359     return rv;
6360 }
6361 
6362 
6363 /*----------------------------------------------------------------------
6364     Pipe message text
6365 
6366    Args: state -- various pine state bits
6367 	 msgmap -- Message number mapping table
6368 	 aopt -- option flags
6369 
6370    Filters the original header and sends stuff to specified command
6371   ---*/
6372 int
cmd_pipe(struct pine * state,MSGNO_S * msgmap,int aopt)6373 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6374 {
6375     ENVELOPE      *e;
6376     MESSAGECACHE  *mc;
6377     BODY	  *b;
6378     PIPE_S	  *syspipe;
6379     char          *resultfilename = NULL, prompt[80], *p;
6380     int            done = 0, rv = 0;
6381     gf_io_t	   pc;
6382     int		   fourlabel = -1, j = 0, next = 0, ku;
6383     int            pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6384     long           i, rawno;
6385     unsigned       flagsforhist = 1;	/* raw=8/delimit=4/newpipe=2/capture=1 */
6386     static HISTORY_S *history = NULL;
6387     int	           capture = 1, raw = 0, delimit = 0, newpipe = 0;
6388     char           pipe_command[MAXPATH];
6389     ESCKEY_S pipe_opt[8];
6390 
6391     if(ps_global->restricted){
6392 	q_status_message(SM_ORDER | SM_DING, 0, 4,
6393 			 "Alpine demo can't pipe messages");
6394 	return rv;
6395     }
6396     else if(!any_messages(msgmap, NULL, "to Pipe"))
6397       return rv;
6398 
6399     pipe_command[0] = '\0';
6400     init_hist(&history, HISTSIZE);
6401     flagsforhist = (raw ? 0x8 : 0) +
6402 		    (delimit ? 0x4 : 0) +
6403 		     (newpipe ? 0x2 : 0) +
6404 		      (capture ? 0x1 : 0);
6405     if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6406 	strncpy(pipe_command, p, sizeof(pipe_command));
6407 	pipe_command[sizeof(pipe_command)-1] = '\0';
6408 	if(history->hist[history->curindex]){
6409 	    flagsforhist = history->hist[history->curindex]->flags;
6410 	    raw     = (flagsforhist & 0x8) ? 1 : 0;
6411 	    delimit = (flagsforhist & 0x4) ? 1 : 0;
6412 	    newpipe = (flagsforhist & 0x2) ? 1 : 0;
6413 	    capture = (flagsforhist & 0x1) ? 1 : 0;
6414 	}
6415     }
6416 
6417     pipe_opt[j].ch    = 0;
6418     pipe_opt[j].rval  = 0;
6419     pipe_opt[j].name  = "";
6420     pipe_opt[j++].label = "";
6421 
6422     pipe_opt[j].ch    = ctrl('W');
6423     pipe_opt[j].rval  = 10;
6424     pipe_opt[j].name  = "^W";
6425     pipe_opt[j++].label = NULL;
6426 
6427     pipe_opt[j].ch    = ctrl('Y');
6428     pipe_opt[j].rval  = 11;
6429     pipe_opt[j].name  = "^Y";
6430     pipe_opt[j++].label = NULL;
6431 
6432     pipe_opt[j].ch    = ctrl('R');
6433     pipe_opt[j].rval  = 12;
6434     pipe_opt[j].name  = "^R";
6435     pipe_opt[j++].label = NULL;
6436 
6437     if(MCMD_ISAGG(aopt)){
6438 	if(!pseudo_selected(state->mail_stream, msgmap))
6439 	  return rv;
6440 	else{
6441 	    fourlabel = j;
6442 	    pipe_opt[j].ch    = ctrl('T');
6443 	    pipe_opt[j].rval  = 13;
6444 	    pipe_opt[j].name  = "^T";
6445 	    pipe_opt[j++].label = NULL;
6446 	}
6447     }
6448 
6449     pipe_opt[j].ch      = KEY_UP;
6450     pipe_opt[j].rval    = 30;
6451     pipe_opt[j].name    = "";
6452     ku = j;
6453     pipe_opt[j++].label = "";
6454 
6455     pipe_opt[j].ch      = KEY_DOWN;
6456     pipe_opt[j].rval    = 31;
6457     pipe_opt[j].name    = "";
6458     pipe_opt[j++].label = "";
6459 
6460     pipe_opt[j].ch = -1;
6461 
6462     while (!done) {
6463 	int flags;
6464 
6465 	snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6466 		raw ? "RAW " : "",
6467 		MCMD_ISAGG(aopt) ? "s" : " ",
6468 		MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6469 		(!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6470 		capture ? "" : "uncaptured",
6471 		(!capture && delimit) ? "," : "",
6472 		delimit ? "delimited" : "",
6473 		((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6474 		(newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6475 		(!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6476 	prompt[sizeof(prompt)-1] = '\0';
6477 	pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6478 	pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6479 	pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6480 	if(fourlabel > 0)
6481 	  pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6482 
6483 
6484 	/*
6485 	 * 2 is really 1 because there will be one real entry and
6486 	 * one entry of "" because of the get_prev_hist above.
6487 	 */
6488 	if(items_in_hist(history) > 2){
6489 	    pipe_opt[ku].name  = HISTORY_UP_KEYNAME;
6490 	    pipe_opt[ku].label = HISTORY_KEYLABEL;
6491 	    pipe_opt[ku+1].name  = HISTORY_DOWN_KEYNAME;
6492 	    pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6493 	}
6494 	else{
6495 	    pipe_opt[ku].name  = "";
6496 	    pipe_opt[ku].label = "";
6497 	    pipe_opt[ku+1].name  = "";
6498 	    pipe_opt[ku+1].label = "";
6499 	}
6500 
6501 	flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6502 	switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6503 				sizeof(pipe_command), prompt,
6504 				pipe_opt, NO_HELP, &flags)){
6505 	  case -1 :
6506 	    q_status_message(SM_ORDER | SM_DING, 3, 4,
6507 			     _("Internal problem encountered"));
6508 	    done++;
6509 	    break;
6510 
6511 	  case 10 :			/* flip raw bit */
6512 	    raw = !raw;
6513 	    break;
6514 
6515 	  case 11 :			/* flip capture bit */
6516 	    capture = !capture;
6517 	    break;
6518 
6519 	  case 12 :			/* flip delimit bit */
6520 	    delimit = !delimit;
6521 	    break;
6522 
6523 	  case 13 :			/* flip newpipe bit */
6524 	    newpipe = !newpipe;
6525 	    break;
6526 
6527 	  case 30 :
6528 	    flagsforhist = (raw ? 0x8 : 0) +
6529 	                    (delimit ? 0x4 : 0) +
6530 			     (newpipe ? 0x2 : 0) +
6531 			      (capture ? 0x1 : 0);
6532 	    if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6533 		strncpy(pipe_command, p, sizeof(pipe_command));
6534 		pipe_command[sizeof(pipe_command)-1] = '\0';
6535 		if(history->hist[history->curindex]){
6536 		    flagsforhist = history->hist[history->curindex]->flags;
6537 		    raw     = (flagsforhist & 0x8) ? 1 : 0;
6538 		    delimit = (flagsforhist & 0x4) ? 1 : 0;
6539 		    newpipe = (flagsforhist & 0x2) ? 1 : 0;
6540 		    capture = (flagsforhist & 0x1) ? 1 : 0;
6541 		}
6542 	    }
6543 	    else
6544 	      Writechar(BELL, 0);
6545 
6546 	    break;
6547 
6548 	  case 31 :
6549 	    flagsforhist = (raw ? 0x8 : 0) +
6550 	                    (delimit ? 0x4 : 0) +
6551 			     (newpipe ? 0x2 : 0) +
6552 			      (capture ? 0x1 : 0);
6553 	    if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6554 		strncpy(pipe_command, p, sizeof(pipe_command));
6555 		pipe_command[sizeof(pipe_command)-1] = '\0';
6556 		if(history->hist[history->curindex]){
6557 		    flagsforhist = history->hist[history->curindex]->flags;
6558 		    raw     = (flagsforhist & 0x8) ? 1 : 0;
6559 		    delimit = (flagsforhist & 0x4) ? 1 : 0;
6560 		    newpipe = (flagsforhist & 0x2) ? 1 : 0;
6561 		    capture = (flagsforhist & 0x1) ? 1 : 0;
6562 		}
6563 	    }
6564 	    else
6565 	      Writechar(BELL, 0);
6566 
6567 	    break;
6568 
6569 	  case 0 :
6570 	    if(pipe_command[0]){
6571 
6572 		flagsforhist = (raw ? 0x8 : 0) +
6573 				(delimit ? 0x4 : 0) +
6574 				 (newpipe ? 0x2 : 0) +
6575 				  (capture ? 0x1 : 0);
6576 		save_hist(history, pipe_command, flagsforhist, NULL);
6577 
6578 		flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6579 		flags |= (raw ? PIPE_RAW : 0);
6580 		if(!capture){
6581 #ifndef	_WINDOWS
6582 		    ClearScreen();
6583 		    fflush(stdout);
6584 		    clear_cursor_pos();
6585 		    ps_global->mangled_screen = 1;
6586 		    ps_global->in_init_seq = 1;
6587 #endif
6588 		    flags |= PIPE_RESET;
6589 		}
6590 
6591 		if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6592 							 (flags & PIPE_RESET)
6593 							   ? NULL
6594 							   : &resultfilename,
6595 							 flags, &pc)))
6596 		  done++;
6597 
6598 		for(i = mn_first_cur(msgmap);
6599 		    i > 0L && !done;
6600 		    i = mn_next_cur(msgmap)){
6601 		    e = pine_mail_fetchstructure(ps_global->mail_stream,
6602 						 mn_m2raw(msgmap, i), &b);
6603 		    if(!(state->mail_stream
6604 		       && (rawno = mn_m2raw(msgmap, i)) > 0L
6605 		       && rawno <= state->mail_stream->nmsgs
6606 		       && (mc = mail_elt(state->mail_stream, rawno))
6607 		       && mc->valid))
6608 		      mc = NULL;
6609 
6610 		    if((newpipe
6611 			&& !(syspipe = cmd_pipe_open(pipe_command,
6612 						     (flags & PIPE_RESET)
6613 						       ? NULL
6614 						       : &resultfilename,
6615 						     flags, &pc)))
6616 		       || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6617 		      done++;
6618 
6619 		    if(!done){
6620 			if(raw){
6621 			    char    *pipe_err;
6622 
6623 			    prime_raw_pipe_getc(ps_global->mail_stream,
6624 						mn_m2raw(msgmap, i), -1L, 0L);
6625 			    gf_filter_init();
6626 			    gf_link_filter(gf_nvtnl_local, NULL);
6627 			    if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6628 				q_status_message1(SM_ORDER|SM_DING,
6629 						  3, 3,
6630 						  _("Internal Error: %s"),
6631 						  pipe_err);
6632 				done++;
6633 			    }
6634 			}
6635 			else if(!format_message(mn_m2raw(msgmap, i), e, b,
6636 						NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6637 			  done++;
6638 		    }
6639 
6640 		    if(newpipe)
6641 		      if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6642 			done++;
6643 		}
6644 
6645 		if(!capture)
6646 		  ps_global->in_init_seq = 0;
6647 
6648 		if(!newpipe)
6649 		  if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6650 		    done++;
6651 		if(done)		/* say we had a problem */
6652 		  q_status_message(SM_ORDER | SM_DING, 3, 3,
6653 				   _("Error piping message"));
6654 		else if(resultfilename){
6655 		    rv++;
6656 		    /* only display if no error */
6657 		    display_output_file(resultfilename, "PIPE MESSAGE",
6658 					NULL, DOF_EMPTY);
6659 		    fs_give((void **)&resultfilename);
6660 		}
6661 		else{
6662 		  rv++;
6663 		  q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6664 		}
6665 
6666 		done++;
6667 		break;
6668 	    }
6669 	    /* else fall thru as if cancelled */
6670 
6671 	  case 1 :
6672 	    cmd_cancelled("Pipe command");
6673 	    done++;
6674 	    break;
6675 
6676 	  case 3 :
6677 	    helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6678 	    ps_global->mangled_screen = 1;
6679 	    break;
6680 
6681 	  case 2 :                              /* no place to escape to */
6682 	  case 4 :                              /* can't suspend */
6683 	  default :
6684 	    break;
6685 	}
6686     }
6687 
6688     ps_global->mangled_footer = 1;
6689     if(MCMD_ISAGG(aopt))
6690       restore_selected(msgmap);
6691 
6692     return rv;
6693 }
6694 
6695 
6696 /*----------------------------------------------------------------------
6697   Screen to offer list management commands contained in message
6698 
6699     Args: state -- pointer to struct holding a bunch of pine state
6700 	 msgmap -- table mapping msg nums to c-client sequence nums
6701 	   aopt -- aggregate options
6702 
6703  Result:
6704 
6705    NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6706  ----*/
6707 void
rfc2369_display(MAILSTREAM * stream,MSGNO_S * msgmap,long int msgno)6708 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6709 {
6710     int	       winner = 0;
6711     char      *h, *hdrs[MLCMD_COUNT + 1];
6712     long       index_no = mn_raw2m(msgmap, msgno);
6713     RFC2369_S  data[MLCMD_COUNT];
6714 
6715     /* for each header field */
6716     if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6717 	memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6718 	if(rfc2369_parse_fields(h, &data[0])){
6719 	    STORE_S *explain;
6720 
6721 	    if((explain = list_mgmt_text(data, index_no)) != NULL){
6722 		list_mgmt_screen(explain);
6723 		ps_global->mangled_screen = 1;
6724 		so_give(&explain);
6725 		winner++;
6726 	    }
6727 	}
6728 
6729 	fs_give((void **) &h);
6730     }
6731 
6732     if(!winner)
6733       q_status_message1(SM_ORDER, 0, 3,
6734 		    "Message %s contains no list management information",
6735 			comatose(index_no));
6736 }
6737 
6738 
6739 STORE_S *
list_mgmt_text(RFC2369_S * data,long int msgno)6740 list_mgmt_text(RFC2369_S *data, long int msgno)
6741 {
6742     STORE_S	  *store;
6743     int		   i, j, n, fields = 0;
6744     static  char  *rfc2369_intro1 =
6745       "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6746     static char *rfc2369_intro2[] = {
6747 	N_(" has information associated with it "),
6748 	N_("that explains how to participate in an email list.  An "),
6749 	N_("email list is represented by a single email address that "),
6750 	N_("users sharing a common interest can send messages to (known "),
6751 	N_("as posting) which are then redistributed to all members "),
6752 	N_("of the list (sometimes after review by a moderator)."),
6753 	N_("<P>List participation commands in this message include:"),
6754 	NULL
6755     };
6756 
6757     if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6758 
6759 	/* Insert introductory text */
6760 	so_puts(store, rfc2369_intro1);
6761 
6762 	so_puts(store, comatose(msgno));
6763 
6764 	for(i = 0; rfc2369_intro2[i]; i++)
6765 	  so_puts(store, _(rfc2369_intro2[i]));
6766 
6767 	so_puts(store, "<P>");
6768 	for(i = 0; i < MLCMD_COUNT; i++)
6769 	  if(data[i].data[0].value
6770 	     || data[i].data[0].comment
6771 	     || data[i].data[0].error){
6772 	      if(!fields++)
6773 		so_puts(store, "<UL>");
6774 
6775 	      so_puts(store, "<LI>");
6776 	      so_puts(store,
6777 		      (n = (data[i].data[1].value || data[i].data[1].comment))
6778 			? "Methods to "
6779 			: "A method to ");
6780 
6781 	      so_puts(store, data[i].field.description);
6782 	      so_puts(store, ". ");
6783 
6784 	      if(n)
6785 		so_puts(store, "<OL>");
6786 
6787 	      for(j = 0;
6788 		  j < MLCMD_MAXDATA
6789 		  && (data[i].data[j].comment
6790 		      || data[i].data[j].value
6791 		      || data[i].data[j].error);
6792 		  j++){
6793 
6794 		  so_puts(store, n ? "<P><LI>" : "<P>");
6795 
6796 		  if(data[i].data[j].comment){
6797 		      so_puts(store,
6798 			      _("With the provided comment:<P><BLOCKQUOTE>"));
6799 		      so_puts(store, data[i].data[j].comment);
6800 		      so_puts(store, "</BLOCKQUOTE><P>");
6801 		  }
6802 
6803 		  if(data[i].data[j].value){
6804 		      if(i == MLCMD_POST
6805 			 && !strucmp(data[i].data[j].value, "NO")){
6806 			  so_puts(store,
6807 			   _("Posting is <EM>not</EM> allowed on this list"));
6808 		      }
6809 		      else{
6810 			  so_puts(store, "Select <A HREF=\"");
6811 			  so_puts(store, data[i].data[j].value);
6812 			  so_puts(store, "\">HERE</A> to ");
6813 			  so_puts(store, (data[i].field.action)
6814 					   ? data[i].field.action
6815 					   : "try it");
6816 		      }
6817 
6818 		      so_puts(store, ".");
6819 		  }
6820 
6821 		  if(data[i].data[j].error){
6822 		      so_puts(store, "<P>Unfortunately, Alpine can not offer");
6823 		      so_puts(store, " to take direct action based upon it");
6824 		      so_puts(store, " because it was improperly formatted.");
6825 		      so_puts(store, " The unrecognized data associated with");
6826 		      so_puts(store, " the \"");
6827 		      so_puts(store, data[i].field.name);
6828 		      so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6829 		      so_puts(store, data[i].data[j].error);
6830 		      so_puts(store, "</BLOCKQUOTE>");
6831 		  }
6832 
6833 		  so_puts(store, "<P>");
6834 	      }
6835 
6836 	      if(n)
6837 		so_puts(store, "</OL>");
6838 	  }
6839 
6840 	if(fields)
6841 	  so_puts(store, "</UL>");
6842 
6843 	so_puts(store, "</BODY></HTML>");
6844     }
6845 
6846     return(store);
6847 }
6848 
6849 
6850 void
list_mgmt_screen(STORE_S * html)6851 list_mgmt_screen(STORE_S *html)
6852 {
6853     int		    cmd = MC_NONE;
6854     long	    offset = 0L;
6855     char	   *error = NULL;
6856     STORE_S	   *store;
6857     HANDLE_S	   *handles = NULL;
6858     gf_io_t	    gc, pc;
6859 
6860     do{
6861 	so_seek(html, 0L, 0);
6862 	gf_set_so_readc(&gc, html);
6863 
6864 	init_handles(&handles);
6865 
6866 	if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6867 	    gf_set_so_writec(&pc, store);
6868 	    gf_filter_init();
6869 
6870 	    gf_link_filter(gf_html2plain,
6871 			   gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6872 					     non_messageview_margin(), &handles, NULL, 0));
6873 
6874 	    error = gf_pipe(gc, pc);
6875 
6876 	    gf_clear_so_writec(store);
6877 
6878 	    if(!error){
6879 		SCROLL_S	sargs;
6880 
6881 		memset(&sargs, 0, sizeof(SCROLL_S));
6882 		sargs.text.text	   = so_text(store);
6883 		sargs.text.src	   = CharStar;
6884 		sargs.text.desc	   = "list commands";
6885 		sargs.text.handles = handles;
6886 		if(offset){
6887 		    sargs.start.on	   = Offset;
6888 		    sargs.start.loc.offset = offset;
6889 		}
6890 
6891 		sargs.bar.title	   = _("MAIL LIST COMMANDS");
6892 		sargs.bar.style	   = MessageNumber;
6893 		sargs.resize_exit  = 1;
6894 		sargs.help.text	   = h_special_list_commands;
6895 		sargs.help.title   = _("HELP FOR LIST COMMANDS");
6896 		sargs.keys.menu	   = &listmgr_keymenu;
6897 		setbitmap(sargs.keys.bitmap);
6898 		if(!handles){
6899 		    clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6900 		    clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6901 		    clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6902 		}
6903 
6904 		cmd = scrolltool(&sargs);
6905 		offset = sargs.start.loc.offset;
6906 	    }
6907 
6908 	    so_give(&store);
6909 	}
6910 
6911 	free_handles(&handles);
6912 	gf_clear_so_readc(html);
6913     }
6914     while(cmd == MC_RESIZE);
6915 }
6916 
6917 
6918 /*----------------------------------------------------------------------
6919  Prompt the user for the type of select desired
6920 
6921    NOTE: any and all functions that successfully exit the second
6922 	 switch() statement below (currently "select_*() functions"),
6923 	 *MUST* update the folder's MESSAGECACHE element's "searched"
6924 	 bits to reflect the search result.  Functions using
6925 	 mail_search() get this for free, the others must update 'em
6926 	 by hand.
6927 
6928     Returns -1 if canceled without changing selection
6929 	     0 if selection may have changed
6930   ----*/
6931 int
aggregate_select(struct pine * state,MSGNO_S * msgmap,int q_line,CmdWhere in_index)6932 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6933 {
6934     long          i, diff, old_tot, msgno, raw;
6935     int           p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6936     ESCKEY_S     *sel_opts;
6937     MESSAGECACHE *mc;
6938     SEARCHSET    *limitsrch = NULL;
6939     PINETHRD_S   *thrd;
6940     extern     MAILSTREAM *mm_search_stream;
6941     extern     long	   mm_search_count;
6942 
6943     hidden           = any_lflagged(msgmap, MN_HIDE) > 0L;
6944     mm_search_stream = state->mail_stream;
6945     mm_search_count  = 0L;
6946 
6947     if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6948        sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6949     else
6950        sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6951 
6952     if(THREADING()){
6953 	sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6954     }
6955     else{
6956 	if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6957 	  sel_opts[SEL_OPTS_THREAD].ch    = SEL_OPTS_XGMEXT_CH;
6958 	  sel_opts[SEL_OPTS_THREAD].rval  = sel_opts[SEL_OPTS_XGMEXT].rval;
6959 	  sel_opts[SEL_OPTS_THREAD].name  = sel_opts[SEL_OPTS_XGMEXT].name;
6960 	  sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6961 	  sel_opts[SEL_OPTS_XGMEXT].ch    = -1;
6962 	}
6963 	else
6964 	  sel_opts[SEL_OPTS_THREAD].ch = -1;
6965     }
6966 
6967     if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6968 	if(THRD_INDX()){
6969 	    i = 0;
6970 	    thrd = fetch_thread(state->mail_stream,
6971 				mn_m2raw(msgmap, mn_get_cur(msgmap)));
6972 	    /* check if whole thread is selected or not */
6973 	    if(thrd &&
6974 	       count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6975 		   ==
6976 	       count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6977 	      i = 1;
6978 
6979 	    sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6980 	}
6981 	else{
6982 	    i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6983 			  MN_SLCT);
6984 	    sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6985 	}
6986 
6987 	sel_opts += 2;			/* disable extra options */
6988 	if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6989 	   q = 	double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6990 				   RB_NORM);
6991 	else
6992 	   q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6993 				 RB_NORM);
6994 	switch(q){
6995 	  case 'f' :			/* flip selection */
6996 	    msgno = 0L;
6997 	    for(i = 1L; i <= mn_get_total(msgmap); i++){
6998 		ret = 0;
6999 		q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
7000 		set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
7001 		if(hidden){
7002 		    set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
7003 		    if(!msgno && q)
7004 		      mn_reset_cur(msgmap, msgno = i);
7005 		}
7006 	    }
7007 
7008 	    return(ret);
7009 
7010 	  case 'n' :			/* narrow selection */
7011 	    narrow++;
7012 	    q = 0;
7013 	    break;
7014 	  case 'r' : 			/* replace selection */
7015 	    p = 1;			/* set flag we want to replace */
7016 	    sel_opts -= 2;		/* re-enable first two options */
7017 	  case 'b' :			/* broaden selection */
7018 	    q = 0;			/* offer criteria prompt */
7019 	    break;
7020 
7021 	  case 'c' :			/* Un/Select Current */
7022 	  case 'a' :			/* Unselect All */
7023 	  case 'x' :			/* cancel */
7024 	    break;
7025 
7026 	  default :
7027 	    q_status_message(SM_ORDER | SM_DING, 3, 3,
7028 			     "Unsupported Select option");
7029 	    return(ret);
7030 	}
7031     }
7032 
7033     if(!q){
7034 	while(1){
7035 	    if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7036 	      q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7037 				   RB_NORM);
7038 	    else
7039 	      q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7040 				 RB_NORM|RB_RET_HELP);
7041 
7042 	    if(q == 3){
7043 		helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7044 		ps_global->mangled_screen = 1;
7045 	    }
7046 	    else
7047 	      break;
7048 	}
7049     }
7050 
7051      /* When we are replacing the search, unselect all messages first, unless
7052       * we are cancelling, in whose case, leave the screen as is, because we
7053       * are cancelling!
7054       */
7055     if(p == 1 && q != 'x'){
7056 	msgno = any_lflagged(msgmap, MN_SLCT);
7057 	diff    = (!msgno) ? mn_get_total(msgmap) : 0L;
7058 	agg_select_all(state->mail_stream, msgmap, &diff,
7059 		       any_lflagged(msgmap, MN_SLCT) <= 0L);
7060     }
7061 
7062     /*
7063      * The purpose of this is to add the appropriate searchset to the
7064      * search so that the search can be limited to only looking at what
7065      * it needs to look at. That is, if we are narrowing then we only need
7066      * to look at messages which are already selected, and if we are
7067      * broadening, then we only need to look at messages which are not
7068      * yet selected. This routine will work whether or not
7069      * limiting_searchset properly limits the search set. In particular,
7070      * the searchset returned by limiting_searchset may include messages
7071      * which really shouldn't be included. We do that because a too-large
7072      * searchset will break some IMAP servers. It is even possible that it
7073      * becomes inefficient to send the whole set. If the select function
7074      * frees limitsrch, it should be sure to set it to NULL so we won't
7075      * try freeing it again here.
7076      */
7077     limitsrch = limiting_searchset(state->mail_stream, narrow);
7078 
7079     /*
7080      * NOTE: See note about MESSAGECACHE "searched" bits above!
7081      */
7082     switch(q){
7083       case 'x':				/* cancel */
7084 	cmd_cancelled("Select command");
7085 	return(ret);
7086 
7087       case 'c' :			/* select/unselect current */
7088 	(void) select_by_current(state, msgmap, in_index);
7089 	ret = 0;
7090 	return(ret);
7091 
7092       case 'a' :			/* select/unselect all */
7093 	msgno = any_lflagged(msgmap, MN_SLCT);
7094 	diff    = (!msgno) ? mn_get_total(msgmap) : 0L;
7095 	ret = 0;
7096 	agg_select_all(state->mail_stream, msgmap, &diff,
7097 		       any_lflagged(msgmap, MN_SLCT) <= 0L);
7098 	q_status_message4(SM_ORDER,0,2,
7099 			  "%s%s message%s %sselected",
7100 			  msgno ? "" : "All ", comatose(diff),
7101 			  plural(diff), msgno ? "UN" : "");
7102 	return(ret);
7103 
7104       case 'n' :			/* Select by Number */
7105 	ret = 0;
7106 	if(THRD_INDX())
7107 	  rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7108 	else
7109 	  rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7110 
7111 	break;
7112 
7113       case 'd' :			/* Select by Date */
7114 	ret = 0;
7115 	rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7116 			 &limitsrch);
7117 	break;
7118 
7119       case 't' :			/* Text */
7120 	ret = 0;
7121 	rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7122 			 &limitsrch);
7123 	break;
7124 
7125       case 'z' :			/* Size */
7126 	ret = 0;
7127 	rv = select_by_size(state->mail_stream, &limitsrch);
7128 	break;
7129 
7130       case 's' :			/* Status */
7131 	ret = 0;
7132 	rv = select_by_status(state->mail_stream, &limitsrch);
7133 	break;
7134 
7135       case 'k' :			/* Keyword */
7136 	ret = 0;
7137 	rv = select_by_keyword(state->mail_stream, &limitsrch);
7138 	break;
7139 
7140       case 'r' :			/* Rule */
7141 	ret = 0;
7142 	rv = select_by_rule(state->mail_stream, &limitsrch);
7143 	break;
7144 
7145       case 'h' :			/* Thread */
7146 	ret = 0;
7147 	rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7148 	break;
7149 
7150       case 'g' :			/* X-GM-EXT-1 */
7151 	ret = 0;
7152 	rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7153 	break;
7154 
7155       default :
7156 	q_status_message(SM_ORDER | SM_DING, 3, 3,
7157 			 "Unsupported Select option");
7158 	return(ret);
7159     }
7160 
7161     if(limitsrch)
7162       mail_free_searchset(&limitsrch);
7163 
7164     if(rv)				/* bad return value.. */
7165       return(ret);			/* error already displayed */
7166 
7167     if(narrow)				/* make sure something was selected */
7168       for(i = 1L; i <= mn_get_total(msgmap); i++)
7169 	if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7170 	   && raw <= state->mail_stream->nmsgs
7171 	   && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7172 	    if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7173 	      break;
7174 	    else
7175 	      mm_search_count--;
7176 	}
7177 
7178     diff = 0L;
7179     if(mm_search_count){
7180 	/*
7181 	 * loop thru all the messages, adjusting local flag bits
7182 	 * based on their "searched" bit...
7183 	 */
7184 	for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7185 	  if(narrow){
7186 	      /* turning OFF selectedness if the "searched" bit isn't lit. */
7187 	      if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7188 		  if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7189 		     && raw <= state->mail_stream->nmsgs
7190 		     && (mc = mail_elt(state->mail_stream, raw))
7191 		     && !mc->searched){
7192 		      diff--;
7193 		      set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7194 		      if(hidden)
7195 			set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7196 		  }
7197 		  /* adjust current message in case we unselect and hide it */
7198 		  else if(msgno < mn_get_cur(msgmap)
7199 			  && (!THRD_INDX()
7200 			      || !get_lflag(state->mail_stream, msgmap,
7201 					    i, MN_CHID)))
7202 		    msgno = i;
7203 	      }
7204 	  }
7205 	  else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7206 	          && raw <= state->mail_stream->nmsgs
7207 	          && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7208 	      /* turn ON selectedness if "searched" bit is lit. */
7209 	      if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7210 		  diff++;
7211 		  set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7212 		  if(hidden)
7213 		    set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7214 	      }
7215 	  }
7216 
7217 	/* if we're zoomed and the current message was unselected */
7218 	if(narrow && msgno
7219 	   && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7220 	  mn_reset_cur(msgmap, msgno);
7221     }
7222 
7223     if(!diff){
7224 	if(narrow)
7225 	  q_status_message4(SM_ORDER, 3, 3,
7226 			"%s.  %s message%s remain%s selected.",
7227 			mm_search_count
7228 					? "No change resulted"
7229 					: "No messages in intersection",
7230 			comatose(old_tot), plural(old_tot),
7231 			(old_tot == 1L) ? "s" : "");
7232 	else if(old_tot)
7233 	  q_status_message(SM_ORDER, 3, 3,
7234 		   _("No change resulted.  Matching messages already selected."));
7235 	else
7236 	  q_status_message1(SM_ORDER | SM_DING, 3, 3,
7237 			    _("Select failed.  No %smessages selected."),
7238 			    old_tot ? _("additional ") : "");
7239     }
7240     else if(old_tot){
7241 	snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7242 		"Select matched %ld message%s.  %s %smessage%s %sselected.",
7243 		(diff > 0) ? diff : old_tot + diff,
7244 		plural((diff > 0) ? diff : old_tot + diff),
7245 		comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7246 		(diff > 0) ? "total " : "",
7247 		plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7248 		(diff > 0) ? "" : "UN");
7249 	tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7250 	q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7251     }
7252     else
7253       q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7254 			comatose(diff), plural(diff));
7255 
7256     return(ret);
7257 }
7258 
7259 
7260 /*----------------------------------------------------------------------
7261  Toggle the state of the current message
7262 
7263    Args: state -- pointer pine's state variables
7264 	 msgmap -- message collection to operate on
7265 	 in_index -- in the message index view
7266    Returns: TRUE if current marked selected, FALSE otw
7267   ----*/
7268 int
select_by_current(struct pine * state,MSGNO_S * msgmap,CmdWhere in_index)7269 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7270 {
7271     long cur;
7272     int  all_selected = 0;
7273     unsigned long was, tot, rawno;
7274     PINETHRD_S *thrd;
7275 
7276     cur = mn_get_cur(msgmap);
7277 
7278     if(THRD_INDX()){
7279 	thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7280 	if(!thrd)
7281 	  return 0;
7282 
7283 	was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7284 	tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7285 	if(was == tot)
7286 	  all_selected++;
7287 
7288 	if(all_selected){
7289 	    set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7290 	    if(any_lflagged(msgmap, MN_HIDE) > 0L){
7291 		set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7292 		/*
7293 		 * See if there's anything left to zoom on.  If so,
7294 		 * pick an adjacent one for highlighting, else make
7295 		 * sure nothing is left hidden...
7296 		 */
7297 		if(any_lflagged(msgmap, MN_SLCT)){
7298 		    mn_inc_cur(state->mail_stream, msgmap,
7299 			       (in_index == View && THREADING()
7300 				&& sp_viewing_a_thread(state->mail_stream))
7301 				 ? MH_THISTHD
7302 				 : (in_index == View)
7303 				   ? MH_ANYTHD : MH_NONE);
7304 		    if(mn_get_cur(msgmap) == cur)
7305 		      mn_dec_cur(state->mail_stream, msgmap,
7306 			         (in_index == View && THREADING()
7307 				  && sp_viewing_a_thread(state->mail_stream))
7308 				   ? MH_THISTHD
7309 				   : (in_index == View)
7310 				     ? MH_ANYTHD : MH_NONE);
7311 		}
7312 		else			/* clear all hidden flags */
7313 		  (void) unzoom_index(state, state->mail_stream, msgmap);
7314 	    }
7315 	}
7316 	else
7317 	  set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7318 
7319 	q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7320 			  comatose(all_selected ? was : tot-was),
7321 			  plural(all_selected ? was : tot-was),
7322 			  all_selected ? "UN" : "");
7323     }
7324     /* collapsed thread */
7325     else if(THREADING()
7326             && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7327             && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7328             && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7329 	/*
7330 	 * This doesn't work quite the same as the colon command works, but
7331 	 * it is arguably doing the correct thing. The difference is
7332 	 * that aggregate_select will zoom after selecting back where it
7333 	 * was called from, but selecting a thread with colon won't zoom.
7334 	 * Maybe it makes sense to zoom after a select but not after a colon
7335 	 * command even though they are very similar.
7336 	 */
7337 	thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7338     }
7339     else{
7340 	if((all_selected =
7341 	    get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7342 	    set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7343 	    if(any_lflagged(msgmap, MN_HIDE) > 0L){
7344 		set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7345 		/*
7346 		 * See if there's anything left to zoom on.  If so,
7347 		 * pick an adjacent one for highlighting, else make
7348 		 * sure nothing is left hidden...
7349 		 */
7350 		if(any_lflagged(msgmap, MN_SLCT)){
7351 		    mn_inc_cur(state->mail_stream, msgmap,
7352 			       (in_index == View && THREADING()
7353 				&& sp_viewing_a_thread(state->mail_stream))
7354 				 ? MH_THISTHD
7355 				 : (in_index == View)
7356 				   ? MH_ANYTHD : MH_NONE);
7357 		    if(mn_get_cur(msgmap) == cur)
7358 		      mn_dec_cur(state->mail_stream, msgmap,
7359 			         (in_index == View && THREADING()
7360 				  && sp_viewing_a_thread(state->mail_stream))
7361 				   ? MH_THISTHD
7362 				   : (in_index == View)
7363 				     ? MH_ANYTHD : MH_NONE);
7364 		}
7365 		else			/* clear all hidden flags */
7366 		  (void) unzoom_index(state, state->mail_stream, msgmap);
7367 	    }
7368 	}
7369 	else
7370 	  set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7371 
7372 	q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7373 			  long2string(cur), all_selected ? "UN" : "");
7374     }
7375 
7376 
7377     return(!all_selected);
7378 }
7379 
7380 
7381 /*----------------------------------------------------------------------
7382  Prompt the user for the command to perform on selected messages
7383 
7384    Args: state -- pointer pine's state variables
7385 	 msgmap -- message collection to operate on
7386 	 q_line -- line on display to write prompts
7387    Returns: 1 if the selected messages are suitably commanded,
7388 	    0 if the choice to pick the command was declined
7389 
7390   ----*/
7391 int
apply_command(struct pine * state,MAILSTREAM * stream,MSGNO_S * msgmap,UCS preloadkeystroke,int flags,int q_line)7392 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7393 	      UCS preloadkeystroke, int flags, int q_line)
7394 {
7395     int i = 8,			/* number of static entries in sel_opts3 */
7396         rv = 0,
7397 	cmd,
7398         we_cancel = 0,
7399 	agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7400     char prompt[80];
7401     PAT_STATE  pstate;
7402 
7403     /*
7404      * To do this "right", we really ought to have access to the keymenu
7405      * here and change the typed command into a real command by running
7406      * it through menu_command. Then the switch below would be against
7407      * results from menu_command. If we did that we'd also pass the
7408      * results of menu_command in as preloadkeystroke instead of passing
7409      * the keystroke itself. But we don't have the keymenu handy,
7410      * so we have to fake it. The only complication that we run into
7411      * is that KEY_DEL is an escape sequence so we change a typed
7412      * KEY_DEL esc seq into the letter D.
7413      */
7414 
7415     if(!preloadkeystroke){
7416 	if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7417 	    sel_opts3[i].ch      = '*';
7418 	    sel_opts3[i].rval    = '*';
7419 	    sel_opts3[i].name    = "*";
7420 	    sel_opts3[i++].label = N_("Flag");
7421 	}
7422 
7423 	if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7424 	    sel_opts3[i].ch      = '|';
7425 	    sel_opts3[i].rval    = '|';
7426 	    sel_opts3[i].name    = "|";
7427 	    sel_opts3[i++].label = N_("Pipe");
7428 	}
7429 
7430 	if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7431 	    sel_opts3[i].ch      = 'b';
7432 	    sel_opts3[i].rval    = 'b';
7433 	    sel_opts3[i].name    = "B";
7434 	    sel_opts3[i++].label = N_("Bounce");
7435 	}
7436 
7437 	if(flags & AC_FROM_THREAD){
7438 	    if(flags & (AC_COLL | AC_EXPN)){
7439 		sel_opts3[i].ch      = '/';
7440 		sel_opts3[i].rval    = '/';
7441 		sel_opts3[i].name    = "/";
7442 		sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7443 							 : N_("Expand");
7444 	    }
7445 
7446 	    sel_opts3[i].ch      = ';';
7447 	    sel_opts3[i].rval    = ';';
7448 	    sel_opts3[i].name    = ";";
7449 	    if(flags & AC_UNSEL)
7450 	      sel_opts3[i++].label = N_("UnSelect");
7451 	    else
7452 	      sel_opts3[i++].label = N_("Select");
7453 	}
7454 
7455 	if(F_ON(F_ENABLE_PRYNT, state)){	/* this one is invisible */
7456 	    sel_opts3[i].ch      = 'y';
7457 	    sel_opts3[i].rval    = '%';
7458 	    sel_opts3[i].name    = "";
7459 	    sel_opts3[i++].label = "";
7460 	}
7461 
7462 	if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){	/* expunge selected messages */
7463 	    sel_opts3[i].ch      = 'x';
7464 	    sel_opts3[i].rval    = 'x';
7465 	    sel_opts3[i].name    = "X";
7466 	    sel_opts3[i++].label = N_("Expunge");
7467 	}
7468 
7469 	if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7470 	   sel_opts3[i].ch      = '#';
7471 	   sel_opts3[i].rval    = '#';
7472 	   sel_opts3[i].name    = "#";
7473 	   sel_opts3[i++].label = N_("Set Role");
7474 	}
7475 
7476 	sel_opts3[i].ch      = KEY_DEL;		/* also invisible */
7477 	sel_opts3[i].rval    = 'd';
7478 	sel_opts3[i].name    = "";
7479 	sel_opts3[i++].label = "";
7480 
7481 	sel_opts3[i].ch = -1;
7482 
7483 	snprintf(prompt, sizeof(prompt), "%s command : ",
7484 		(flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7485 	prompt[sizeof(prompt)-1] = '\0';
7486 	cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7487 				   RB_SEQ_SENSITIVE);
7488 	if(isupper(cmd))
7489 	  cmd = tolower(cmd);
7490     }
7491     else{
7492 	if(preloadkeystroke == KEY_DEL)
7493 	  cmd = 'd';
7494 	else{
7495 	    if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7496 	      cmd = tolower((int) preloadkeystroke);
7497 	    else
7498 	      cmd = (int) preloadkeystroke;	/* shouldn't happen */
7499 	}
7500     }
7501 
7502     switch(cmd){
7503       case 'd' :			/* delete */
7504 	we_cancel = busy_cue(NULL, NULL, 1);
7505 	rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7506 	if(we_cancel)
7507 	  cancel_busy_cue(0);
7508 	break;
7509 
7510       case 'u' :			/* undelete */
7511 	we_cancel = busy_cue(NULL, NULL, 1);
7512 	rv = cmd_undelete(state, msgmap, agg);
7513 	if(we_cancel)
7514 	  cancel_busy_cue(0);
7515 	break;
7516 
7517       case 'r' :			/* reply */
7518 	rv = cmd_reply(state, msgmap, agg, NULL);
7519 	break;
7520 
7521       case 'f' :			/* Forward */
7522 	rv = cmd_forward(state, msgmap, agg, NULL);
7523 	break;
7524 
7525       case '%' :			/* print */
7526 	rv = cmd_print(state, msgmap, agg, MsgIndx);
7527 	break;
7528 
7529       case 't' :			/* take address */
7530 	rv = cmd_take_addr(state, msgmap, agg);
7531 	break;
7532 
7533       case 's' :			/* save */
7534 	rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7535 	break;
7536 
7537       case 'e' :			/* export */
7538 	rv = cmd_export(state, msgmap, q_line, agg);
7539 	break;
7540 
7541       case '|' :			/* pipe */
7542 	rv = cmd_pipe(state, msgmap, agg);
7543 	break;
7544 
7545       case '*' :			/* flag */
7546 	we_cancel = busy_cue(NULL, NULL, 1);
7547 	rv = cmd_flag(state, msgmap, agg);
7548 	if(we_cancel)
7549 	  cancel_busy_cue(0);
7550 	break;
7551 
7552       case 'b' :			/* bounce */
7553 	rv = cmd_bounce(state, msgmap, agg, NULL);
7554 	break;
7555 
7556       case '/' :
7557 	collapse_or_expand(state, stream, msgmap,
7558 			   F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7559 			     ? 0L
7560 			     : mn_get_cur(msgmap));
7561 	break;
7562 
7563       case ':' :
7564 	select_thread_stmp(state, stream, msgmap);
7565 	break;
7566 
7567       case 'x' :			/* Expunge */
7568 	rv = cmd_expunge(state, stream, msgmap, agg);
7569 	break;
7570 
7571       case 'c' :			/* cancel */
7572 	cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7573 					       : "Apply command");
7574 	break;
7575 
7576       case '#' :
7577 	if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7578 	   static ESCKEY_S choose_role[] = {
7579 	    {'r', 'r', "R", N_("Reply")},
7580 	    {'f', 'f', "F", N_("Forward")},
7581 	    {'b', 'b', "B", N_("Bounce")},
7582 	    {-1, 0, NULL, NULL}
7583 	   };
7584 	   int action;
7585 	   ACTION_S *role = NULL;
7586 
7587            action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7588                                    -FOOTER_ROWS(state), choose_role,
7589                                    'r', 'x', h_role_aggregate, RB_NORM);
7590 	   if(action == 'r' || action == 'f' || action == 'b'){
7591 	     void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7592 
7593 	     redraw = state->redrawer;
7594 	     state->redrawer = NULL;
7595 	     prev_screen = state->prev_screen;
7596 	     role = NULL;
7597 	     state->next_screen = SCREEN_FUN_NULL;
7598 
7599 	     if(role_select_screen(state, &role,
7600 			      action == 'f' ? MC_FORWARD :
7601 			       action == 'r' ? MC_REPLY :
7602 			        action == 'b' ? MC_BOUNCE : 0) < 0){
7603 		cmd_cancelled(action == 'f' ? _("Forward") :
7604 				action == 'r' ? _("Reply") : _("Bounce"));
7605 		state->next_screen = prev_screen;
7606 		state->redrawer = redraw;
7607 		state->mangled_screen = 1;
7608 	     }
7609 	     else{
7610 		if(role)
7611 		   role = combine_inherited_role(role);
7612 	        else{
7613 	           role = (ACTION_S *) fs_get(sizeof(*role));
7614 		   memset((void *) role, 0, sizeof(*role));
7615 		   role->nick = cpystr("Default Role");
7616 	 	}
7617 
7618 		state->redrawer = NULL;
7619 		switch(action){
7620 		   case 'r':
7621 			(void) cmd_reply(state, msgmap, agg, role);
7622 			break;
7623 
7624 		   case 'f':
7625 			(void) cmd_forward(state, msgmap, agg, role);
7626 			break;
7627 
7628 		   case 'b':
7629 			(void) cmd_bounce(state, msgmap, agg, role);
7630 			break;
7631 		}
7632 
7633 		if(role)
7634 		  free_action(&role);
7635 
7636 		if(redraw)
7637 		  (*redraw)();
7638 
7639 		state->next_screen = prev_screen;
7640 		state->redrawer = redraw;
7641 		state->mangled_screen = 1;
7642 	     }
7643 	   }
7644         }
7645 	break;
7646 
7647       case 'z' :			/* default */
7648         q_status_message(SM_INFO, 0, 2,
7649 			 "Cancelled, there is no default command");
7650 	break;
7651 
7652       default:
7653 	break;
7654     }
7655 
7656     return(rv);
7657 }
7658 
7659 
7660 /*
7661  * Select by message number ranges.
7662  * Sets searched bits in mail_elts
7663  *
7664  * Args    limitsrch -- limit search to this searchset
7665  *
7666  * Returns 0 on success.
7667  */
7668 int
select_by_number(MAILSTREAM * stream,MSGNO_S * msgmap,SEARCHSET ** limitsrch)7669 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7670 {
7671     int r, end, cur;
7672     long n1, n2, raw;
7673     char number1[16], number2[16], numbers[80], *p, *t;
7674     HelpType help;
7675     MESSAGECACHE *mc;
7676 
7677     numbers[0] = '\0';
7678     ps_global->mangled_footer = 1;
7679     help = NO_HELP;
7680     while(1){
7681 	int flags = OE_APPEND_CURRENT;
7682 
7683         r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7684 			     sizeof(numbers), _(select_num), NULL, help, &flags);
7685         if(r == 4)
7686 	  continue;
7687 
7688         if(r == 3){
7689             help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7690 	    continue;
7691 	}
7692 
7693 	for(t = p = numbers; *p ; p++)	/* strip whitespace */
7694 	  if(!isspace((unsigned char)*p))
7695 	    *t++ = *p;
7696 
7697 	*t = '\0';
7698 
7699         if(r == 1 || numbers[0] == '\0'){
7700 	    cmd_cancelled("Selection by number");
7701 	    return(1);
7702         }
7703 	else
7704 	  break;
7705     }
7706 
7707     for(n1 = 1; n1 <= stream->nmsgs; n1++)
7708       if((mc = mail_elt(stream, n1)) != NULL)
7709         mc->searched = 0;			/* clear searched bits */
7710 
7711     for(p = numbers; *p ; p++){
7712 	t = number1;
7713 	while(*p && isdigit((unsigned char)*p))
7714 	  *t++ = *p++;
7715 
7716 	*t = '\0';
7717 
7718 	end = cur = 0;
7719 	if(number1[0] == '\0'){
7720 	    if(*p == '-'){
7721 		q_status_message1(SM_ORDER | SM_DING, 0, 2,
7722 		   _("Invalid number range, missing number before \"-\": %s"),
7723 		       numbers);
7724 		return(1);
7725 	    }
7726 	    else if(!strucmp("end", p)){
7727 		end = 1;
7728 		p += strlen("end");
7729 	    }
7730 	    else if(!strucmp("$", p)){
7731 		end = 1;
7732 		p++;
7733 	    }
7734 	    else if(*p == '.'){
7735 		cur = 1;
7736 		p++;
7737 	    }
7738 	    else{
7739 		q_status_message1(SM_ORDER | SM_DING, 0, 2,
7740 			        _("Invalid message number: %s"), numbers);
7741 		return(1);
7742 	    }
7743 	}
7744 
7745 	if(end)
7746 	  n1 = mn_get_total(msgmap);
7747 	else if(cur)
7748 	  n1 = mn_get_cur(msgmap);
7749 	else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7750 	    q_status_message1(SM_ORDER | SM_DING, 0, 2,
7751 			      _("\"%s\" out of message number range"),
7752 			      long2string(n1));
7753 	    return(1);
7754 	}
7755 
7756 	t = number2;
7757 	if(*p == '-'){
7758 	    while(*++p && isdigit((unsigned char)*p))
7759 	      *t++ = *p;
7760 
7761 	    *t = '\0';
7762 
7763 	    end = cur = 0;
7764 	    if(number2[0] == '\0'){
7765 		if(!strucmp("end", p)){
7766 		    end = 1;
7767 		    p += strlen("end");
7768 		}
7769 		else if(!strucmp(p, "$")){
7770 		    end = 1;
7771 		    p++;
7772 		}
7773 		else if(*p == '.'){
7774 		    cur = 1;
7775 		    p++;
7776 		}
7777 		else{
7778 		    q_status_message1(SM_ORDER | SM_DING, 0, 2,
7779 		 _("Invalid number range, missing number after \"-\": %s"),
7780 		     numbers);
7781 		    return(1);
7782 		}
7783 	    }
7784 
7785 	    if(end)
7786 	      n2 = mn_get_total(msgmap);
7787 	    else if(cur)
7788 	      n2 = mn_get_cur(msgmap);
7789 	    else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7790 		q_status_message1(SM_ORDER | SM_DING, 0, 2,
7791 				  _("\"%s\" out of message number range"),
7792 				  long2string(n2));
7793 		return(1);
7794 	    }
7795 
7796 	    if(n2 <= n1){
7797 		char t[20];
7798 
7799 		strncpy(t, long2string(n1), sizeof(t));
7800 		t[sizeof(t)-1] = '\0';
7801 		q_status_message2(SM_ORDER | SM_DING, 0, 2,
7802 			  _("Invalid reverse message number range: %s-%s"),
7803 				  t, long2string(n2));
7804 		return(1);
7805 	    }
7806 
7807 	    for(;n1 <= n2; n1++){
7808 		raw = mn_m2raw(msgmap, n1);
7809 		if(raw > 0L
7810 		   && (!(limitsrch && *limitsrch)
7811 		       || in_searchset(*limitsrch, (unsigned long) raw)))
7812 		  mm_searched(stream, raw);
7813 	    }
7814 	}
7815 	else{
7816 	    raw = mn_m2raw(msgmap, n1);
7817 	    if(raw > 0L
7818 	       && (!(limitsrch && *limitsrch)
7819 		   || in_searchset(*limitsrch, (unsigned long) raw)))
7820 	      mm_searched(stream, raw);
7821 	}
7822 
7823 	if(*p == '\0')
7824 	  break;
7825     }
7826 
7827     return(0);
7828 }
7829 
7830 
7831 /*
7832  * Select by thread number ranges.
7833  * Sets searched bits in mail_elts
7834  *
7835  * Args    limitsrch -- limit search to this searchset
7836  *
7837  * Returns 0 on success.
7838  */
7839 int
select_by_thrd_number(MAILSTREAM * stream,MSGNO_S * msgmap,SEARCHSET ** msgset)7840 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7841 {
7842     int r, end, cur;
7843     long n1, n2;
7844     char number1[16], number2[16], numbers[80], *p, *t;
7845     HelpType help;
7846     PINETHRD_S   *thrd = NULL, *th;
7847     MESSAGECACHE *mc;
7848 
7849     numbers[0] = '\0';
7850     ps_global->mangled_footer = 1;
7851     help = NO_HELP;
7852     while(1){
7853 	int flags = OE_APPEND_CURRENT;
7854 
7855         r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7856 			     sizeof(numbers), _(select_num), NULL, help, &flags);
7857         if(r == 4)
7858 	  continue;
7859 
7860         if(r == 3){
7861             help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7862 	    continue;
7863 	}
7864 
7865 	for(t = p = numbers; *p ; p++)	/* strip whitespace */
7866 	  if(!isspace((unsigned char)*p))
7867 	    *t++ = *p;
7868 
7869 	*t = '\0';
7870 
7871         if(r == 1 || numbers[0] == '\0'){
7872 	    cmd_cancelled("Selection by number");
7873 	    return(1);
7874         }
7875 	else
7876 	  break;
7877     }
7878 
7879     for(n1 = 1; n1 <= stream->nmsgs; n1++)
7880       if((mc = mail_elt(stream, n1)) != NULL)
7881         mc->searched = 0;			/* clear searched bits */
7882 
7883     for(p = numbers; *p ; p++){
7884 	t = number1;
7885 	while(*p && isdigit((unsigned char)*p))
7886 	  *t++ = *p++;
7887 
7888 	*t = '\0';
7889 
7890 	end = cur = 0;
7891 	if(number1[0] == '\0'){
7892 	    if(*p == '-'){
7893 		q_status_message1(SM_ORDER | SM_DING, 0, 2,
7894 		   _("Invalid number range, missing number before \"-\": %s"),
7895 		       numbers);
7896 		return(1);
7897 	    }
7898 	    else if(!strucmp("end", p)){
7899 		end = 1;
7900 		p += strlen("end");
7901 	    }
7902 	    else if(!strucmp(p, "$")){
7903 		end = 1;
7904 		p++;
7905 	    }
7906 	    else if(*p == '.'){
7907 		cur = 1;
7908 		p++;
7909 	    }
7910 	    else{
7911 		q_status_message1(SM_ORDER | SM_DING, 0, 2,
7912 			        _("Invalid thread number: %s"), numbers);
7913 		return(1);
7914 	    }
7915 	}
7916 
7917 	if(end)
7918 	  n1 = msgmap->max_thrdno;
7919 	else if(cur){
7920 	  th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7921 	  n1 = th->thrdno;
7922 	}
7923 	else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7924 	    q_status_message1(SM_ORDER | SM_DING, 0, 2,
7925 			      _("\"%s\" out of thread number range"),
7926 			      long2string(n1));
7927 	    return(1);
7928 	}
7929 
7930 	t = number2;
7931 	if(*p == '-'){
7932 
7933 	    while(*++p && isdigit((unsigned char)*p))
7934 	      *t++ = *p;
7935 
7936 	    *t = '\0';
7937 
7938 	    end = 0;
7939 	    if(number2[0] == '\0'){
7940 		if(!strucmp("end", p)){
7941 		    end = 1;
7942 		    p += strlen("end");
7943 		}
7944 		else if(!strucmp("$", p)){
7945 		    end = 1;
7946 		    p++;
7947 		}
7948 		else if(*p == '.'){
7949 		    cur = 1;
7950 		    p++;
7951 		}
7952 		else{
7953 		    q_status_message1(SM_ORDER | SM_DING, 0, 2,
7954 		 _("Invalid number range, missing number after \"-\": %s"),
7955 		     numbers);
7956 		    return(1);
7957 		}
7958 	    }
7959 
7960 	    if(end)
7961 	      n2 = msgmap->max_thrdno;
7962 	    else if(cur){
7963 	      th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7964 	      n2 = th->thrdno;
7965 	    }
7966 	    else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7967 		q_status_message1(SM_ORDER | SM_DING, 0, 2,
7968 				  _("\"%s\" out of thread number range"),
7969 				  long2string(n2));
7970 		return(1);
7971 	    }
7972 
7973 	    if(n2 <= n1){
7974 		char t[20];
7975 
7976 		strncpy(t, long2string(n1), sizeof(t));
7977 		t[sizeof(t)-1] = '\0';
7978 		q_status_message2(SM_ORDER | SM_DING, 0, 2,
7979 			  _("Invalid reverse message number range: %s-%s"),
7980 				  t, long2string(n2));
7981 		return(1);
7982 	    }
7983 
7984 	    for(;n1 <= n2; n1++){
7985 		thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7986 
7987 		if(thrd)
7988 		  set_search_bit_for_thread(stream, thrd, msgset);
7989 	    }
7990 	}
7991 	else{
7992 	    thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7993 
7994 	    if(thrd)
7995 	      set_search_bit_for_thread(stream, thrd, msgset);
7996 	}
7997 
7998 	if(*p == '\0')
7999 	  break;
8000     }
8001 
8002     return(0);
8003 }
8004 
8005 
8006 /*
8007  * Select by message dates.
8008  * Sets searched bits in mail_elts
8009  *
8010  * Args    limitsrch -- limit search to this searchset
8011  *
8012  * Returns 0 on success.
8013  */
8014 int
select_by_date(MAILSTREAM * stream,MSGNO_S * msgmap,long int msgno,SEARCHSET ** limitsrch)8015 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8016 {
8017     int	       r, we_cancel = 0, when = 0;
8018     char       date[100], defdate[100], prompt[128];
8019     time_t     seldate = time(0);
8020     struct tm *seldate_tm;
8021     SEARCHPGM *pgm;
8022     HelpType   help;
8023     static struct _tense {
8024 	char *preamble,
8025 	     *range,
8026 	     *scope;
8027     } tense[] = {
8028 	{"were ", "SENT SINCE",     " (inclusive)"},
8029 	{"were ", "SENT BEFORE",    " (exclusive)"},
8030 	{"were ", "SENT ON",        ""            },
8031 	{"",      "ARRIVED SINCE",  " (inclusive)"},
8032 	{"",      "ARRIVED BEFORE", " (exclusive)"},
8033 	{"",      "ARRIVED ON",     ""            }
8034     };
8035 
8036     date[0]		      = '\0';
8037     ps_global->mangled_footer = 1;
8038     help		      = NO_HELP;
8039 
8040     /*
8041      * If talking to an old server, default to SINCE instead of
8042      * SENTSINCE, which was added later.
8043      */
8044     if(is_imap_stream(stream) && !modern_imap_stream(stream))
8045       when = 3;
8046 
8047     while(1){
8048 	int flags = OE_APPEND_CURRENT;
8049 
8050 	seldate_tm = localtime(&seldate);
8051 	snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8052 		month_abbrev(seldate_tm->tm_mon + 1),
8053 		seldate_tm->tm_year + 1900);
8054 	defdate[sizeof(defdate)-1] = '\0';
8055 	snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8056 		tense[when].preamble, tense[when].range,
8057 		tense[when].scope, defdate);
8058 	prompt[sizeof(prompt)-1] = '\0';
8059 	r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8060 			     prompt, sel_date_opt, help, &flags);
8061 	switch (r){
8062 	  case 1 :
8063 	    cmd_cancelled("Selection by date");
8064 	    return(1);
8065 
8066 	  case 3 :
8067 	    help = (help == NO_HELP) ? h_select_date : NO_HELP;
8068 	    continue;
8069 
8070 	  case 4 :
8071 	    continue;
8072 
8073 	  case 11 :
8074 	    {
8075 		MESSAGECACHE *mc;
8076 		long rawno;
8077 
8078 		if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8079 		   && rawno <= stream->nmsgs
8080 		   && (mc = mail_elt(stream, rawno))){
8081 
8082 		    /* cache not filled in yet? */
8083 		    if(mc->day == 0){
8084 			char seq[20];
8085 
8086 			if(stream->dtb && stream->dtb->flags & DR_NEWS){
8087 			    strncpy(seq,
8088 				    ulong2string(mail_uid(stream, rawno)),
8089 				    sizeof(seq));
8090 			    seq[sizeof(seq)-1] = '\0';
8091 			    mail_fetch_overview(stream, seq, NULL);
8092 			}
8093 			else{
8094 			    strncpy(seq, long2string(rawno),
8095 				    sizeof(seq));
8096 			    seq[sizeof(seq)-1] = '\0';
8097 			    mail_fetch_fast(stream, seq, 0L);
8098 			}
8099 		    }
8100 
8101 		    /* mail_date returns fixed field width date */
8102 		    mail_date(date, mc);
8103 		    date[11] = '\0';
8104 		}
8105 	    }
8106 
8107 	    continue;
8108 
8109 	  case 12 :			/* set default to PREVIOUS day */
8110 	    seldate -= 86400;
8111 	    continue;
8112 
8113 	  case 13 :			/* set default to NEXT day */
8114 	    seldate += 86400;
8115 	    continue;
8116 
8117 	  case 14 :
8118 	    when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8119 	    continue;
8120 
8121 	  default:
8122 	    break;
8123 	}
8124 
8125 	removing_leading_white_space(date);
8126 	removing_trailing_white_space(date);
8127 	if(!*date){
8128 	    strncpy(date, defdate, sizeof(date));
8129 	    date[sizeof(date)-1] = '\0';
8130 	}
8131 
8132 	break;
8133     }
8134 
8135     if((pgm = mail_newsearchpgm()) != NULL){
8136 	MESSAGECACHE elt;
8137 	short          converted_date;
8138 
8139 	if(mail_parse_date(&elt, (unsigned char *) date)){
8140 	    converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8141 
8142 	    switch(when){
8143 	      case 0:
8144 		pgm->sentsince = converted_date;
8145 		break;
8146 	      case 1:
8147 		pgm->sentbefore = converted_date;
8148 		break;
8149 	      case 2:
8150 		pgm->senton = converted_date;
8151 		break;
8152 	      case 3:
8153 		pgm->since = converted_date;
8154 		break;
8155 	      case 4:
8156 		pgm->before = converted_date;
8157 		break;
8158 	      case 5:
8159 		pgm->on = converted_date;
8160 		break;
8161 	    }
8162 
8163 	    pgm->msgno = (limitsrch ? *limitsrch : NULL);
8164 
8165 	    if(ps_global && ps_global->ttyo){
8166 		blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8167 		ps_global->mangled_footer = 1;
8168 	    }
8169 
8170 	    we_cancel = busy_cue(_("Selecting"), NULL, 1);
8171 
8172 	    pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8173 
8174 	    if(we_cancel)
8175 	      cancel_busy_cue(0);
8176 
8177 	    /* we know this was freed in mail_search, let caller know */
8178 	    if(limitsrch)
8179 	      *limitsrch = NULL;
8180 	}
8181 	else{
8182 	    mail_free_searchpgm(&pgm);
8183 	    q_status_message1(SM_ORDER, 3, 3,
8184 			     _("Invalid date entered: %s"), date);
8185 	    return(1);
8186 	}
8187     }
8188 
8189     return(0);
8190 }
8191 
8192 /*
8193  * Select by searching in message headers or body
8194  * using the x-gm-ext-1 capaility. This function
8195  * reads the input from the user and passes it to
8196  * the server directly. We need a x-gm-ext-1 variable
8197  * in the search pgm to carry this information to the
8198  * server.
8199  *
8200  * Sets searched bits in mail_elts
8201  *
8202  * Args    limitsrch -- limit search to this searchset
8203  *
8204  * Returns 0 on success.
8205  */
8206 int
select_by_gm_content(MAILSTREAM * stream,MSGNO_S * msgmap,long int msgno,SEARCHSET ** limitsrch)8207 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8208 {
8209     int          r, we_cancel = 0, rv;
8210     char         tmp[128];
8211     char         namehdr[MAILTMPLEN];
8212     ESCKEY_S     ekey[8];
8213     HelpType help;
8214 
8215     ps_global->mangled_footer = 1;
8216     ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8217 
8218     strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8219     tmp[sizeof(tmp)-1] = '\0';
8220 
8221     namehdr[0] = '\0';
8222 
8223     help = NO_HELP;
8224     while (1){
8225        int flags = OE_APPEND_CURRENT;
8226 
8227        r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8228 			 sizeof(namehdr), tmp, ekey, help, &flags);
8229 
8230        if(r == 4)
8231 	  continue;
8232 
8233        if(r == 3){
8234             help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8235 	    continue;
8236        }
8237 
8238        if (r == 1){
8239 	  cmd_cancelled("Selection by content");
8240 	  return(1);
8241        }
8242 
8243        removing_leading_white_space(namehdr);
8244 
8245        if ((namehdr[0] != '\0')
8246 	     && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8247 	       removing_trailing_white_space(namehdr);
8248 
8249        if (namehdr[0] != '\0')
8250 	  break;
8251     }
8252 
8253     if(ps_global && ps_global->ttyo){
8254 	blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8255 	ps_global->mangled_footer = 1;
8256     }
8257 
8258     we_cancel = busy_cue(_("Selecting"), NULL, 1);
8259 
8260     rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8261     if(we_cancel)
8262       cancel_busy_cue(0);
8263 
8264     return(rv);
8265 }
8266 
8267 /*
8268  * Select by searching in message headers or body.
8269  * Sets searched bits in mail_elts
8270  *
8271  * Args    limitsrch -- limit search to this searchset
8272  *
8273  * Returns 0 on success.
8274  */
8275 int
select_by_text(MAILSTREAM * stream,MSGNO_S * msgmap,long int msgno,SEARCHSET ** limitsrch)8276 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8277 {
8278     int          r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8279     int          not = 0, me = 0;
8280     char         sstring[512], tmp[128];
8281     char        *p, *sval = NULL;
8282     char         buftmp[MAILTMPLEN], namehdr[80];
8283     ESCKEY_S     ekey[8];
8284     ENVELOPE    *env = NULL;
8285     HelpType     help;
8286     unsigned     flagsforhist = 0;
8287     static HISTORY_S *history = NULL;
8288     static char *recip = "RECIPIENTS";
8289     static char *partic = "PARTICIPANTS";
8290     static char *match_me = N_("[Match_My_Addresses]");
8291     static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8292 
8293     ps_global->mangled_footer = 1;
8294     ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8295 
8296     while(1){
8297 	type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8298 			     -FOOTER_ROWS(ps_global), sel_text_opt,
8299 			     's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8300 
8301 	if(type == '!')
8302 	  not = !not;
8303 	else if(type == 3){
8304 	    helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8305 		   HLPD_SIMPLE);
8306 	    ps_global->mangled_screen = 1;
8307 	}
8308 	else
8309 	  break;
8310     }
8311 
8312     /*
8313      * prepare some friendly defaults...
8314      */
8315     switch(type){
8316       case 't' :			/* address fields, offer To or From */
8317       case 'f' :
8318       case 'c' :
8319       case 'r' :
8320       case 'p' :
8321 	sval          = (type == 't') ? "TO" :
8322 			  (type == 'f') ? "FROM" :
8323 			    (type == 'c') ? "CC" :
8324 			      (type == 'r') ? recip : partic;
8325 	ekey[ekeyi].ch    = ctrl('T');
8326 	ekey[ekeyi].name  = "^T";
8327 	ekey[ekeyi].rval  = 10;
8328 	/* TRANSLATORS: use Current To Address */
8329 	ekey[ekeyi++].label = N_("Cur To");
8330 	ekey[ekeyi].ch    = ctrl('R');
8331 	ekey[ekeyi].name  = "^R";
8332 	ekey[ekeyi].rval  = 11;
8333 	/* TRANSLATORS: use Current From Address */
8334 	ekey[ekeyi++].label = N_("Cur From");
8335 	ekey[ekeyi].ch    = ctrl('W');
8336 	ekey[ekeyi].name  = "^W";
8337 	ekey[ekeyi].rval  = 12;
8338 	/* TRANSLATORS: use Current Cc Address */
8339 	ekey[ekeyi++].label = N_("Cur Cc");
8340 	ekey[ekeyi].ch    = ctrl('Y');
8341 	ekey[ekeyi].name  = "^Y";
8342 	ekey[ekeyi].rval  = 13;
8343 	/* TRANSLATORS: Match Me means match my address */
8344 	ekey[ekeyi++].label = N_("Match Me");
8345 	ekey[ekeyi].ch    = 0;
8346 	ekey[ekeyi].name  = "";
8347 	ekey[ekeyi].rval  = 0;
8348 	ekey[ekeyi++].label = "";
8349 	break;
8350 
8351       case 's' :
8352 	sval          = "SUBJECT";
8353 	ekey[ekeyi].ch    = ctrl('X');
8354 	ekey[ekeyi].name  = "^X";
8355 	ekey[ekeyi].rval  = 14;
8356 	/* TRANSLATORS: use Current Subject */
8357 	ekey[ekeyi++].label = N_("Cur Subject");
8358 	break;
8359 
8360       case 'a' :
8361 	sval = "TEXT";
8362 	break;
8363 
8364       case 'b' :
8365 	sval = "BODYTEXT";
8366 	break;
8367 
8368       case 'h' :
8369 	strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8370 	tmp[sizeof(tmp)-1] = '\0';
8371 	flags = OE_APPEND_CURRENT;
8372 	namehdr[0] = '\0';
8373 	r = 'x';
8374 	while (r == 'x'){
8375 	       int done = 0;
8376 
8377 	       r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8378 				 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8379 	       if (r == 1){
8380 		  cmd_cancelled("Selection by text");
8381 		  return(1);
8382 	       }
8383 	       removing_leading_white_space(namehdr);
8384 	       while(!done){
8385 	          while ((namehdr[0] != '\0') && /* remove trailing ":" */
8386 			(namehdr[strlen(namehdr) - 1] == ':'))
8387 		     namehdr[strlen(namehdr) - 1] = '\0';
8388 		  if ((namehdr[0] != '\0')
8389 		     && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8390 		       removing_trailing_white_space(namehdr);
8391 		  else
8392 		    done++;
8393 	       }
8394 	       if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8395 		   strchr(namehdr,':'))
8396 		  namehdr[0] = '\0';
8397 	       if (namehdr[0] == '\0')
8398 	         r = 'x';
8399 	}
8400 	sval = namehdr;
8401 	break;
8402 
8403       case 'x':
8404 	break;
8405 
8406       default:
8407 	dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8408 	return(1);
8409     }
8410 
8411     ekey[ekeyi].ch      = KEY_UP;
8412     ekey[ekeyi].rval    = 30;
8413     ekey[ekeyi].name    = "";
8414     ku = ekeyi;
8415     ekey[ekeyi++].label = "";
8416 
8417     ekey[ekeyi].ch      = KEY_DOWN;
8418     ekey[ekeyi].rval    = 31;
8419     ekey[ekeyi].name    = "";
8420     ekey[ekeyi++].label = "";
8421 
8422     ekey[ekeyi].ch = -1;
8423 
8424     if(type != 'x'){
8425 
8426 	init_hist(&history, HISTSIZE);
8427 
8428 	if(ekey[0].ch > -1 && msgno > 0L
8429 	   && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8430 					     NULL)))
8431 	  ekey[0].ch = -1;
8432 
8433 	sstring[0] = '\0';
8434 	help = NO_HELP;
8435 	r = type;
8436 	while(r != 'x'){
8437 	    if(not)
8438 	      /* TRANSLATORS: character String in message <message number> to NOT match : " */
8439 	      snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8440 	    else
8441 	      snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8442 
8443 	    if(items_in_hist(history) > 0){
8444 		ekey[ku].name  = HISTORY_UP_KEYNAME;
8445 		ekey[ku].label = HISTORY_KEYLABEL;
8446 		ekey[ku+1].name  = HISTORY_DOWN_KEYNAME;
8447 		ekey[ku+1].label = HISTORY_KEYLABEL;
8448 	    }
8449 	    else{
8450 		ekey[ku].name  = "";
8451 		ekey[ku].label = "";
8452 		ekey[ku+1].name  = "";
8453 		ekey[ku+1].label = "";
8454 	    }
8455 
8456 	    flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8457 	    r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8458 				 sizeof(sstring), tmp, ekey, help, &flags);
8459 
8460 	    if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8461 	      me = 0;
8462 
8463 	    switch(r){
8464 	      case 3 :
8465 		help = (help == NO_HELP)
8466 			? (not
8467 			    ? ((type == 'f') ? h_select_txt_not_from
8468 			      : (type == 't') ? h_select_txt_not_to
8469 			       : (type == 'c') ? h_select_txt_not_cc
8470 				: (type == 's') ? h_select_txt_not_subj
8471 				 : (type == 'a') ? h_select_txt_not_all
8472 				  : (type == 'r') ? h_select_txt_not_recip
8473 				   : (type == 'p') ? h_select_txt_not_partic
8474 				    : (type == 'b') ? h_select_txt_not_body
8475 				     :                 NO_HELP)
8476 			    : ((type == 'f') ? h_select_txt_from
8477 			      : (type == 't') ? h_select_txt_to
8478 			       : (type == 'c') ? h_select_txt_cc
8479 				: (type == 's') ? h_select_txt_subj
8480 				 : (type == 'a') ? h_select_txt_all
8481 				  : (type == 'r') ? h_select_txt_recip
8482 				   : (type == 'p') ? h_select_txt_partic
8483 				    : (type == 'b') ? h_select_txt_body
8484 				     :                 NO_HELP))
8485 			: NO_HELP;
8486 
8487 	      case 4 :
8488 		continue;
8489 
8490 	      case 10 :			/* To: default */
8491 		if(env && env->to && env->to->mailbox){
8492 		    snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8493 			  env->to->host ? "@" : "",
8494 			  env->to->host ? env->to->host : "");
8495 		    sstring[sizeof(sstring)-1] = '\0';
8496 		}
8497 		continue;
8498 
8499 	      case 11 :			/* From: default */
8500 		if(env && env->from && env->from->mailbox){
8501 		    snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8502 			  env->from->host ? "@" : "",
8503 			  env->from->host ? env->from->host : "");
8504 		    sstring[sizeof(sstring)-1] = '\0';
8505 		}
8506 		continue;
8507 
8508 	      case 12 :			/* Cc: default */
8509 		if(env && env->cc && env->cc->mailbox){
8510 		    snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8511 			  env->cc->host ? "@" : "",
8512 			  env->cc->host ? env->cc->host : "");
8513 		    sstring[sizeof(sstring)-1] = '\0';
8514 		}
8515 		continue;
8516 
8517 	      case 13 :			/* Match my addresses */
8518 		me++;
8519 		snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8520 		continue;
8521 
8522 	      case 14 :			/* Subject: default */
8523 		if(env && env->subject && env->subject[0]){
8524 		    char *q = NULL;
8525 
8526 		    q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8527 							SIZEOF_20KBUF, env->subject);
8528 		    snprintf(sstring, sizeof(sstring), "%s", q);
8529 		    sstring[sizeof(sstring)-1] = '\0';
8530 		}
8531 
8532 		continue;
8533 
8534 	      case 30 :
8535 		flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8536 		if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8537 		    strncpy(sstring, p, sizeof(sstring));
8538 		    sstring[sizeof(sstring)-1] = '\0';
8539 		    if(history->hist[history->curindex]){
8540 			flagsforhist = history->hist[history->curindex]->flags;
8541 			not = (flagsforhist & 0x1) ? 1 : 0;
8542 			me = (flagsforhist & 0x2) ? 1 : 0;
8543 		    }
8544 		}
8545 		else
8546 		  Writechar(BELL, 0);
8547 
8548 		continue;
8549 
8550 	      case 31 :
8551 		flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8552 		if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8553 		    strncpy(sstring, p, sizeof(sstring));
8554 		    sstring[sizeof(sstring)-1] = '\0';
8555 		    if(history->hist[history->curindex]){
8556 			flagsforhist = history->hist[history->curindex]->flags;
8557 			not = (flagsforhist & 0x1) ? 1 : 0;
8558 			me = (flagsforhist & 0x2) ? 1 : 0;
8559 		    }
8560 		}
8561 		else
8562 		  Writechar(BELL, 0);
8563 
8564 		continue;
8565 
8566 	      default :
8567 		break;
8568 	    }
8569 
8570 	    if(r == 1 || sstring[0] == '\0')
8571 	      r = 'x';
8572 
8573 	    break;
8574 	}
8575     }
8576 
8577     if(type == 'x' || r == 'x'){
8578 	cmd_cancelled("Selection by text");
8579 	return(1);
8580     }
8581 
8582     if(ps_global && ps_global->ttyo){
8583 	blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8584 	ps_global->mangled_footer = 1;
8585     }
8586 
8587     we_cancel = busy_cue(_("Selecting"), NULL, 1);
8588 
8589     flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8590     save_hist(history, sstring, flagsforhist, NULL);
8591 
8592     rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8593     if(we_cancel)
8594       cancel_busy_cue(0);
8595 
8596     return(rv);
8597 }
8598 
8599 
8600 /*
8601  * Select by message size.
8602  * Sets searched bits in mail_elts
8603  *
8604  * Args    limitsrch -- limit search to this searchset
8605  *
8606  * Returns 0 on success.
8607  */
8608 int
select_by_size(MAILSTREAM * stream,SEARCHSET ** limitsrch)8609 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8610 {
8611     int        r, large = 1, we_cancel = 0;
8612     unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8613     char       size[16], numbers[80], *p, *t;
8614     HelpType   help;
8615     SEARCHPGM *pgm;
8616     long       flags = (SE_NOPREFETCH | SE_FREE);
8617 
8618     numbers[0] = '\0';
8619     ps_global->mangled_footer = 1;
8620 
8621     help = NO_HELP;
8622     while(1){
8623 	int flgs = OE_APPEND_CURRENT;
8624 
8625 	sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8626 
8627         r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8628 			     sizeof(numbers), large ? _(select_size_larger_msg)
8629 						    : _(select_size_smaller_msg),
8630 			     sel_size_opt, help, &flgs);
8631         if(r == 4)
8632 	  continue;
8633 
8634         if(r == 14){
8635 	    large = 1 - large;
8636 	    continue;
8637 	}
8638 
8639         if(r == 3){
8640             help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8641 					      : h_select_by_smaller_size)
8642 				     : NO_HELP;
8643 	    continue;
8644 	}
8645 
8646 	for(t = p = numbers; *p ; p++)	/* strip whitespace */
8647 	  if(!isspace((unsigned char)*p))
8648 	    *t++ = *p;
8649 
8650 	*t = '\0';
8651 
8652         if(r == 1 || numbers[0] == '\0'){
8653 	    cmd_cancelled("Selection by size");
8654 	    return(1);
8655         }
8656 	else
8657 	  break;
8658     }
8659 
8660     if(numbers[0] == '-'){
8661 	q_status_message1(SM_ORDER | SM_DING, 0, 2,
8662 			  _("Invalid size entered: %s"), numbers);
8663 	return(1);
8664     }
8665 
8666     t = size;
8667     p = numbers;
8668 
8669     while(*p && isdigit((unsigned char)*p))
8670       *t++ = *p++;
8671 
8672     *t = '\0';
8673 
8674     if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8675 	size[0] = '0';
8676 	size[1] = '\0';
8677     }
8678 
8679     if(size[0] == '\0'){
8680 	q_status_message1(SM_ORDER | SM_DING, 0, 2,
8681 			  _("Invalid size entered: %s"), numbers);
8682 	return(1);
8683     }
8684 
8685     n = strtoul(size, (char **)NULL, 10);
8686 
8687     size[0] = '\0';
8688     if(*p == '.'){
8689 	/*
8690 	 * We probably ought to just use atof() to convert 1.1 into a
8691 	 * double, but since we haven't used atof() anywhere else I'm
8692 	 * reluctant to use it because of portability concerns.
8693 	 */
8694 	p++;
8695 	t = size;
8696 	while(*p && isdigit((unsigned char)*p)){
8697 	    *t++ = *p++;
8698 	    divisor *= 10;
8699 	}
8700 
8701 	*t = '\0';
8702 
8703 	if(size[0])
8704 	  numerator = strtoul(size, (char **)NULL, 10);
8705     }
8706 
8707     switch(*p){
8708       case 'g':
8709       case 'G':
8710         mult *= 1000;
8711 	/* fall through */
8712 
8713       case 'm':
8714       case 'M':
8715         mult *= 1000;
8716 	/* fall through */
8717 
8718       case 'k':
8719       case 'K':
8720         mult *= 1000;
8721 	break;
8722     }
8723 
8724     n = n * mult + (numerator * mult) / divisor;
8725 
8726     pgm = mail_newsearchpgm();
8727     if(large)
8728 	pgm->larger = n;
8729     else
8730 	pgm->smaller = n;
8731 
8732     if(is_imap_stream(stream) && !modern_imap_stream(stream))
8733       flags |= SE_NOSERVER;
8734 
8735     if(ps_global && ps_global->ttyo){
8736 	blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8737 	ps_global->mangled_footer = 1;
8738     }
8739 
8740     we_cancel = busy_cue(_("Selecting"), NULL, 1);
8741 
8742     pgm->msgno = (limitsrch ? *limitsrch : NULL);
8743     pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8744     /* we know this was freed in mail_search, let caller know */
8745     if(limitsrch)
8746       *limitsrch = NULL;
8747 
8748     if(we_cancel)
8749       cancel_busy_cue(0);
8750 
8751     return(0);
8752 }
8753 
8754 
8755 /*
8756  * visible_searchset -- return c-client search set unEXLDed
8757  *			sequence numbers
8758  */
8759 SEARCHSET *
visible_searchset(MAILSTREAM * stream,MSGNO_S * msgmap)8760 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8761 {
8762     long       n, run;
8763     SEARCHSET *full_set = NULL, **set;
8764 
8765     /*
8766      * If we're talking to anything other than a server older than
8767      * imap 4rev1, build a searchset otherwise it'll choke.
8768      */
8769     if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8770 	if(any_lflagged(msgmap, MN_EXLD)){
8771 	    for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8772 	      if(get_lflag(stream, NULL, n, MN_EXLD)){
8773 		  if(run){		/* previous NOT excluded? */
8774 		      if(run > 1L)
8775 			(*set)->last = n - 1L;
8776 
8777 		      set = &(*set)->next;
8778 		      run = 0L;
8779 		  }
8780 	      }
8781 	      else if(run++){		/* next in run */
8782 		  (*set)->last = n;
8783 	      }
8784 	      else{				/* start of run */
8785 		  *set = mail_newsearchset();
8786 		  (*set)->first = n;
8787 	      }
8788 	}
8789 	else{
8790 	    full_set = mail_newsearchset();
8791 	    full_set->first = 1L;
8792 	    full_set->last  = stream->nmsgs;
8793 	}
8794     }
8795 
8796     return(full_set);
8797 }
8798 
8799 
8800 /*
8801  * Select by message status bits.
8802  * Sets searched bits in mail_elts
8803  *
8804  * Args    limitsrch -- limit search to this searchset
8805  *
8806  * Returns 0 on success.
8807  */
8808 int
select_by_status(MAILSTREAM * stream,SEARCHSET ** limitsrch)8809 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8810 {
8811     int s, not = 0, we_cancel = 0, rv;
8812 
8813     while(1){
8814 	s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8815 			  -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8816 			  NO_HELP, RB_NORM|RB_RET_HELP);
8817 
8818 	if(s == 'x'){
8819 	    cmd_cancelled("Selection by status");
8820 	    return(1);
8821 	}
8822 	else if(s == 3){
8823 	    helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8824 		   HLPD_SIMPLE);
8825 	    ps_global->mangled_screen = 1;
8826 	}
8827 	else if(s == '!')
8828 	  not = !not;
8829 	else
8830 	  break;
8831     }
8832 
8833     if(ps_global && ps_global->ttyo){
8834 	blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8835 	ps_global->mangled_footer = 1;
8836     }
8837 
8838     we_cancel = busy_cue(_("Selecting"), NULL, 1);
8839     rv = agg_flag_select(stream, not, s, limitsrch);
8840     if(we_cancel)
8841       cancel_busy_cue(0);
8842 
8843     return(rv);
8844 }
8845 
8846 
8847 /*
8848  * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8849  * Sets searched bits in mail_elts
8850  *
8851  * Args    limitsrch -- limit search to this searchset
8852  *
8853  * Returns 0 on success.
8854  */
8855 int
select_by_rule(MAILSTREAM * stream,SEARCHSET ** limitsrch)8856 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8857 {
8858     char       rulenick[1000], *nick;
8859     PATGRP_S  *patgrp;
8860     int        r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8861 				    | ROLE_DO_INCOLS
8862 				    | ROLE_DO_ROLES
8863 				    | ROLE_DO_SCORES
8864 				    | ROLE_DO_OTHER
8865 				    | ROLE_DO_FILTER;
8866 
8867     rulenick[0] = '\0';
8868     ps_global->mangled_footer = 1;
8869 
8870     do{
8871 	int oe_flags;
8872 
8873 	oe_flags = OE_APPEND_CURRENT;
8874         r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8875 			     sizeof(rulenick),
8876 			     not ? _("Rule to NOT match: ")
8877 			         : _("Rule to match: "),
8878 			     sel_key_opt, NO_HELP, &oe_flags);
8879 
8880 	if(r == 14){
8881 	    /* select rulenick from a list */
8882 	    if((nick=choose_a_rule(rflags)) != NULL){
8883 		strncpy(rulenick, nick, sizeof(rulenick)-1);
8884 		rulenick[sizeof(rulenick)-1] = '\0';
8885 		fs_give((void **) &nick);
8886 	    }
8887 	    else
8888 	      r = 4;
8889 	}
8890 	else if(r == '!')
8891 	  not = !not;
8892 
8893 	if(r == 3){
8894 	    helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8895 	    ps_global->mangled_screen = 1;
8896 	}
8897 	else if(r == 1){
8898 	    cmd_cancelled("Selection by Rule");
8899 	    return(1);
8900 	}
8901 
8902 	removing_leading_and_trailing_white_space(rulenick);
8903 
8904     }while(r == 3 || r == 4 || r == '!');
8905 
8906 
8907     /*
8908      * The approach of requiring a nickname instead of just allowing the
8909      * user to select from the list of rules has the drawback that a rule
8910      * may not have a nickname, or there may be more than one rule with
8911      * the same nickname. However, it has the benefit of allowing the user
8912      * to type in the nickname and, most importantly, allows us to set
8913      * up the ! (not). We could incorporate the ! into the selection
8914      * screen, but this is easier and also allows the typing of nicks.
8915      * User can just set up nicknames if they want to use this feature.
8916      */
8917     patgrp = nick_to_patgrp(rulenick, rflags);
8918 
8919     if(patgrp){
8920 	if(ps_global && ps_global->ttyo){
8921 	    blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8922 	    ps_global->mangled_footer = 1;
8923 	}
8924 
8925 	we_cancel = busy_cue(_("Selecting"), NULL, 1);
8926 	match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8927 		      get_msg_score,
8928 		      (not ? MP_NOT : 0) | SE_NOPREFETCH);
8929 	free_patgrp(&patgrp);
8930 	if(we_cancel)
8931 	  cancel_busy_cue(0);
8932     }
8933 
8934     if(limitsrch && *limitsrch){
8935 	mail_free_searchset(limitsrch);
8936 	*limitsrch = NULL;
8937     }
8938 
8939     return(0);
8940 }
8941 
8942 
8943 /*
8944  * Allow user to choose a rule from their list of rules.
8945  *
8946  * Returns an allocated rule nickname on success, NULL otherwise.
8947  */
8948 char *
choose_a_rule(int rflags)8949 choose_a_rule(int rflags)
8950 {
8951     char      *choice = NULL;
8952     char     **rule_list, **lp;
8953     int        cnt = 0;
8954     PAT_S     *pat;
8955     PAT_STATE  pstate;
8956 
8957     if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8958 	q_status_message(SM_ORDER, 3, 3,
8959 			 _("No rules available. Use Setup/Rules to add some."));
8960 	return(choice);
8961     }
8962 
8963     /*
8964      * Build a list of rules to choose from.
8965      */
8966 
8967     for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8968       cnt++;
8969 
8970     if(cnt <= 0){
8971 	q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8972 	return(choice);
8973     }
8974 
8975     lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8976     memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8977 
8978     for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8979       *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8980 			  ? pat->patgrp->nick : "?");
8981 
8982     /* TRANSLATORS: SELECT A RULE is a screen title
8983        TRANSLATORS: Print something1 using something2.
8984        "rules" is something1 */
8985     choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8986 				   _("rules"), h_select_rule_screen,
8987 				   _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8988 
8989     if(!choice)
8990       q_status_message(SM_ORDER, 1, 4, "No choice");
8991 
8992     free_list_array(&rule_list);
8993 
8994     return(choice);
8995 }
8996 
8997 
8998 /*
8999  * Select by current thread.
9000  * Sets searched bits in mail_elts for this entire thread
9001  *
9002  * Args    limitsrch -- limit search to this searchset
9003  *
9004  * Returns 0 on success.
9005  */
9006 int
select_by_thread(MAILSTREAM * stream,MSGNO_S * msgmap,SEARCHSET ** limitsrch)9007 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
9008 {
9009     long n;
9010     PINETHRD_S *thrd = NULL;
9011     int ret = 1;
9012     MESSAGECACHE *mc;
9013 
9014     if(!stream)
9015       return(ret);
9016 
9017     for(n = 1L; n <= stream->nmsgs; n++)
9018       if((mc = mail_elt(stream, n)) != NULL)
9019         mc->searched = 0;			/* clear searched bits */
9020 
9021     thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9022     if(thrd && thrd->top && thrd->top != thrd->rawno)
9023       thrd = fetch_thread(stream, thrd->top);
9024 
9025     /*
9026      * This doesn't unselect if the thread is already selected
9027      * (like select current does), it always selects.
9028      * There is no way to select ! this thread.
9029      */
9030     if(thrd){
9031 	set_search_bit_for_thread(stream, thrd, limitsrch);
9032 	ret = 0;
9033     }
9034 
9035     return(ret);
9036 }
9037 
9038 
9039 /*
9040  * Select by message keywords.
9041  * Sets searched bits in mail_elts
9042  *
9043  * Args    limitsrch -- limit search to this searchset
9044  *
9045  * Returns 0 on success.
9046  */
9047 int
select_by_keyword(MAILSTREAM * stream,SEARCHSET ** limitsrch)9048 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9049 {
9050     int        r, not = 0, we_cancel = 0;
9051     char       keyword[MAXUSERFLAG+1], *kword;
9052     char      *error = NULL, *p, *prompt;
9053     HelpType   help;
9054     SEARCHPGM *pgm;
9055 
9056     keyword[0] = '\0';
9057     ps_global->mangled_footer = 1;
9058 
9059     help = NO_HELP;
9060     do{
9061 	int oe_flags;
9062 
9063 	if(error){
9064 	    q_status_message(SM_ORDER, 3, 4, error);
9065 	    fs_give((void **) &error);
9066 	}
9067 
9068 	if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9069 	    if(not)
9070 	      prompt = _("Keyword (or keyword initial) to NOT match: ");
9071 	    else
9072 	      prompt = _("Keyword (or keyword initial) to match: ");
9073 	}
9074 	else{
9075 	    if(not)
9076 	      prompt = _("Keyword to NOT match: ");
9077 	    else
9078 	      prompt = _("Keyword to match: ");
9079 	}
9080 
9081 	oe_flags = OE_APPEND_CURRENT;
9082         r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9083 			     sizeof(keyword),
9084 			     prompt, sel_key_opt, help, &oe_flags);
9085 
9086 	if(r == 14){
9087 	    /* select keyword from a list */
9088 	    if((kword=choose_a_keyword()) != NULL){
9089 		strncpy(keyword, kword, sizeof(keyword)-1);
9090 		keyword[sizeof(keyword)-1] = '\0';
9091 		fs_give((void **) &kword);
9092 	    }
9093 	    else
9094 	      r = 4;
9095 	}
9096 	else if(r == '!')
9097 	  not = !not;
9098 
9099 	if(r == 3)
9100 	  help = help == NO_HELP ? h_select_keyword : NO_HELP;
9101 	else if(r == 1){
9102 	    cmd_cancelled("Selection by keyword");
9103 	    return(1);
9104 	}
9105 
9106 	removing_leading_and_trailing_white_space(keyword);
9107 
9108     }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
9109 
9110 
9111     if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9112 	p = initial_to_keyword(keyword);
9113 	if(p != keyword){
9114 	    strncpy(keyword, p, sizeof(keyword)-1);
9115 	    keyword[sizeof(keyword)-1] = '\0';
9116 	}
9117     }
9118 
9119     /*
9120      * We want to check the keyword, not the nickname of the keyword,
9121      * so convert it to the keyword if necessary.
9122      */
9123     p = nick_to_keyword(keyword);
9124     if(p != keyword){
9125 	strncpy(keyword, p, sizeof(keyword)-1);
9126 	keyword[sizeof(keyword)-1] = '\0';
9127     }
9128 
9129     pgm = mail_newsearchpgm();
9130     if(not){
9131 	pgm->unkeyword = mail_newstringlist();
9132 	pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9133 	pgm->unkeyword->text.size = strlen(keyword);
9134     }
9135     else{
9136 	pgm->keyword = mail_newstringlist();
9137 	pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9138 	pgm->keyword->text.size = strlen(keyword);
9139     }
9140 
9141     if(ps_global && ps_global->ttyo){
9142 	blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9143 	ps_global->mangled_footer = 1;
9144     }
9145 
9146     we_cancel = busy_cue(_("Selecting"), NULL, 1);
9147 
9148     pgm->msgno = (limitsrch ? *limitsrch : NULL);
9149     pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9150     /* we know this was freed in mail_search, let caller know */
9151     if(limitsrch)
9152       *limitsrch = NULL;
9153 
9154     if(we_cancel)
9155       cancel_busy_cue(0);
9156 
9157     return(0);
9158 }
9159 
9160 
9161 /*
9162  * Allow user to choose a keyword from their list of keywords.
9163  *
9164  * Returns an allocated keyword on success, NULL otherwise.
9165  */
9166 char *
choose_a_keyword(void)9167 choose_a_keyword(void)
9168 {
9169     char      *choice = NULL;
9170     char     **keyword_list, **lp;
9171     int        cnt;
9172     KEYWORD_S *kw;
9173 
9174     /*
9175      * Build a list of keywords to choose from.
9176      */
9177 
9178     for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9179       cnt++;
9180 
9181     if(cnt <= 0){
9182 	q_status_message(SM_ORDER, 3, 4,
9183 	    _("No keywords defined, use \"keywords\" option in Setup/Config"));
9184 	return(choice);
9185     }
9186 
9187     lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9188     memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9189 
9190     for(kw = ps_global->keywords; kw; kw = kw->next)
9191       *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9192 
9193     /* TRANSLATORS: SELECT A KEYWORD is a screen title
9194        TRANSLATORS: Print something1 using something2.
9195        "keywords" is something1 */
9196     choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9197 				   _("keywords"), h_select_keyword_screen,
9198 				   _("HELP FOR SELECTING A KEYWORD"), NULL);
9199 
9200     if(!choice)
9201       q_status_message(SM_ORDER, 1, 4, "No choice");
9202 
9203     free_list_array(&keyword_list);
9204 
9205     return(choice);
9206 }
9207 
9208 
9209 /*
9210  * Allow user to choose a list of keywords from their list of keywords.
9211  *
9212  * Returns allocated list.
9213  */
9214 char **
choose_list_of_keywords(void)9215 choose_list_of_keywords(void)
9216 {
9217     LIST_SEL_S *listhead, *ls, *p;
9218     char      **ret = NULL;
9219     int         cnt, i;
9220     KEYWORD_S  *kw;
9221 
9222     /*
9223      * Build a list of keywords to choose from.
9224      */
9225 
9226     p = listhead = NULL;
9227     for(kw = ps_global->keywords; kw; kw = kw->next){
9228 
9229 	ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9230 	memset(ls, 0, sizeof(*ls));
9231 	ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9232 
9233 	if(p){
9234 	    p->next = ls;
9235 	    p = p->next;
9236 	}
9237 	else
9238 	  listhead = p = ls;
9239     }
9240 
9241     if(!listhead)
9242       return(ret);
9243 
9244     /* TRANSLATORS: SELECT KEYWORDS is a screen title
9245        Print something1 using something2.
9246        "keywords" is something1 */
9247     if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9248 				_("SELECT KEYWORDS"), _("keywords"),
9249 				h_select_multkeyword_screen,
9250 			        _("HELP FOR SELECTING KEYWORDS"), NULL)){
9251 	for(cnt = 0, p = listhead; p; p = p->next)
9252 	  if(p->selected)
9253 	    cnt++;
9254 
9255 	ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9256 	memset(ret, 0, (cnt+1) * sizeof(*ret));
9257 	for(i = 0, p = listhead; p; p = p->next)
9258 	  if(p->selected)
9259 	    ret[i++] = cpystr(p->item ? p->item : "");
9260     }
9261 
9262     free_list_sel(&listhead);
9263 
9264     return(ret);
9265 }
9266 
9267 
9268 /*
9269  * Allow user to choose a charset
9270  *
9271  * Returns an allocated charset on success, NULL otherwise.
9272  */
9273 char *
choose_a_charset(int which_charsets)9274 choose_a_charset(int which_charsets)
9275 {
9276     char      *choice = NULL;
9277     char     **charset_list, **lp;
9278     const CHARSET *cs;
9279     int        cnt;
9280 
9281     /*
9282      * Build a list of charsets to choose from.
9283      */
9284 
9285     for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9286 	if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9287 	   && ((which_charsets & CAC_ALL)
9288 	       || (which_charsets & CAC_POSTING
9289 		   && cs->flags & CF_POSTING)
9290 	       || (which_charsets & CAC_DISPLAY
9291 		   && cs->type != CT_2022
9292 		   && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9293 	  cnt++;
9294     }
9295 
9296     if(cnt <= 0){
9297 	q_status_message(SM_ORDER, 3, 4,
9298 	    _("No charsets found? Enter charset manually."));
9299 	return(choice);
9300     }
9301 
9302     lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9303     memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9304 
9305     for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9306 	if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9307 	   && ((which_charsets & CAC_ALL)
9308 	       || (which_charsets & CAC_POSTING
9309 		   && cs->flags & CF_POSTING)
9310 	       || (which_charsets & CAC_DISPLAY
9311 		   && cs->type != CT_2022
9312 		   && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9313 	  *lp++ = cpystr(cs->name);
9314     }
9315 
9316     /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9317        TRANSLATORS: Print something1 using something2.
9318        "character sets" is something1 */
9319     choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9320 				   _("character sets"), h_select_charset_screen,
9321 				   _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9322 
9323     if(!choice)
9324       q_status_message(SM_ORDER, 1, 4, "No choice");
9325 
9326     free_list_array(&charset_list);
9327 
9328     return(choice);
9329 }
9330 
9331 
9332 /*
9333  * Allow user to choose a list of character sets and/or scripts
9334  *
9335  * Returns allocated list.
9336  */
9337 char **
choose_list_of_charsets(void)9338 choose_list_of_charsets(void)
9339 {
9340     LIST_SEL_S *listhead, *ls, *p;
9341     char      **ret = NULL;
9342     int         cnt, i, got_one;
9343     const CHARSET *cs;
9344     SCRIPT     *s;
9345     char       *q, *t;
9346     long 	width, limit;
9347     char        buf[1024], *folded;
9348 
9349     /*
9350      * Build a list of charsets to choose from.
9351      */
9352 
9353     p = listhead = NULL;
9354 
9355     /* this width is determined by select_from_list_screen() */
9356     width = ps_global->ttyo->screen_cols - 4;
9357 
9358     /* first comes a list of scripts (sets of character sets) */
9359     for(s = utf8_script(NIL); s && s->name; s++){
9360 
9361 	limit = sizeof(buf)-1;
9362 	q = buf;
9363 	memset(q, 0, limit+1);
9364 
9365 	if(s->name)
9366 	  sstrncpy(&q, s->name, limit);
9367 
9368 	if(s->description){
9369 	    sstrncpy(&q, " (", limit-(q-buf));
9370 	    sstrncpy(&q, s->description, limit-(q-buf));
9371 	    sstrncpy(&q, ")", limit-(q-buf));
9372 	}
9373 
9374 	/* add the list of charsets that are in this script */
9375 	got_one = 0;
9376 	for(cs = utf8_charset(NIL);
9377 	    cs && cs->name && (q-buf) < limit; cs++){
9378 	    if(cs->script & s->script){
9379 		/*
9380 		 * Filter out some un-useful members of the list.
9381 		 * UTF-7 and UTF-8 weren't actually in the list at the
9382 		 * time this was written. Just making sure.
9383 		 */
9384 		if(!strucmp(cs->name, "ISO-2022-JP-2")
9385 		   || !strucmp(cs->name, "UTF-7")
9386 		   || !strucmp(cs->name, "UTF-8"))
9387 		  continue;
9388 
9389 		if(got_one)
9390 		  sstrncpy(&q, " ", limit-(q-buf));
9391 		else{
9392 		    got_one = 1;
9393 		    sstrncpy(&q, " {", limit-(q-buf));
9394 		}
9395 
9396 		sstrncpy(&q, cs->name, limit-(q-buf));
9397 	    }
9398 	}
9399 
9400 	if(got_one)
9401 	  sstrncpy(&q, "}", limit-(q-buf));
9402 
9403 	/* fold this line so that it can all be seen on the screen */
9404 	folded = fold(buf, width, width, "", "    ", FLD_NONE);
9405 	if(folded){
9406 	    t = folded;
9407 	    while(t && *t && (q = strindex(t, '\n')) != NULL){
9408 		*q = '\0';
9409 
9410 		ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9411 		memset(ls, 0, sizeof(*ls));
9412 		if(t == folded)
9413 		  ls->item = cpystr(s->name);
9414 		else
9415 		  ls->flags = SFL_NOSELECT;
9416 
9417 		ls->display_item = cpystr(t);
9418 
9419 		t = q+1;
9420 
9421 		if(p){
9422 		    p->next = ls;
9423 		    p = p->next;
9424 		}
9425 		else{
9426 		    /* add a heading */
9427 		    listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9428 		    memset(listhead, 0, sizeof(*listhead));
9429 		    listhead->flags = SFL_NOSELECT;
9430 		    listhead->display_item =
9431 	   cpystr(_("Scripts representing groups of related character sets"));
9432 		    listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9433 		    memset(listhead->next, 0, sizeof(*listhead));
9434 		    listhead->next->flags = SFL_NOSELECT;
9435 		    listhead->next->display_item =
9436 	   cpystr(repeat_char(width, '-'));
9437 
9438 		    listhead->next->next = ls;
9439 		    p = ls;
9440 		}
9441 	    }
9442 
9443 	    fs_give((void **) &folded);
9444 	}
9445     }
9446 
9447     ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9448     memset(ls, 0, sizeof(*ls));
9449     ls->flags = SFL_NOSELECT;
9450     if(p){
9451 	p->next = ls;
9452 	p = p->next;
9453     }
9454     else
9455       listhead = p = ls;
9456 
9457     ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9458     memset(ls, 0, sizeof(*ls));
9459     ls->flags = SFL_NOSELECT;
9460     ls->display_item =
9461                cpystr(_("Individual character sets, may be mixed with scripts"));
9462     p->next = ls;
9463     p = p->next;
9464 
9465     ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9466     memset(ls, 0, sizeof(*ls));
9467     ls->flags = SFL_NOSELECT;
9468     ls->display_item =
9469 	       cpystr(repeat_char(width, '-'));
9470     p->next = ls;
9471     p = p->next;
9472 
9473     /* then comes a list of individual character sets */
9474     for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9475 	ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9476 	memset(ls, 0, sizeof(*ls));
9477 	ls->item = cpystr(cs->name);
9478 
9479 	if(p){
9480 	    p->next = ls;
9481 	    p = p->next;
9482 	}
9483 	else
9484 	  listhead = p = ls;
9485     }
9486 
9487     if(!listhead)
9488       return(ret);
9489 
9490     /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9491        Print something1 using something2.
9492        "character sets" is something1 */
9493     if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9494 				_("SELECT CHARACTER SETS"), _("character sets"),
9495 				h_select_multcharsets_screen,
9496 			        _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9497 	for(cnt = 0, p = listhead; p; p = p->next)
9498 	  if(p->selected)
9499 	    cnt++;
9500 
9501 	ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9502 	memset(ret, 0, (cnt+1) * sizeof(*ret));
9503 	for(i = 0, p = listhead; p; p = p->next)
9504 	  if(p->selected)
9505 	    ret[i++] = cpystr(p->item ? p->item : "");
9506     }
9507 
9508     free_list_sel(&listhead);
9509 
9510     return(ret);
9511 }
9512 
9513 /* Report quota summary resources in an IMAP server */
9514 
9515 void
cmd_quota(struct pine * state)9516 cmd_quota (struct pine *state)
9517 {
9518    QUOTALIST *imapquota;
9519    NETMBX mb;
9520    STORE_S *store;
9521    SCROLL_S  sargs;
9522 
9523    if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9524       q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9525       return;
9526    }
9527 
9528    if (state->mail_stream
9529       && !sp_dead_stream(state->mail_stream)
9530       && state->mail_stream->mailbox
9531       && *state->mail_stream->mailbox
9532       && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9533       imap_getquotaroot(state->mail_stream, mb.mailbox);
9534 
9535    if(!state->quota)  /* failed ? */
9536       return;         /* go back... */
9537 
9538    if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9539        q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9540        return;
9541    }
9542 
9543    so_puts(store, "Quota Report for ");
9544    so_puts(store, state->mail_stream->original_mailbox);
9545    so_puts(store, "\n\n");
9546 
9547    for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9548 
9549         so_puts(store, _("Resource : "));
9550         so_puts(store, imapquota->name);
9551 	so_writec('\n', store);
9552 
9553 	so_puts(store, _("Usage    : "));
9554 	so_puts(store, long2string(imapquota->usage));
9555 	if(!strucmp(imapquota->name,"STORAGE"))
9556 	  so_puts(store, " KiB ");
9557 	if(!strucmp(imapquota->name,"MESSAGE")){
9558 	  so_puts(store, _(" message"));
9559 	  if(imapquota->usage != 1)
9560 	    so_puts(store, _("s "));	/* plural */
9561 	  else
9562 	    so_puts(store, _(" "));
9563 	}
9564 	so_writec('(', store);
9565 	so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9566 	so_puts(store, "%)\n");
9567 
9568 	so_puts(store, _("Limit    : "));
9569 	so_puts(store, long2string(imapquota->limit));
9570 	if(!strucmp(imapquota->name,"STORAGE"))
9571 	  so_puts(store, " KiB\n\n");
9572 	if(!strucmp(imapquota->name,"MESSAGE")){
9573 	  so_puts(store, _(" message"));
9574 	  if(imapquota->usage != 1)
9575 	    so_puts(store, _("s\n\n"));	/* plural */
9576 	  else
9577 	    so_puts(store, _("\n\n"));
9578 	}
9579    }
9580 
9581    memset(&sargs, 0, sizeof(SCROLL_S));
9582    sargs.text.text   = so_text(store);
9583    sargs.text.src    = CharStar;
9584    sargs.text.desc   = _("Quota Resources Summary");
9585    sargs.bar.title   = _("QUOTA SUMMARY");
9586    sargs.proc.tool   = NULL;
9587    sargs.help.text   = h_quota_command;
9588    sargs.help.title  = NULL;
9589    sargs.keys.menu   = NULL;
9590    setbitmap(sargs.keys.bitmap);
9591 
9592    scrolltool(&sargs);
9593    so_give(&store);
9594 
9595    if (state->quota)
9596      mail_free_quotalist(&(state->quota));
9597 }
9598 
9599 /*----------------------------------------------------------------------
9600    Prompt the user for the type of sort he desires
9601 
9602 Args: state -- pine state pointer
9603       q1 -- Line to prompt on
9604 
9605       Returns 0 if it was cancelled, 1 otherwise.
9606   ----*/
9607 int
select_sort(struct pine * state,int ql,SortOrder * sort,int * rev)9608 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9609 {
9610     char      prompt[200], tmp[3], *p;
9611     int       s, i;
9612     int       deefault = 'a', retval = 1;
9613     HelpType  help;
9614     ESCKEY_S  sorts[14];
9615 
9616 #ifdef _WINDOWS
9617     DLG_SORTPARAM	sortsel;
9618 
9619     if (mswin_usedialog ()) {
9620 
9621 	sortsel.reverse = mn_get_revsort (state->msgmap);
9622 	sortsel.cursort = mn_get_sort (state->msgmap);
9623 	/* assumption here that HelpType is char **  */
9624 	sortsel.helptext = h_select_sort;
9625 	sortsel.rval = 0;
9626 
9627 	if ((retval = os_sortdialog (&sortsel))) {
9628 	    *sort = sortsel.cursort;
9629 	    *rev  = sortsel.reverse;
9630         }
9631 
9632 	return (retval);
9633     }
9634 #endif
9635 
9636     /*----- String together the prompt ------*/
9637     tmp[1] = '\0';
9638     if(F_ON(F_USE_FK,ps_global))
9639       strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9640     else
9641       strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9642 	      sizeof(prompt));
9643 
9644     for(i = 0; state->sort_types[i] != EndofList; i++) {
9645 	sorts[i].rval	   = i;
9646 	p = sorts[i].label = sort_name(state->sort_types[i]);
9647 	while(*(p+1) && islower((unsigned char)*p))
9648 	  p++;
9649 
9650 	sorts[i].ch   = tolower((unsigned char)(tmp[0] = *p));
9651 	sorts[i].name = cpystr(tmp);
9652 
9653         if(mn_get_sort(state->msgmap) == state->sort_types[i])
9654 	  deefault = sorts[i].rval;
9655     }
9656 
9657     sorts[i].ch     = 'r';
9658     sorts[i].rval   = 'r';
9659     sorts[i].name   = cpystr("R");
9660     if(F_ON(F_USE_FK,ps_global))
9661       sorts[i].label  = N_("Reverse");
9662     else
9663       sorts[i].label  = "";
9664 
9665     sorts[++i].ch   = -1;
9666     help = h_select_sort;
9667 
9668     if((F_ON(F_USE_FK,ps_global)
9669         && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9670 				      help,RB_NORM)) != 'x'))
9671        ||
9672        (F_OFF(F_USE_FK,ps_global)
9673         && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9674 			       help,RB_NORM)) != 'x'))){
9675 	state->mangled_body = 1;		/* signal screen's changed */
9676 	if(s == 'r')
9677 	  *rev = !mn_get_revsort(state->msgmap);
9678 	else
9679 	  *sort = state->sort_types[s];
9680 
9681 	if(F_ON(F_SHOW_SORT, ps_global))
9682 	  ps_global->mangled_header = 1;
9683     }
9684     else{
9685 	retval = 0;
9686 	cmd_cancelled("Sort");
9687     }
9688 
9689     while(--i >= 0)
9690       fs_give((void **)&sorts[i].name);
9691 
9692     blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9693     return(retval);
9694 }
9695 
9696 
9697 /*---------------------------------------------------------------------
9698   Build list of folders in the given context for user selection
9699 
9700   Args: c -- pointer to pointer to folder's context context
9701 	f -- folder prefix to display
9702 	sublist -- whether or not to use 'f's contents as prefix
9703 	lister -- function used to do the actual display
9704 
9705   Returns: malloc'd string containing sequence, else NULL if
9706 	   no messages in msgmap with local "selected" flag.
9707   ----*/
9708 int
display_folder_list(CONTEXT_S ** c,char * f,int sublist,int (* lister)(struct pine *,CONTEXT_S **,char *,int))9709 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9710 {
9711     int	       rc;
9712     CONTEXT_S *tc;
9713     void (*redraw)(void) = ps_global->redrawer;
9714 
9715     push_titlebar_state();
9716     tc = *c;
9717     if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9718       *c = tc;
9719 
9720     ClearScreen();
9721     pop_titlebar_state();
9722     redraw_titlebar();
9723     if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9724       (*ps_global->redrawer)();
9725 
9726     if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9727       return(1);
9728 
9729     return(0);
9730 }
9731 
9732 
9733 /*
9734  * Allow user to choose a single item from a list of strings.
9735  *
9736  * Args    list -- Array of strings to choose from, NULL terminated.
9737  *     displist -- Array of strings to display instead of displaying list.
9738  *                   Indices correspond to the list array. Display the displist
9739  *                   but return the item from list if displist non-NULL.
9740  *        title -- For conf_scroll_screen
9741  *        pdesc -- For conf_scroll_screen
9742  *         help -- For conf_scroll_screen
9743  *       htitle -- For conf_scroll_screen
9744  *
9745  * Returns an allocated copy of the chosen item or NULL.
9746  */
9747 char *
choose_item_from_list(char ** list,char ** displist,char * title,char * pdesc,HelpType help,char * htitle,char * cursor_location)9748 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9749 		      char *htitle, char *cursor_location)
9750 {
9751     LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9752     char      **t, **dl;
9753     char       *ret = NULL, *choice = NULL;
9754 
9755     /* build the LIST_SEL_S list */
9756     p = listhead = NULL;
9757     for(t = list, dl = displist; *t; t++, dl++){
9758 	ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9759 	memset(ls, 0, sizeof(*ls));
9760 	ls->item = cpystr(*t);
9761 	if(displist)
9762 	  ls->display_item = cpystr(*dl);
9763 
9764 	if(cursor_location && (cursor_location == (*t)))
9765 	  starting_val = ls;
9766 
9767 	if(p){
9768 	    p->next = ls;
9769 	    p = p->next;
9770 	}
9771 	else
9772 	  listhead = p = ls;
9773     }
9774 
9775     if(!listhead)
9776       return(ret);
9777 
9778     if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9779 				help, htitle, starting_val))
9780       for(p = listhead; !choice && p; p = p->next)
9781 	if(p->selected)
9782 	  choice = p->item;
9783 
9784     if(choice)
9785       ret = cpystr(choice);
9786 
9787     free_list_sel(&listhead);
9788 
9789     return(ret);
9790 }
9791 
9792 
9793 void
free_list_sel(LIST_SEL_S ** lsel)9794 free_list_sel(LIST_SEL_S **lsel)
9795 {
9796     if(lsel && *lsel){
9797 	free_list_sel(&(*lsel)->next);
9798 	if((*lsel)->item)
9799 	  fs_give((void **) &(*lsel)->item);
9800 
9801 	if((*lsel)->display_item)
9802 	  fs_give((void **) &(*lsel)->display_item);
9803 
9804 	fs_give((void **) lsel);
9805     }
9806 }
9807 
9808 
9809 /*
9810  * file_lister - call pico library's file lister
9811  */
9812 int
file_lister(char * title,char * path,size_t pathlen,char * file,size_t filelen,int newmail,int flags)9813 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9814 {
9815     PICO   pbf;
9816     int	   rv;
9817     void (*redraw)(void) = ps_global->redrawer;
9818 
9819     standard_picobuf_setup(&pbf);
9820     push_titlebar_state();
9821     if(!newmail)
9822       pbf.newmail = NULL;
9823 
9824 /* BUG: what about help command and text? */
9825     pbf.pine_anchor   = title;
9826 
9827     rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9828     standard_picobuf_teardown(&pbf);
9829     fix_windsize(ps_global);
9830     init_signals();		/* has it's own signal stuff */
9831 
9832     /* Restore display's titlebar and body */
9833     pop_titlebar_state();
9834     redraw_titlebar();
9835     if((ps_global->redrawer = redraw) != NULL)
9836       (*ps_global->redrawer)();
9837 
9838     return(rv);
9839 }
9840 
9841 
9842 /*----------------------------------------------------------------------
9843     Print current folder index
9844 
9845   ---*/
9846 int
print_index(struct pine * state,MSGNO_S * msgmap,int agg)9847 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9848 {
9849     long     i;
9850     ICE_S   *ice;
9851     char     buf[MAX_SCREEN_COLS+1];
9852 
9853     for(i = 1L; i <= mn_get_total(msgmap); i++){
9854 	if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9855 	  continue;
9856 
9857 	if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9858 	  continue;
9859 
9860 	ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9861 
9862 	if(ice){
9863 	  /*
9864 	   * I don't understand why we'd want to mark the current message
9865 	   * instead of printing out the first character of the status
9866 	   * so I'm taking it out and including the first character of the
9867 	   * line instead. Hubert 2006-02-09
9868 	   *
9869 	  if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9870 	    return(0);
9871 	   */
9872 
9873 	  if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9874 				        print_char)
9875 	     || !gf_puts(NEWLINE, print_char))
9876 	    return(0);
9877 	}
9878     }
9879 
9880     return(1);
9881 }
9882 
9883 #ifdef	_WINDOWS
9884 
9885 /*
9886  * windows callback to get/set header mode state
9887  */
9888 int
header_mode_callback(set,args)9889 header_mode_callback(set, args)
9890     int  set;
9891     long args;
9892 {
9893     return(ps_global->full_header);
9894 }
9895 
9896 
9897 /*
9898  * windows callback to get/set zoom mode state
9899  */
9900 int
zoom_mode_callback(set,args)9901 zoom_mode_callback(set, args)
9902     int  set;
9903     long args;
9904 {
9905     return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9906 }
9907 
9908 
9909 /*
9910  * windows callback to get/set zoom mode state
9911  */
9912 int
any_selected_callback(set,args)9913 any_selected_callback(set, args)
9914     int  set;
9915     long args;
9916 {
9917     return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9918 }
9919 
9920 
9921 /*
9922  *
9923  */
9924 int
flag_callback(set,flags)9925 flag_callback(set, flags)
9926     int  set;
9927     long flags;
9928 {
9929     MESSAGECACHE *mc;
9930     int		  newflags = 0;
9931     long	  msgno;
9932     int		  permflag = 0;
9933 
9934     switch (set) {
9935       case 1:			/* Important */
9936         permflag = ps_global->mail_stream->perm_flagged;
9937 	break;
9938 
9939       case 2:			/* New */
9940         permflag = ps_global->mail_stream->perm_seen;
9941 	break;
9942 
9943       case 3:			/* Answered */
9944         permflag = ps_global->mail_stream->perm_answered;
9945 	break;
9946 
9947       case 4:			/* Deleted */
9948         permflag = ps_global->mail_stream->perm_deleted;
9949 	break;
9950 
9951     }
9952 
9953     if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9954 	 && can_set_flag(ps_global, "flag", permflag)))
9955       return(0);
9956 
9957     if(sp_io_error_on_stream(ps_global->mail_stream)){
9958 	sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9959 	pine_mail_check(ps_global->mail_stream);	/* forces write */
9960 	return(0);
9961     }
9962 
9963     msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9964     if(msgno > 0L && ps_global->mail_stream
9965        && msgno <= ps_global->mail_stream->nmsgs
9966        && (mc = mail_elt(ps_global->mail_stream, msgno))
9967        && mc->valid){
9968 	/*
9969 	 * NOTE: code below is *VERY* sensitive to the order of
9970 	 * the messages defined in resource.h for flag handling.
9971 	 * Don't change it unless you know what you're doing.
9972 	 */
9973 	if(set){
9974 	    char *flagstr;
9975 	    long  mflag;
9976 
9977 	    switch(set){
9978 	      case 1 :			/* Important */
9979 		flagstr = "\\FLAGGED";
9980 		mflag   = (mc->flagged) ? 0L : ST_SET;
9981 		break;
9982 
9983 	      case 2 :			/* New */
9984 		flagstr = "\\SEEN";
9985 		mflag   = (mc->seen) ? 0L : ST_SET;
9986 		break;
9987 
9988 	      case 3 :			/* Answered */
9989 		flagstr = "\\ANSWERED";
9990 		mflag   = (mc->answered) ? 0L : ST_SET;
9991 		break;
9992 
9993 	      case 4 :		/* Deleted */
9994 		flagstr = "\\DELETED";
9995 		mflag   = (mc->deleted) ? 0L : ST_SET;
9996 		break;
9997 
9998 	      default :			/* bogus */
9999 		return(0);
10000 	    }
10001 
10002 	    mail_flag(ps_global->mail_stream, long2string(msgno),
10003 		      flagstr, mflag);
10004 
10005 	    if(ps_global->redrawer)
10006 	      (*ps_global->redrawer)();
10007 	}
10008 	else{
10009 	    /* Important */
10010 	    if(mc->flagged)
10011 	      newflags |= 0x0001;
10012 
10013 	    /* New */
10014 	    if(!mc->seen)
10015 	      newflags |= 0x0002;
10016 
10017 	    /* Answered */
10018 	    if(mc->answered)
10019 	      newflags |= 0x0004;
10020 
10021 	    /* Deleted */
10022 	    if(mc->deleted)
10023 	      newflags |= 0x0008;
10024 	}
10025     }
10026 
10027     return(newflags);
10028 }
10029 
10030 
10031 
10032 /*
10033  * BUG:  Should teach this about keywords
10034  */
10035 MPopup *
flag_submenu(mc)10036 flag_submenu(mc)
10037     MESSAGECACHE *mc;
10038 {
10039     static MPopup flag_submenu[] = {
10040 	{tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10041 	{tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10042 	{tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10043 	{tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10044 	{tTail}
10045     };
10046 
10047     /* Important */
10048     flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10049 
10050     /* New */
10051     flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10052 
10053     /* Answered */
10054     flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10055 
10056     /* Deleted */
10057     flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10058 
10059     return(flag_submenu);
10060 }
10061 
10062 #endif	/* _WINDOWS */
10063