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, ¬realinbox, &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