#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2013-2021 Eduardo Chappa * Copyright 2006-2009 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ /*====================================================================== mailcmd.c The meat and pototoes of mail processing here: - initial command processing and dispatch - save message - capture address off incoming mail - jump to specific numbered message - open (broach) a new folder - search message headers (where is) command ====*/ #include "headers.h" #include "mailcmd.h" #include "status.h" #include "mailview.h" #include "flagmaint.h" #include "listsel.h" #include "keymenu.h" #include "alpine.h" #include "mailpart.h" #include "mailindx.h" #include "folder.h" #include "reply.h" #include "help.h" #include "titlebar.h" #include "signal.h" #include "radio.h" #include "pipe.h" #include "send.h" #include "takeaddr.h" #include "roleconf.h" #include "smime.h" #include "../pith/state.h" #include "../pith/msgno.h" #include "../pith/store.h" #include "../pith/thread.h" #include "../pith/flag.h" #include "../pith/sort.h" #include "../pith/maillist.h" #include "../pith/save.h" #include "../pith/pipe.h" #include "../pith/news.h" #include "../pith/util.h" #include "../pith/sequence.h" #include "../pith/keyword.h" #include "../pith/stream.h" #include "../pith/mailcmd.h" #include "../pith/hist.h" #include "../pith/list.h" #include "../pith/icache.h" #include "../pith/busy.h" #include "../pith/mimedesc.h" #include "../pith/pattern.h" #include "../pith/tempfile.h" #include "../pith/search.h" #include "../pith/margin.h" #include "../pith/rules.h" #ifdef _WINDOWS #include "../pico/osdep/mswin.h" #endif /* * Internal Prototypes */ int cmd_flag(struct pine *, MSGNO_S *, int); int cmd_flag_prompt(struct pine *, struct flag_screen *, int); void free_flag_table(struct flag_table **); int cmd_reply(struct pine *, MSGNO_S *, int, ACTION_S *); int cmd_forward(struct pine *, MSGNO_S *, int, ACTION_S *); int cmd_bounce(struct pine *, MSGNO_S *, int, ACTION_S *); int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere); void role_compose(struct pine *); int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int); int cmd_export(struct pine *, MSGNO_S *, int, int); char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere); char *cmd_delete_view(struct pine *, MSGNO_S *); char *cmd_delete_index(struct pine *, MSGNO_S *); long get_level(int, UCS, SCROLL_S *); long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t); int update_folder_spec(char *, size_t, char *); int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere); int cmd_pipe(struct pine *, MSGNO_S *, int); STORE_S *list_mgmt_text(RFC2369_S *, long); void list_mgmt_screen(STORE_S *); int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere); int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **); int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **); int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); int select_by_gm_content(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); int select_by_size(MAILSTREAM *, SEARCHSET **); SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *); int select_by_status(MAILSTREAM *, SEARCHSET **); int select_by_rule(MAILSTREAM *, SEARCHSET **); int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **); char *choose_a_rule(int); int select_by_keyword(MAILSTREAM *, SEARCHSET **); char *choose_a_keyword(void); int select_sort(struct pine *, int, SortOrder *, int *); int print_index(struct pine *, MSGNO_S *, int); /* * List of Select options used by apply_* functions... */ static char *sel_pmt1 = N_("ALTER message selection : "); ESCKEY_S sel_opts1[] = { /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means we will add more messages to the selection, Narrow selection means we will remove some selections (like a logical AND instead of logical OR), and Flip Selected means that all the messages that are currently selected become unselected, and all the unselected messages become selected. */ {'a', 'a', "A", N_("unselect All")}, {'c', 'c', "C", NULL}, {'b', 'b', "B", N_("Broaden selctn")}, {'n', 'n', "N", N_("Narrow selctn")}, {'f', 'f', "F", N_("Flip selected")}, {'r', 'r', "R", N_("Replace selctn")}, {-1, 0, NULL, NULL} }; #define SEL_OPTS_THREAD 9 /* index number of "tHread" */ #define SEL_OPTS_THREAD_CH 'h' #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */ #define SEL_OPTS_XGMEXT_CH 'g' char *sel_pmt2 = "SELECT criteria : "; static ESCKEY_S sel_opts2[] = { /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur means select the currently highlighted message; select by Number is by message number; Status is by status of the message, for example the message might be New or it might be Unseen or marked Important; Size has the Z upper case because it is a Z command; Keyword is an alpine keyword that has been set by the user; and Rule is an alpine rule */ {'a', 'a', "A", N_("select All")}, {'c', 'c', "C", N_("select Cur")}, {'n', 'n', "N", N_("Number")}, {'d', 'd', "D", N_("Date")}, {'t', 't', "T", N_("Text")}, {'s', 's', "S", N_("Status")}, {'z', 'z', "Z", N_("siZe")}, {'k', 'k', "K", N_("Keyword")}, {'r', 'r', "R", N_("Rule")}, {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, {-1, 0, NULL, NULL} }; static ESCKEY_S sel_opts3[] = { /* TRANSLATORS: these are operations we can do on a set of selected messages. Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into the address book; Save means to save the messages into another alpine folder; Export means to copy the messages to a file outside of alpine, external to alpine's world. */ {'d', 'd', "D", N_("Del")}, {'u', 'u', "U", N_("Undel")}, {'r', 'r', "R", N_("Reply")}, {'f', 'f', "F", N_("Forward")}, {'%', '%', "%", N_("Print")}, {'t', 't', "T", N_("TakeAddr")}, {'s', 's', "S", N_("Save")}, {'e', 'e', "E", N_("Export")}, { -1, 0, NULL, NULL}, { -1, 0, NULL, NULL}, { -1, 0, NULL, NULL}, { -1, 0, NULL, NULL}, { -1, 0, NULL, NULL}, { -1, 0, NULL, NULL}, { -1, 0, NULL, NULL}, {-1, 0, NULL, NULL} }; static ESCKEY_S sel_opts4[] = { {'a', 'a', "A", N_("select All")}, /* TRANSLATORS: select currently highlighted message Thread */ {'c', 'c', "C", N_("select Curthrd")}, {'n', 'n', "N", N_("Number")}, {'d', 'd', "D", N_("Date")}, {'t', 't', "T", N_("Text")}, {'s', 's', "S", N_("Status")}, {'z', 'z', "Z", N_("siZe")}, {'k', 'k', "K", N_("Keyword")}, {'r', 'r', "R", N_("Rule")}, {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, {-1, 0, NULL, NULL} }; static ESCKEY_S sel_opts5[] = { /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur means select the currently highlighted message; select by Number is by message number; Status is by status of the message, for example the message might be New or it might be Unseen or marked Important; Size has the Z upper case because it is a Z command; Keyword is an alpine keyword that has been set by the user; and Rule is an alpine rule */ {'a', 'a', "A", N_("select All")}, {'c', 'c', "C", N_("select Cur")}, {'n', 'n', "N", N_("Number")}, {'d', 'd', "D", N_("Date")}, {'t', 't', "T", N_("Text")}, {'s', 's', "S", N_("Status")}, {'z', 'z', "Z", N_("siZe")}, {'k', 'k', "K", N_("Keyword")}, {'r', 'r', "R", N_("Rule")}, {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL} }; static ESCKEY_S sel_opts6[] = { {'a', 'a', "A", N_("select All")}, /* TRANSLATORS: select currently highlighted message Thread */ {'c', 'c', "C", N_("select Curthrd")}, {'n', 'n', "N", N_("Number")}, {'d', 'd', "D", N_("Date")}, {'t', 't', "T", N_("Text")}, {'s', 's', "S", N_("Status")}, {'z', 'z', "Z", N_("siZe")}, {'k', 'k', "K", N_("Keyword")}, {'r', 'r', "R", N_("Rule")}, {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL} }; static char *sel_flag = N_("Select New, Deleted, Answered, Forwarded, or Important messages ? "); static char *sel_flag_not = N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? "); static ESCKEY_S sel_flag_opt[] = { /* TRANSLATORS: When selecting messages by message Status these are the different types of Status you can select on. Is the message New, Recent, and so on. Not means flip the meaning of the selection to the opposite thing, so message is not New or not Important. */ {'n', 'n', "N", N_("New")}, {'*', '*', "*", N_("Important")}, {'d', 'd', "D", N_("Deleted")}, {'a', 'a', "A", N_("Answered")}, {'f', 'f', "F", N_("Forwarded")}, {-2, 0, NULL, NULL}, {'!', '!', "!", N_("Not")}, {-2, 0, NULL, NULL}, {'r', 'r', "R", N_("Recent")}, {'u', 'u', "U", N_("Unseen")}, {-1, 0, NULL, NULL} }; static ESCKEY_S sel_date_opt[] = { {0, 0, NULL, NULL}, /* TRANSLATORS: options when selecting messages by Date */ {ctrl('P'), 12, "^P", N_("Prev Day")}, {ctrl('N'), 13, "^N", N_("Next Day")}, {ctrl('X'), 11, "^X", N_("Cur Msg")}, {ctrl('W'), 14, "^W", N_("Toggle When")}, {KEY_UP, 12, "", ""}, {KEY_DOWN, 13, "", ""}, {-1, 0, NULL, NULL} }; static char *sel_x_gm_ext = N_("Search: "); static char *sel_text = N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? "); static char *sel_text_not = N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? "); static ESCKEY_S sel_text_opt[] = { /* TRANSLATORS: Select messages based on the text contained in the From line, or the Subject line, and so on. */ {'f', 'f', "F", N_("From")}, {'s', 's', "S", N_("Subject")}, {'t', 't', "T", N_("To")}, {'a', 'a', "A", N_("All Text")}, {'c', 'c', "C", N_("Cc")}, {'!', '!', "!", N_("Not")}, {'r', 'r', "R", N_("Recipient")}, {'p', 'p', "P", N_("Participant")}, {'b', 'b', "B", N_("Body")}, {'h', 'h', "H", N_("Header")}, {-1, 0, NULL, NULL} }; static ESCKEY_S choose_action[] = { {'c', 'c', "C", N_("Compose")}, {'r', 'r', "R", N_("Reply")}, {'f', 'f', "F", N_("Forward")}, {'b', 'b', "B", N_("Bounce")}, {-1, 0, NULL, NULL} }; static char *select_num = N_("Enter comma-delimited list of numbers (dash between ranges): "); static char *select_size_larger_msg = N_("Select messages with size larger than: "); static char *select_size_smaller_msg = N_("Select messages with size smaller than: "); static char *sel_size_larger = N_("Larger"); static char *sel_size_smaller = N_("Smaller"); static ESCKEY_S sel_size_opt[] = { {0, 0, NULL, NULL}, {ctrl('W'), 14, "^W", NULL}, {-1, 0, NULL, NULL} }; static ESCKEY_S sel_key_opt[] = { {0, 0, NULL, NULL}, {ctrl('T'), 14, "^T", N_("To List")}, {0, 0, NULL, NULL}, {'!', '!', "!", N_("Not")}, {-1, 0, NULL, NULL} }; static ESCKEY_S flag_text_opt[] = { /* TRANSLATORS: these are types of flags (markers) that the user can set. For example, they can flag the message as an important message. */ {'n', 'n', "N", N_("New")}, {'*', '*', "*", N_("Important")}, {'d', 'd', "D", N_("Deleted")}, {'a', 'a', "A", N_("Answered")}, {'f', 'f', "F", N_("Forwarded")}, {'!', '!', "!", N_("Not")}, {ctrl('T'), 10, "^T", N_("To Flag Details")}, {-1, 0, NULL, NULL} }; int alpine_smime_confirm_save(char *email) { char prompt[128]; snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"), email ? email : _("missing address")); return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y'; } int alpine_get_password(char *prompt, char *pass, size_t len) { int flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD; flags |= OE_DISALLOW_HELP; pass[0] = '\0'; return optionally_enter(pass, -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3), 0, len, prompt, NULL, NO_HELP, &flags); } int smime_import_certificate(char *filename, char *full_filename, char *what, size_t len) { int r = 1; static HISTORY_S *history = NULL; static ESCKEY_S eopts[] = { {ctrl('T'), 10, "^T", N_("To Files")}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}}; if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ eopts[r].ch = ctrl('I'); eopts[r].rval = 11; eopts[r].name = "TAB"; eopts[r].label = N_("Complete"); } eopts[++r].ch = -1; filename[0] = '\0'; full_filename[0] = '\0'; r = get_export_filename(ps_global, filename, NULL, full_filename, len, what, "IMPORT", eopts, NULL, -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history); return r; } /*---------------------------------------------------------------------- The giant switch on the commands for index and viewing Input: command -- The command char/code in_index -- flag indicating command is from index orig_command -- The original command typed before pre-processing Output: force_mailchk -- Set to tell caller to force call to new_mail(). Result: Manifold Returns 1 if the message number or attachment to show changed ---*/ int process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int command, CmdWhere in_index, int *force_mailchk) { int question_line, a_changed, flags = 0, ret, j; int notrealinbox; long new_msgno, del_count, old_msgno, i; long start; char *newfolder, prompt[MAX_SCREEN_COLS+1]; CONTEXT_S *tc; MESSAGECACHE *mc; #if defined(DOS) && !defined(_WINDOWS) extern long coreleft(); #endif dprint((4, "\n - process_cmd(cmd=%d) -\n", command)); question_line = -FOOTER_ROWS(state); state->mangled_screen = 0; state->mangled_footer = 0; state->mangled_header = 0; state->next_screen = SCREEN_FUN_NULL; old_msgno = mn_get_cur(msgmap); a_changed = FALSE; *force_mailchk = 0; switch (command) { /*------------- Help --------*/ case MC_HELP : /* * We're not using the h_mail_view portion of this right now because * that call is being handled in scrolltool() before it gets * here. Leave it in case we change how it works. */ helper((in_index == MsgIndx) ? h_mail_index : (in_index == View) ? h_mail_view : h_mail_thread_index, (in_index == MsgIndx) ? _("HELP FOR MESSAGE INDEX") : (in_index == View) ? _("HELP FOR MESSAGE TEXT") : _("HELP FOR THREAD INDEX"), HLPD_NONE); dprint((4,"MAIL_CMD: did help command\n")); state->mangled_screen = 1; break; /*--------- Return to main menu ------------*/ case MC_MAIN : state->next_screen = main_menu_screen; dprint((2,"MAIL_CMD: going back to main menu\n")); break; /*------- View message text --------*/ case MC_VIEW_TEXT : view_text: if(any_messages(msgmap, NULL, "to View")){ state->next_screen = mail_view_screen; } break; /*------- View attachment --------*/ case MC_VIEW_ATCH : state->next_screen = attachment_screen; dprint((2,"MAIL_CMD: going to attachment screen\n")); break; /*---------- Previous message ----------*/ case MC_PREVITEM : if(any_messages(msgmap, NULL, NULL)){ if((i = mn_get_cur(msgmap)) > 1L){ mn_dec_cur(stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); if(i == mn_get_cur(msgmap)){ PINETHRD_S *thrd = NULL, *topthrd = NULL; if(THRD_INDX_ENABLED()){ mn_dec_cur(stream, msgmap, MH_ANYTHD); if(i == mn_get_cur(msgmap)) q_status_message1(SM_ORDER, 0, 2, _("Already on first %s in Zoomed Index"), THRD_INDX() ? _("thread") : _("message")); else{ if(in_index == View || F_ON(F_NEXT_THRD_WO_CONFIRM, state)) ret = 'y'; else ret = want_to(_("View previous thread"), 'y', 'x', NO_HELP, WT_NORM); if(ret == 'y'){ q_status_message(SM_ORDER, 0, 2, _("Viewing previous thread")); new_msgno = mn_get_cur(msgmap); mn_set_cur(msgmap, i); if(unview_thread(state, stream, msgmap)){ state->next_screen = mail_index_screen; state->view_skipped_index = 0; state->mangled_screen = 1; } mn_set_cur(msgmap, new_msgno); if(THRD_AUTO_VIEW() && in_index == View){ thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno)); if(count_lflags_in_thread(stream, thrd, msgmap, MN_NONE) == 1){ if(view_thread(state, stream, msgmap, 1)){ if(current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; state->view_skipped_index = 1; command = MC_VIEW_TEXT; goto view_text; } } } j = 0; if(THRD_AUTO_VIEW() && in_index != View){ thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno)); if(thrd && thrd->top) topthrd = fetch_thread(stream, thrd->top); if(topthrd) j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE); } if(!THRD_AUTO_VIEW() || in_index == View || j != 1){ if(view_thread(state, stream, msgmap, 1) && current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; } state->next_screen = SCREEN_FUN_NULL; } else mn_set_cur(msgmap, i); /* put it back */ } } else q_status_message1(SM_ORDER, 0, 2, _("Already on first %s in Zoomed Index"), THRD_INDX() ? _("thread") : _("message")); } } else{ time_t now; if(!IS_NEWS(stream) && ((now = time(0)) > state->last_nextitem_forcechk)){ *force_mailchk = 1; /* check at most once a second */ state->last_nextitem_forcechk = now; } q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"), THRD_INDX() ? _("thread") : _("message")); } } break; /*---------- Next Message ----------*/ case MC_NEXTITEM : if(mn_get_total(msgmap) > 0L && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){ mn_inc_cur(stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); if(i == mn_get_cur(msgmap)){ PINETHRD_S *thrd, *topthrd; if(THRD_INDX_ENABLED()){ if(!THRD_INDX()) mn_inc_cur(stream, msgmap, MH_ANYTHD); if(i == mn_get_cur(msgmap)){ if(any_lflagged(msgmap, MN_HIDE)) any_messages(NULL, "more", "in Zoomed Index"); else goto nfolder; } else{ if(in_index == View || F_ON(F_NEXT_THRD_WO_CONFIRM, state)) ret = 'y'; else ret = want_to(_("View next thread"), 'y', 'x', NO_HELP, WT_NORM); if(ret == 'y'){ q_status_message(SM_ORDER, 0, 2, _("Viewing next thread")); new_msgno = mn_get_cur(msgmap); mn_set_cur(msgmap, i); if(unview_thread(state, stream, msgmap)){ state->next_screen = mail_index_screen; state->view_skipped_index = 0; state->mangled_screen = 1; } mn_set_cur(msgmap, new_msgno); if(THRD_AUTO_VIEW() && in_index == View){ thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno)); if(count_lflags_in_thread(stream, thrd, msgmap, MN_NONE) == 1){ if(view_thread(state, stream, msgmap, 1)){ if(current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; state->view_skipped_index = 1; command = MC_VIEW_TEXT; goto view_text; } } } j = 0; if(THRD_AUTO_VIEW() && in_index != View){ thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno)); if(thrd && thrd->top) topthrd = fetch_thread(stream, thrd->top); else topthrd = NULL; if(topthrd) j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE); } if(!THRD_AUTO_VIEW() || in_index == View || j != 1){ if(view_thread(state, stream, msgmap, 1) && current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; } state->next_screen = SCREEN_FUN_NULL; } else mn_set_cur(msgmap, i); /* put it back */ } } else if(THREADING() && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i))) && thrd->next && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){ q_status_message(SM_ORDER, 0, 2, _("Expand collapsed thread to see more messages")); } else any_messages(NULL, "more", "in Zoomed Index"); } } else{ time_t now; nfolder: prompt[0] = '\0'; if(IS_NEWS(stream) || (state->context_current->use & CNTXT_INCMNG)){ char nextfolder[MAXPATH]; strncpy(nextfolder, state->cur_folder, sizeof(nextfolder)); nextfolder[sizeof(nextfolder)-1] = '\0'; if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder, state->context_current, NULL, NULL)) strncpy(prompt, _(". Press TAB for next folder."), sizeof(prompt)); else strncpy(prompt, _(". No more folders to TAB to."), sizeof(prompt)); prompt[sizeof(prompt)-1] = '\0'; } any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL, prompt[0] ? prompt : NULL); if(!IS_NEWS(stream) && ((now = time(0)) > state->last_nextitem_forcechk)){ *force_mailchk = 1; /* check at most once a second */ state->last_nextitem_forcechk = now; } } break; /*---------- Delete message ----------*/ case MC_DELETE : (void) cmd_delete(state, msgmap, MCMD_NONE, (in_index == View) ? cmd_delete_view : cmd_delete_index); break; /*---------- Undelete message ----------*/ case MC_UNDELETE : (void) cmd_undelete(state, msgmap, MCMD_NONE); update_titlebar_status(); break; /*---------- Reply to message ----------*/ case MC_REPLY : (void) cmd_reply(state, msgmap, MCMD_NONE, NULL); break; /*---------- Forward message ----------*/ case MC_FORWARD : (void) cmd_forward(state, msgmap, MCMD_NONE, NULL); break; /*---------- Quit pine ------------*/ case MC_QUIT : state->next_screen = quit_screen; dprint((1,"MAIL_CMD: quit\n")); break; /*---------- Compose message ----------*/ case MC_COMPOSE : state->prev_screen = (in_index == View) ? mail_view_screen : mail_index_screen; compose_screen(state); state->mangled_screen = 1; if (state->next_screen) a_changed = TRUE; break; /*---------- Alt Compose message ----------*/ case MC_ROLE : state->prev_screen = (in_index == View) ? mail_view_screen : mail_index_screen; role_compose(state); if(state->next_screen) a_changed = TRUE; break; /*--------- Folders menu ------------*/ case MC_FOLDERS : state->start_in_context = 1; /*--------- Top of Folders list menu ------------*/ case MC_COLLECTIONS : state->next_screen = folder_screen; dprint((2,"MAIL_CMD: going to folder/collection menu\n")); break; /*---------- Open specific new folder ----------*/ case MC_GOTO : tc = (state->context_last && !NEWS_TEST(state->context_current)) ? state->context_last : state->context_current; newfolder = broach_folder(question_line, 1, ¬realinbox, &tc); if(newfolder){ visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT); a_changed = TRUE; } break; /*------- Go to Index Screen ----------*/ case MC_INDEX : state->next_screen = mail_index_screen; break; /*------- Skip to next interesting message -----------*/ case MC_TAB : if(THRD_INDX()){ PINETHRD_S *thrd; /* * If we're in the thread index, start looking after this * thread. We don't want to match something in the current * thread. */ start = mn_get_cur(msgmap); thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); if(mn_get_revsort(msgmap)){ /* if reversed, top of thread is last one before next thread */ if(thrd && thrd->top) start = mn_raw2m(msgmap, thrd->top); } else{ /* last msg of thread is at the ends of the branches/nexts */ while(thrd){ start = mn_raw2m(msgmap, thrd->rawno); if(thrd->branch) thrd = fetch_thread(stream, thrd->branch); else if(thrd->next) thrd = fetch_thread(stream, thrd->next); else thrd = NULL; } } /* * Flags is 0 in this case because we want to not skip * messages inside of threads so that we can find threads * which have some unseen messages even though the top-level * of the thread is already seen. * If new_msgno ends up being a message which is not visible * because it isn't at the top-level, the current message # * will be adjusted below in adjust_cur. */ flags = 0; new_msgno = next_sorted_flagged((F_UNDEL | F_UNSEEN | ((F_ON(F_TAB_TO_NEW,state)) ? 0 : F_OR_FLAG)), stream, start, &flags); } else if(THREADING() && sp_viewing_a_thread(stream)){ PINETHRD_S *thrd, *topthrd = NULL; start = mn_get_cur(msgmap); /* * Things are especially complicated when we're viewing_a_thread * from the thread index. First we have to check within the * current thread for a new message. If none is found, then * we search in the next threads and offer to continue in * them. Then we offer to go to the next folder. */ flags = NSF_SKIP_CHID; new_msgno = next_sorted_flagged((F_UNDEL | F_UNSEEN | ((F_ON(F_TAB_TO_NEW,state)) ? 0 : F_OR_FLAG)), stream, start, &flags); /* * If we found a match then we are done, that is another message * in the current thread index. Otherwise, we have to look * further. */ if(!(flags & NSF_FLAG_MATCH)){ ret = 'n'; while(1){ flags = 0; new_msgno = next_sorted_flagged((F_UNDEL | F_UNSEEN | ((F_ON(F_TAB_TO_NEW, state)) ? 0 : F_OR_FLAG)), stream, start, &flags); /* * If we got a match, new_msgno is a message in * a different thread from the one we are viewing. */ if(flags & NSF_FLAG_MATCH){ thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno)); if(thrd && thrd->top) topthrd = fetch_thread(stream, thrd->top); if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){ static ESCKEY_S next_opt[] = { {'y', 'y', "Y", N_("Yes")}, {'n', 'n', "N", N_("No")}, {TAB, 'n', "Tab", N_("NextNew")}, {-1, 0, NULL, NULL} }; if(in_index) snprintf(prompt, sizeof(prompt), _("View thread number %s? "), topthrd ? comatose(topthrd->thrdno) : "?"); else snprintf(prompt, sizeof(prompt), _("View message in thread number %s? "), topthrd ? comatose(topthrd->thrdno) : "?"); prompt[sizeof(prompt)-1] = '\0'; ret = radio_buttons(prompt, -FOOTER_ROWS(state), next_opt, 'y', 'x', NO_HELP, RB_NORM); if(ret == 'x'){ cmd_cancelled(NULL); goto get_out; } } else ret = 'y'; if(ret == 'y'){ if(unview_thread(state, stream, msgmap)){ state->next_screen = mail_index_screen; state->view_skipped_index = 0; state->mangled_screen = 1; } mn_set_cur(msgmap, new_msgno); if(THRD_AUTO_VIEW()){ if(count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE) == 1){ if(view_thread(state, stream, msgmap, 1)){ if(current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; state->view_skipped_index = 1; command = MC_VIEW_TEXT; goto view_text; } } } if(view_thread(state, stream, msgmap, 1) && current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; state->next_screen = SCREEN_FUN_NULL; break; } else if(ret == 'n' && topthrd){ /* * skip to end of this thread and look starting * in the next thread. */ if(mn_get_revsort(msgmap)){ /* * if reversed, top of thread is last one * before next thread */ start = mn_raw2m(msgmap, topthrd->rawno); } else{ /* * last msg of thread is at the ends of * the branches/nexts */ thrd = topthrd; while(thrd){ start = mn_raw2m(msgmap, thrd->rawno); if(thrd->branch) thrd = fetch_thread(stream, thrd->branch); else if(thrd->next) thrd = fetch_thread(stream, thrd->next); else thrd = NULL; } } } else if(ret == 'n') break; } else break; } } } else{ start = mn_get_cur(msgmap); /* * If we are on a collapsed thread, start looking after the * collapsed part, unless we are viewing the message. */ if(THREADING() && in_index != View){ PINETHRD_S *thrd; long rawno; int collapsed; rawno = mn_m2raw(msgmap, start); thrd = fetch_thread(stream, rawno); collapsed = thrd && thrd->next && get_lflag(stream, NULL, rawno, MN_COLL); if(collapsed){ if(mn_get_revsort(msgmap)){ if(thrd && thrd->top) start = mn_raw2m(msgmap, thrd->top); } else{ while(thrd){ start = mn_raw2m(msgmap, thrd->rawno); if(thrd->branch) thrd = fetch_thread(stream, thrd->branch); else if(thrd->next) thrd = fetch_thread(stream, thrd->next); else thrd = NULL; } } } } new_msgno = next_sorted_flagged((F_UNDEL | F_UNSEEN | ((F_ON(F_TAB_TO_NEW,state)) ? 0 : F_OR_FLAG)), stream, start, &flags); } /* * If there weren't any unread messages left, OR there * aren't any messages at all, we may want to offer to * go on to the next folder... */ if(flags & NSF_FLAG_MATCH){ mn_set_cur(msgmap, new_msgno); if(in_index != View) adjust_cur_to_visible(stream, msgmap); } else{ int in_inbox = sp_flagged(stream, SP_INBOX); if(state->context_current && ((NEWS_TEST(state->context_current) && context_isambig(state->cur_folder)) || ((state->context_current->use & CNTXT_INCMNG) && (in_inbox || folder_index(state->cur_folder, state->context_current, FI_FOLDER) >= 0)))){ char nextfolder[MAXPATH]; MAILSTREAM *nextstream = NULL; long recent_cnt; int did_cancel = 0; strncpy(nextfolder, state->cur_folder, sizeof(nextfolder)); nextfolder[sizeof(nextfolder)-1] = '\0'; while(1){ if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder, state->context_current, &recent_cnt, F_ON(F_TAB_NO_CONFIRM,state) ? NULL : &did_cancel))){ if(!in_inbox){ static ESCKEY_S inbox_opt[] = { {'y', 'y', "Y", N_("Yes")}, {'n', 'n', "N", N_("No")}, {TAB, 'z', "Tab", N_("To Inbox")}, {-1, 0, NULL, NULL} }; if(F_ON(F_RET_INBOX_NO_CONFIRM,state)) ret = 'y'; else{ /* TRANSLATORS: this is a question, with some information followed by Return to INBOX? */ if(state->context_current->use&CNTXT_INCMNG) snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name); else snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name); ret = radio_buttons(prompt, -FOOTER_ROWS(state), inbox_opt, 'y', 'x', NO_HELP, RB_NORM); } /* * 'z' is a synonym for 'y'. It is not 'y' * so that it isn't displayed as a default * action with square-brackets around it * in the keymenu... */ if(ret == 'y' || ret == 'z'){ visit_folder(state, state->inbox_name, state->context_current, NULL, DB_INBOXWOCNTXT); a_changed = TRUE; } } else if (did_cancel) cmd_cancelled(NULL); else{ if(state->context_current->use&CNTXT_INCMNG) q_status_message(SM_ORDER, 0, 2, _("No more incoming folders")); else q_status_message(SM_ORDER, 0, 2, _("No more news groups")); } break; } { #define CNTLEN 80 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1]; int rbspace, avail, need, take_back; /* * View_next_ * Incoming_folder_ or news_group_ or folder_ or group_ * "foldername" * _(13 recent) or _(some recent) or nothing * ?_ */ front = "View next"; strncpy(type, (state->context_current->use & CNTXT_INCMNG) ? "Incoming folder" : "news group", sizeof(type)); type[sizeof(type)-1] = '\0'; snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20, recent_cnt ? long2string(recent_cnt) : "some", F_ON(F_TAB_USES_UNSEEN, ps_global) ? "unseen" : "recent"); cnt[sizeof(cnt)-1] = '\0'; /* * Space reserved for radio_buttons call. * If we make this 3 then radio_buttons won't mess * with the prompt. If we make it 2, then we get * one more character to use but radio_buttons will * cut off the last character of our prompt, which is * ok because it is a space. */ rbspace = 2; avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80; need = strlen(front)+1 + strlen(type)+1 + + strlen(nextfolder)+2 + strlen(cnt) + 2 + rbspace; if(avail < need){ take_back = strlen(type); strncpy(type, (state->context_current->use & CNTXT_INCMNG) ? "folder" : "group", sizeof(type)); take_back -= strlen(type); need -= take_back; if(avail < need){ need -= strlen(cnt); cnt[0] = '\0'; } } /* MAX_SCREEN_COLS+1 = sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ", (MAX_SCREEN_COLS+1)/8, front, (MAX_SCREEN_COLS+1)/8, type, (MAX_SCREEN_COLS+1)/2, short_str(nextfolder, fbuf, sizeof(fbuf), strlen(nextfolder) - ((need>avail) ? (need-avail) : 0), MidDots), (MAX_SCREEN_COLS+1)/8, cnt); prompt[sizeof(prompt)-1] = '\0'; } /* * When help gets added, this'll have to become * a loop like the rest... */ if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){ static ESCKEY_S next_opt[] = { {'y', 'y', "Y", N_("Yes")}, {'n', 'n', "N", N_("No")}, {TAB, 'n', "Tab", N_("NextNew")}, {-1, 0, NULL, NULL} }; ret = radio_buttons(prompt, -FOOTER_ROWS(state), next_opt, 'y', 'x', NO_HELP, RB_NORM); if(ret == 'x'){ cmd_cancelled(NULL); break; } } else ret = 'y'; if(ret == 'y'){ if(nextstream && sp_dead_stream(nextstream)) nextstream = NULL; visit_folder(state, nextfolder, state->context_current, nextstream, DB_FROMTAB); /* visit_folder takes care of nextstream */ nextstream = NULL; a_changed = TRUE; break; } } if(nextstream) pine_mail_close(nextstream); } else any_messages(NULL, (mn_get_total(msgmap) > 0L) ? IS_NEWS(stream) ? "more undeleted" : "more new" : NULL, NULL); } get_out: break; /*------- Zoom -----------*/ case MC_ZOOM : /* * Right now the way zoom is implemented is sort of silly. * There are two per-message flags where just one and a * global "zoom mode" flag to suppress messages from the index * should suffice. */ if(any_messages(msgmap, NULL, "to Zoom on")){ if(unzoom_index(state, stream, msgmap)){ dprint((4, "\n\n ---- Exiting ZOOM mode ----\n")); q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off")); } else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){ if(any_lflagged(msgmap, MN_HIDE)){ dprint((4,"\n\n ---- Entering ZOOM mode ----\n")); q_status_message4(SM_ORDER, 0, 2, _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"), THRD_INDX() ? "" : comatose(i), THRD_INDX() ? "" : " ", THRD_INDX() ? _("threads") : _("message"), THRD_INDX() ? "" : plural(i)); } else q_status_message(SM_ORDER, 0, 2, _("All messages selected, so not entering Index Zoom Mode")); } else any_messages(NULL, "selected", "to Zoom on"); } break; /*---------- print message on paper ----------*/ case MC_PRINTMSG : if(any_messages(msgmap, NULL, "to print")) (void) cmd_print(state, msgmap, MCMD_NONE, in_index); break; /*---------- Take Address ----------*/ case MC_TAKE : if(F_ON(F_ENABLE_ROLE_TAKE, state) || any_messages(msgmap, NULL, "to Take address from")) (void) cmd_take_addr(state, msgmap, MCMD_NONE); break; /*---------- Save Message ----------*/ case MC_SAVE : if(any_messages(msgmap, NULL, "to Save")) (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index); break; /*---------- Export message ----------*/ case MC_EXPORT : if(any_messages(msgmap, NULL, "to Export")){ (void) cmd_export(state, msgmap, question_line, MCMD_NONE); state->mangled_footer = 1; } break; /*---------- Expunge ----------*/ case MC_EXPUNGE : (void) cmd_expunge(state, stream, msgmap, MCMD_NONE); break; /*------- Unexclude -----------*/ case MC_UNEXCLUDE : if(!(IS_NEWS(stream) && stream->rdonly)){ q_status_message(SM_ORDER, 0, 3, _("Unexclude not available for mail folders")); } else if(any_lflagged(msgmap, MN_EXLD)){ SEARCHPGM *pgm; long i; int exbits; /* * Since excluded means "hidden deleted" and "killed", * the count should reflect the former. */ pgm = mail_newsearchpgm(); pgm->deleted = 1; pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++) if((mc = mail_elt(stream, i)) && mc->searched && get_lflag(stream, NULL, i, MN_EXLD) && !(msgno_exceptions(stream, i, "0", &exbits, FALSE) && (exbits & MSG_EX_FILTERED))) del_count++; if(del_count > 0L){ state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count, plural(del_count), MAX_SCREEN_COLS+1-45, pretty_fn(state->cur_folder)); prompt[sizeof(prompt)-1] = '\0'; if(F_ON(F_FULL_AUTO_EXPUNGE, state) || (F_ON(F_AUTO_EXPUNGE, state) && (state->context_current && (state->context_current->use & CNTXT_INCMNG)) && context_isambig(state->cur_folder)) || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){ long save_cur_rawno; int were_viewing_a_thread; save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); were_viewing_a_thread = (THREADING() && sp_viewing_a_thread(stream)); if(msgno_include(stream, msgmap, MI_NONE)){ clear_index_cache(stream, 0); if(stream && stream->spare) erase_threading_info(stream, msgmap); refresh_sort(stream, msgmap, SRT_NON); } if(were_viewing_a_thread){ if(save_cur_rawno > 0L) mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno)); if(view_thread(state, stream, msgmap, 1) && current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; } if(save_cur_rawno > 0L) mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno)); state->mangled_screen = 1; q_status_message2(SM_ORDER, 0, 4, "%s message%s UNexcluded", long2string(del_count), plural(del_count)); if(in_index != View) adjust_cur_to_visible(stream, msgmap); } else any_messages(NULL, NULL, "UNexcluded"); } else any_messages(NULL, "excluded", "to UNexclude"); } else any_messages(NULL, "excluded", "to UNexclude"); break; /*------- Make Selection -----------*/ case MC_SELECT : if(any_messages(msgmap, NULL, "to Select")){ if(aggregate_select(state, msgmap, question_line, in_index) == 0 && (in_index == MsgIndx || in_index == ThrdIndx) && F_ON(F_AUTO_ZOOM, state) && any_lflagged(msgmap, MN_SLCT) > 0L && !any_lflagged(msgmap, MN_HIDE)) (void) zoom_index(state, stream, msgmap, MN_SLCT); } break; /*------- Toggle Current Message Selection State -----------*/ case MC_SELCUR : if(any_messages(msgmap, NULL, NULL)){ if((select_by_current(state, msgmap, in_index) || (F_OFF(F_UNSELECT_WONT_ADVANCE, state) && !any_lflagged(msgmap, MN_HIDE))) && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){ /* advance current */ mn_inc_cur(stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); } } break; /*------- Apply command -----------*/ case MC_APPLY : if(any_messages(msgmap, NULL, NULL)){ if(any_lflagged(msgmap, MN_SLCT) > 0L){ if(apply_command(state, stream, msgmap, 0, AC_NONE, question_line)){ if(F_ON(F_AUTO_UNSELECT, state)){ agg_select_all(stream, msgmap, NULL, 0); unzoom_index(state, stream, msgmap); } else if(F_ON(F_AUTO_UNZOOM, state)) unzoom_index(state, stream, msgmap); } } else any_messages(NULL, NULL, "to Apply command to. Try \"Select\""); } break; /*-------- Sort command -------*/ case MC_SORT : { int were_threading = THREADING(); SortOrder sort = mn_get_sort(msgmap); int rev = mn_get_revsort(msgmap); dprint((1,"MAIL_CMD: sort\n")); if(select_sort(state, question_line, &sort, &rev)){ /* $ command reinitializes threading collapsed/expanded info */ if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX()) erase_threading_info(stream, msgmap); if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN); } state->mangled_footer = 1; /* * We've changed whether we are threading or not so we need to * exit the index and come back in so that we switch between the * thread index and the regular index. Sort_folder will have * reset viewing_a_thread if necessary. */ if(SEP_THRDINDX() && ((!were_threading && THREADING()) || (were_threading && !THREADING()))){ state->next_screen = mail_index_screen; state->mangled_screen = 1; } } break; /*------- Toggle Full Headers -----------*/ case MC_FULLHDR : state->full_header++; if(state->full_header == 1){ if(!(state->quote_suppression_threshold && (state->some_quoting_was_suppressed || in_index != View))) state->full_header++; } else if(state->full_header > 2) state->full_header = 0; switch(state->full_header){ case 0: q_status_message(SM_ORDER, 0, 3, _("Display of full headers is now off.")); break; case 1: q_status_message1(SM_ORDER, 0, 3, _("Quotes displayed, use %s to see full headers"), F_ON(F_USE_FK, state) ? "F9" : "H"); break; case 2: q_status_message(SM_ORDER, 0, 3, _("Display of full headers is now on.")); break; } a_changed = TRUE; break; case MC_TOGGLE : a_changed = TRUE; break; #ifdef SMIME /*------- Try to decrypt message -----------*/ case MC_DECRYPT: if(state->smime && state->smime->need_passphrase) smime_get_passphrase(); a_changed = TRUE; break; case MC_SECURITY: smime_info_screen(state); break; #endif /*------- Bounce -----------*/ case MC_BOUNCE : (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL); break; /*------- Flag -----------*/ case MC_FLAG : dprint((4, "\n - flag message -\n")); (void) cmd_flag(state, msgmap, MCMD_NONE); break; /*------- Pipe message -----------*/ case MC_PIPE : (void) cmd_pipe(state, msgmap, MCMD_NONE); break; /*--------- Default, unknown command ----------*/ default: alpine_panic("Unexpected command case"); break; } return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0); } /*---------------------------------------------------------------------- Map some of the special characters into sensible strings for human consumption. c is a UCS-4 character! ----*/ char * pretty_command(UCS c) { static char buf[10]; char *s; buf[0] = '\0'; s = buf; switch(c){ case ' ' : s = "SPACE"; break; case '\033' : s = "ESC"; break; case '\177' : s = "DEL"; break; case ctrl('I') : s = "TAB"; break; case ctrl('J') : s = "LINEFEED"; break; case ctrl('M') : s = "RETURN"; break; case ctrl('Q') : s = "XON"; break; case ctrl('S') : s = "XOFF"; break; case KEY_UP : s = "Up Arrow"; break; case KEY_DOWN : s = "Down Arrow"; break; case KEY_RIGHT : s = "Right Arrow"; break; case KEY_LEFT : s = "Left Arrow"; break; case KEY_PGUP : s = "Prev Page"; break; case KEY_PGDN : s = "Next Page"; break; case KEY_HOME : s = "Home"; break; case KEY_END : s = "End"; break; case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */ case KEY_JUNK : s = "Junk!"; break; case BADESC : s = "Bad Esc"; break; case NO_OP_IDLE : s = "NO_OP_IDLE"; break; case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break; case KEY_RESIZE : s = "KEY_RESIZE"; break; case KEY_UTF8 : s = "KEY_UTF8"; break; case KEY_MOUSE : s = "KEY_MOUSE"; break; case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break; case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break; case KEY_SCRLTO : s = "KEY_SCRLTO"; break; case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break; case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break; case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break; case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break; case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break; case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break; case PF1 : case PF2 : case PF3 : case PF4 : case PF5 : case PF6 : case PF7 : case PF8 : case PF9 : case PF10 : case PF11 : case PF12 : snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1)); break; default: if(c < ' ' || (c >= 0x80 && c < 0xA0)){ char d; int c1; c1 = (c >= 0x80); d = (c & 0x1f) + 'A' - 1; snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d); } else{ memset(buf, 0, sizeof(buf)); utf8_put((unsigned char *) buf, (unsigned long) c); } break; } return(s); } /*---------------------------------------------------------------------- Complain about bogus input Args: ch -- input command to complain about help -- string indicating where to get help ----*/ void bogus_command(UCS cmd, char *help) { if(cmd == ctrl('Q') || cmd == ctrl('S')) q_status_message1(SM_ASYNC, 0, 2, "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.", pretty_command(cmd)); else if(cmd == KEY_JUNK) q_status_message3(SM_ORDER, 0, 2, "Invalid key pressed.%s%s%s", (help) ? " Use " : "", (help) ? help : "", (help) ? " for help" : ""); else q_status_message4(SM_ORDER, 0, 2, "Command \"%s\" not defined for this screen.%s%s%s", pretty_command(cmd), (help) ? " Use " : "", (help) ? help : "", (help) ? " for help" : ""); } void bogus_utf8_command(char *cmd, char *help) { q_status_message4(SM_ORDER, 0, 2, "Command \"%s\" not defined for this screen.%s%s%s", cmd ? cmd : "?", (help) ? " Use " : "", (help) ? help : "", (help) ? " for help" : ""); } /*---------------------------------------------------------------------- Execute FLAG message command Args: state -- Various satate info msgmap -- map of c-client to local message numbers Result: with side effect of "current" message FLAG flag set or UNset ----*/ int cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt) { char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL; char *keyword_array[2]; int user_defined_flags = 0, mailbox_flags = 0; int directly_to_maint_screen = 0; int use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global); long unflagged, flagged, flags, rawno; MESSAGECACHE *mc = NULL; KEYWORD_S *kw; int i, cnt, is_set, trouble = 0, rv = 0; size_t len; struct flag_table *fp, *ftbl = NULL; struct flag_screen flag_screen; static char *flag_screen_text1[] = { N_(" Set desired flags for current message below. An 'X' means set"), N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."), NULL }; static char *flag_screen_text2[] = { N_(" Set desired flags below for selected messages. A '?' means to"), N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"), N_(" to unset it. Use the \"Return\" key to toggle, and choose"), N_(" \"Exit\" when finished."), NULL }; static struct flag_table default_ftbl[] = { {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL}, {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL}, {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL}, {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL}, {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL}, {NULL, NO_HELP, 0, 0, 0, NULL, NULL} }; /* Only check for dead stream for now. Should check permanent flags * eventually */ if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1))) return rv; if(sp_io_error_on_stream(state->mail_stream)){ sp_set_io_error_on_stream(state->mail_stream, 0); pine_mail_check(state->mail_stream); /* forces write */ return rv; } go_again: answer = NULL; user_defined_flags = 0; mailbox_flags = 0; mc = NULL; trouble = 0; ftbl = NULL; /* count how large ftbl will be */ for(cnt = 0; default_ftbl[cnt].name; cnt++) ; /* add user flags */ for(kw = ps_global->keywords; kw; kw = kw->next){ if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){ user_defined_flags++; cnt++; } } /* * Add mailbox flags that aren't user-defined flags. * Don't consider it if it matches either one of our defined * keywords or one of our defined nicknames for a keyword. */ for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){ char *q; q = stream_to_user_flag_name(state->mail_stream, i); if(q && q[0]){ for(kw = ps_global->keywords; kw; kw = kw->next){ if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q))) break; } } if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){ mailbox_flags++; cnt++; } } cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0); /* set up ftbl, first the system flags */ ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl)); memset(ftbl, 0, (cnt+1) * sizeof(*ftbl)); for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){ fp->name = cpystr(default_ftbl[i].name); fp->help = default_ftbl[i].help; fp->flag = default_ftbl[i].flag; fp->set = default_ftbl[i].set; fp->ukn = default_ftbl[i].ukn; } if(user_defined_flags){ fp->flag = F_COMMENT; fp->name = cpystr(""); fp++; fp->flag = F_COMMENT; len = strlen(_("User-defined Keywords from Setup/Config")); fp->name = (char *) fs_get((len+6+6+1) * sizeof(char)); snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config")); fp++; } /* then the user-defined keywords */ if(user_defined_flags) for(kw = ps_global->keywords; kw; kw = kw->next){ if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){ fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : ""); fp->keyword = cpystr(kw->kw ? kw->kw : ""); if(kw->nick && kw->kw){ size_t l; l = strlen(kw->kw)+2; fp->comment = (char *) fs_get((l+1) * sizeof(char)); snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw); fp->comment[l] = '\0'; } fp->help = h_flag_user_flag; fp->flag = F_KEYWORD; fp->set = 0; fp->ukn = 0; fp++; } } if(mailbox_flags){ fp->flag = F_COMMENT; fp->name = cpystr(""); fp++; fp->flag = F_COMMENT; len = strlen(_("Other keywords in the mailbox that are not user-defined")); fp->name = (char *) fs_get((len+6+6+1) * sizeof(char)); snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined")); fp++; } /* then the extra mailbox-defined keywords */ if(mailbox_flags) for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){ char *q; q = stream_to_user_flag_name(state->mail_stream, i); if(q && q[0]){ for(kw = ps_global->keywords; kw; kw = kw->next){ if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q))) break; } } if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){ fp->name = cpystr(q); fp->keyword = cpystr(q); fp->help = h_flag_user_flag; fp->flag = F_KEYWORD; fp->set = 0; fp->ukn = 0; fp++; } } flag_screen.flag_table = &ftbl; flag_screen.explanation = screen_text; if(MCMD_ISAGG(aopt)){ if(!pseudo_selected(ps_global->mail_stream, msgmap)){ free_flag_table(&ftbl); return rv; } exp = flag_screen_text2; for(fp = ftbl; fp->name; fp++){ fp->set = CMD_FLAG_UNKN; /* set to unknown */ fp->ukn = TRUE; } } else if(state->mail_stream && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L && rawno <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, rawno))){ exp = flag_screen_text1; for(fp = &ftbl[0]; fp->name; fp++){ fp->ukn = 0; if(fp->flag == F_KEYWORD){ /* see if this keyword is defined for this message */ fp->set = CMD_FLAG_CLEAR; if(user_flag_is_set(state->mail_stream, rawno, fp->keyword)) fp->set = CMD_FLAG_SET; } else if(fp->flag == F_FWD){ /* see if forwarded keyword is defined for this message */ fp->set = CMD_FLAG_CLEAR; if(user_flag_is_set(state->mail_stream, rawno, FORWARDED_FLAG)) fp->set = CMD_FLAG_SET; } else if(fp->flag != F_COMMENT) fp->set = ((fp->flag == F_SEEN && !mc->seen) || (fp->flag == F_DEL && mc->deleted) || (fp->flag == F_FLAG && mc->flagged) || (fp->flag == F_ANS && mc->answered)) ? CMD_FLAG_SET : CMD_FLAG_CLEAR; } } else{ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Error accessing message data")); free_flag_table(&ftbl); return rv; } if(directly_to_maint_screen) goto the_maint_screen; #ifdef _WINDOWS if (mswin_usedialog ()) { if (!os_flagmsgdialog (&ftbl[0])){ free_flag_table(&ftbl); return rv; } } else #endif { int keyword_shortcut = 0; if(!use_maint_screen){ /* * We're going to call cmd_flag_prompt(). We need * to decide whether or not to offer the keyword setting * shortcut. We'll offer it if the user has the feature * enabled AND there are some possible keywords that could * be set. */ if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){ for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){ if(fp->flag == F_KEYWORD){ int first_char; ESCKEY_S *tp; first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2; if(isascii(first_char) && isupper(first_char)) first_char = tolower((unsigned char) first_char); for(tp=flag_text_opt; tp->ch != -1; tp++) if(tp->ch == first_char) break; if(tp->ch == -1) keyword_shortcut++; } } } use_maint_screen = !cmd_flag_prompt(state, &flag_screen, keyword_shortcut); } the_maint_screen: if(use_maint_screen){ for(p = &screen_text[0]; *exp; p++, exp++) *p = *exp; *p = NULL; directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen); } } /* reacquire the elt pointer */ mc = (state->mail_stream && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L && rawno <= state->mail_stream->nmsgs) ? mail_elt(state->mail_stream, rawno) : NULL; for(fp = ftbl; mc && fp->name; fp++){ flags = -1; switch(fp->flag){ case F_SEEN: if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen) || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ flagit = "\\SEEN"; if(fp->set){ flags = 0L; unflagged = F_SEEN; } else{ flags = ST_SET; unflagged = F_UNSEEN; } } break; case F_ANS: if((!MCMD_ISAGG(aopt) && fp->set != mc->answered) || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ flagit = "\\ANSWERED"; if(fp->set){ flags = ST_SET; unflagged = F_UNANS; } else{ flags = 0L; unflagged = F_ANS; } } break; case F_DEL: if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted) || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ flagit = "\\DELETED"; if(fp->set){ flags = ST_SET; unflagged = F_UNDEL; } else{ flags = 0L; unflagged = F_DEL; } } break; case F_FLAG: if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged) || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ flagit = "\\FLAGGED"; if(fp->set){ flags = ST_SET; unflagged = F_UNFLAG; } else{ flags = 0L; unflagged = F_FLAG; } } break; case F_FWD : if(!MCMD_ISAGG(aopt)){ /* see if forwarded is defined for this message */ is_set = CMD_FLAG_CLEAR; if(user_flag_is_set(state->mail_stream, mn_m2raw(msgmap, mn_get_cur(msgmap)), FORWARDED_FLAG)) is_set = CMD_FLAG_SET; } if((!MCMD_ISAGG(aopt) && fp->set != is_set) || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ flagit = FORWARDED_FLAG; if(fp->set){ flags = ST_SET; unflagged = F_UNFWD; } else{ flags = 0L; unflagged = F_FWD; } } break; case F_KEYWORD: if(!MCMD_ISAGG(aopt)){ /* see if this keyword is defined for this message */ is_set = CMD_FLAG_CLEAR; if(user_flag_is_set(state->mail_stream, mn_m2raw(msgmap, mn_get_cur(msgmap)), fp->keyword)) is_set = CMD_FLAG_SET; } if((!MCMD_ISAGG(aopt) && fp->set != is_set) || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ flagit = fp->keyword; keyword_array[0] = fp->keyword; keyword_array[1] = NULL; if(fp->set){ flags = ST_SET; unflagged = F_UNKEYWORD; } else{ flags = 0L; unflagged = F_KEYWORD; } } break; default: break; } flagged = 0L; if(flags >= 0L && (seq = currentf_sequence(state->mail_stream, msgmap, unflagged, &flagged, unflagged & F_DEL, (fp->flag == F_KEYWORD && unflagged == F_KEYWORD) ? keyword_array : NULL, (fp->flag == F_KEYWORD && unflagged == F_UNKEYWORD) ? keyword_array : NULL))){ /* * For user keywords, we may have to create the flag in * the folder if it doesn't already exist and we are setting * it (as opposed to clearing it). Mail_flag will * do that for us, but it's failure isn't very friendly * error-wise. So we try to make it a little smoother. */ if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set || ((i=user_flag_index(state->mail_stream, flagit)) >= 0 && i < NUSERFLAGS)) mail_flag(state->mail_stream, seq, flagit, flags); else{ /* trouble, see if we can add the user flag */ if(state->mail_stream->kwd_create) mail_flag(state->mail_stream, seq, flagit, flags); else{ trouble++; if(some_user_flags_defined(state->mail_stream)) q_status_message(SM_ORDER, 3, 4, _("No more keywords allowed in this folder!")); else if(fp->flag == F_FWD) q_status_message(SM_ORDER, 3, 4, _("Cannot add keywords for this folder so cannot set Forwarded flag")); else q_status_message(SM_ORDER, 3, 4, _("Cannot add keywords for this folder")); } } fs_give((void **) &seq); if(flagged && !trouble){ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"", (fp->set) ? "F" : "Unf", MCMD_ISAGG(aopt) ? " " : "", MCMD_ISAGG(aopt) ? long2string(flagged) : "", (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap)) ? " (of " : "", (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap)) ? comatose(mn_total_cur(msgmap)) : "", (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap)) ? ")" : "", MCMD_ISAGG(aopt) ? plural(flagged) : " ", MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)), fp->name); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf); rv++; } } } free_flag_table(&ftbl); if(directly_to_maint_screen) goto go_again; if(MCMD_ISAGG(aopt)) restore_selected(msgmap); if(!answer) q_status_message(SM_ORDER, 0, 2, _("No flags changed.")); return rv; } /*---------------------------------------------------------------------- Offer concise status line flag prompt Args: state -- Various satate info flags -- flags to offer setting Result: TRUE if flag to set specified in flags struct or FALSE otw ----*/ int cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts) { int r, setflag = 1, first_char; struct flag_table *fp; ESCKEY_S *ek; char *ftext, *ftext_not; static char *flag_text = N_("Flag New, Deleted, Answered, Forwarded or Important ? "); static char *flag_text_ak = N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? "); static char *flag_text_not = N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? "); static char *flag_text_ak_not = N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? "); if(allow_keyword_shortcuts){ int cnt = 0; ESCKEY_S *dp, *sp, *tp; for(sp=flag_text_opt; sp->ch != -1; sp++) cnt++; for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++) if(fp->flag == F_KEYWORD) cnt++; /* set up an ESCKEY_S list which includes invisible keys for keywords */ ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek)); memset(ek, 0, (cnt+1) * sizeof(*ek)); for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++) *dp = *sp; for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){ if(fp->flag == F_KEYWORD){ first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2; if(isascii(first_char) && isupper(first_char)) first_char = tolower((unsigned char) first_char); /* * Check to see if an earlier keyword in the list, or one of * the builtin system letters already uses this character. * If so, the first one wins. */ for(tp=ek; tp->ch != 0; tp++) if(tp->ch == first_char) break; if(tp->ch != 0) continue; /* skip it, already used that char */ dp->ch = first_char; dp->rval = first_char; dp->name = ""; dp->label = ""; dp++; } } dp->ch = -1; ftext = _(flag_text_ak); ftext_not = _(flag_text_ak_not); } else{ ek = flag_text_opt; ftext = _(flag_text); ftext_not = _(flag_text_not); } while(1){ r = radio_buttons(setflag ? ftext : ftext_not, -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1, NO_HELP, RB_NORM | RB_SEQ_SENSITIVE); /* * It is SEQ_EXCEPTION-1 just so that it is some value that isn't * being used otherwise. The keywords use up all the possible * letters, so a negative number is good, but it has to be different * from other negative return values. */ if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */ return(TRUE); else if(r == 10) /* return and goto flag screen */ return(FALSE); else if(r == '!') /* flip intention */ setflag = !setflag; else break; } for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){ if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){ if((r == 'n' && fp->flag == F_SEEN) || (r == '*' && fp->flag == F_FLAG) || (r == 'd' && fp->flag == F_DEL) || (r == 'f' && fp->flag == F_FWD) || (r == 'a' && fp->flag == F_ANS)){ fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR; break; } } else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){ first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2; if(isascii(first_char) && isupper(first_char)) first_char = tolower((unsigned char) first_char); if(r == first_char){ fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR; break; } } } if(ek != flag_text_opt) fs_give((void **) &ek); return(TRUE); } /* * (*ft) is an array of flag_table entries. */ void free_flag_table(struct flag_table **ft) { struct flag_table *fp; if(ft && *ft){ for(fp = (*ft); fp->name; fp++){ if(fp->name) fs_give((void **) &fp->name); if(fp->keyword) fs_give((void **) &fp->keyword); if(fp->comment) fs_give((void **) &fp->comment); } fs_give((void **) ft); } } /*---------------------------------------------------------------------- Execute REPLY message command Args: state -- Various satate info msgmap -- map of c-client to local message numbers Result: reply sent or not ----*/ int cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role) { int rv = 0; if(any_messages(msgmap, NULL, "to Reply to")){ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) return rv; rv = reply(state, role); if(MCMD_ISAGG(aopt)) restore_selected(msgmap); state->mangled_screen = 1; } return rv; } /*---------------------------------------------------------------------- Execute FORWARD message command Args: state -- Various satate info msgmap -- map of c-client to local message numbers Result: selected message[s] forwarded or not ----*/ int cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role) { int rv = 0; if(any_messages(msgmap, NULL, "to Forward")){ if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) return rv; rv = forward(state, role); if(MCMD_ISAGG(aopt)) restore_selected(msgmap); state->mangled_screen = 1; } return rv; } /*---------------------------------------------------------------------- Execute BOUNCE message command Args: state -- Various satate info msgmap -- map of c-client to local message numbers aopt -- aggregate options Result: selected message[s] bounced or not ----*/ int cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role) { int rv = 0; if(any_messages(msgmap, NULL, "to Bounce")){ long i; if(MCMD_ISAGG(aopt)){ if(!pseudo_selected(state->mail_stream, msgmap)) return rv; } else if((i = any_lflagged(msgmap, MN_SLCT)) > 0 && get_lflag(state->mail_stream, msgmap, mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0) q_status_message(SM_ORDER | SM_DING, 3, 4, _("WARNING: non-selected message is being bounced!")); else if (i > 1L && get_lflag(state->mail_stream, msgmap, mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT)) q_status_message(SM_ORDER | SM_DING, 3, 4, _("WARNING: not bouncing all selected messages!")); rv = bounce(state, role); if(MCMD_ISAGG(aopt)) restore_selected(msgmap); state->mangled_footer = 1; } return rv; } /*---------------------------------------------------------------------- Execute save message command: prompt for folder and call function to save Args: screen_line -- Line on the screen to prompt on message -- The MESSAGECACHE entry of message to save Result: The folder lister can be called to make selection; mangled screen set This does the prompting for the folder name to save to, possibly calling up the folder display for selection of folder by user. ----*/ int cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index) { char newfolder[MAILTMPLEN], nmsgs[32], *nick; int we_cancel = 0, rv = 0, save_flags; long i, raw; CONTEXT_S *cntxt = NULL; ENVELOPE *e = NULL; SaveDel del = DontAsk; SavePreserveOrder pre = DontAskPreserve; dprint((4, "\n - saving message -\n")); if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap)) return rv; state->ugly_consider_advancing_bit = 0; if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state) && msgno_any_deletedparts(stream, msgmap) && want_to(_("Saved copy will NOT include entire message! Continue"), 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){ restore_selected(msgmap); cmd_cancelled("Save message"); return rv; } raw = mn_m2raw(msgmap, mn_get_cur(msgmap)); if(mn_total_cur(msgmap) <= 1L){ snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap)); nmsgs[sizeof(nmsgs)-1] = '\0'; e = pine_mail_fetchstructure(stream, raw, NULL); if(!e) { q_status_message(SM_ORDER | SM_DING, 3, 4, _("Can't save message. Error accessing folder")); restore_selected(msgmap); return rv; } } else{ snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap))); nmsgs[sizeof(nmsgs)-1] = '\0'; /* e is just used to get a default save folder from the first msg */ e = pine_mail_fetchstructure(stream, mn_m2raw(msgmap, mn_first_cur(msgmap)), NULL); } del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global)) ? Del : NoDel; if(mn_total_cur(msgmap) > 1L) pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve; else pre = DontAskPreserve; if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e, raw, NULL, &del, &pre)){ if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } save_flags = SV_FIX_DELS; if(pre == RetPreserve) save_flags |= SV_PRESERVE; if(del == RetDel) save_flags |= SV_DELETE; if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name)) save_flags |= SV_INBOXWOCNTXT; we_cancel = busy_cue(_("Saving"), NULL, 1); i = save(state, stream, cntxt, newfolder, msgmap, save_flags); if(we_cancel) cancel_busy_cue(0); if(i == mn_total_cur(msgmap)){ rv++; if(mn_total_cur(msgmap) <= 1L){ int need, avail = ps_global->ttyo->screen_cols - 2; int lennick, lenfldr; if(cntxt && ps_global->context_list->next && context_isambig(newfolder)){ lennick = MIN(strlen(cntxt->nickname), 500); lenfldr = MIN(strlen(newfolder), 500); need = 27 + strlen(long2string(mn_get_cur(msgmap))) + lenfldr + lennick; if(need > avail){ if(lennick > 10){ need -= MIN(lennick-10, need-avail); lennick -= MIN(lennick-10, need-avail); } if(need > avail && lenfldr > 10) lenfldr -= MIN(lenfldr-10, need-avail); } snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Message %s copied to \"%s\" in <%s>", long2string(mn_get_cur(msgmap)), short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000, lenfldr, MidDots), short_str(cntxt->nickname, (char *)(tmp_20k_buf+2000), 1000, lennick, EndDots)); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; } else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Message %s copied to \"%s\"", long2string(mn_get_cur(msgmap)), nick); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; } else{ char *f = " folder"; lenfldr = MIN(strlen(newfolder), 500); need = 28 + strlen(long2string(mn_get_cur(msgmap))) + lenfldr; if(need > avail){ need -= strlen(f); f = ""; if(need > avail && lenfldr > 10) lenfldr -= MIN(lenfldr-10, need-avail); } snprintf(tmp_20k_buf,SIZEOF_20KBUF, "Message %s copied to%s \"%s\"", long2string(mn_get_cur(msgmap)), f, short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000, lenfldr, MidDots)); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; } } else{ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved", comatose(mn_total_cur(msgmap))); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; } if(del == RetDel){ strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; } q_status_message(SM_ORDER, 0, 3, tmp_20k_buf); if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){ if(sp_new_mail_count(stream)) process_filter_patterns(stream, msgmap, sp_new_mail_count(stream)); mn_inc_cur(stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); } state->ugly_consider_advancing_bit = 1; } } if(MCMD_ISAGG(aopt)) /* straighten out fakes */ restore_selected(msgmap); if(del == RetDel) update_titlebar_status(); /* make sure they see change */ return rv; } void role_compose(struct pine *state) { int action; if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){ PAT_STATE pstate; if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){ action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "), -FOOTER_ROWS(state), choose_action, 'c', 'x', h_role_compose, RB_NORM); } else{ q_status_message(SM_ORDER, 0, 3, _("No roles available. Use Setup/Rules to add roles.")); return; } } else action = 'c'; if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){ ACTION_S *role = NULL; void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL; redraw = state->redrawer; state->redrawer = NULL; prev_screen = state->prev_screen; role = NULL; state->next_screen = SCREEN_FUN_NULL; /* Setup role */ if(role_select_screen(state, &role, action == 'f' ? MC_FORWARD : action == 'r' ? MC_REPLY : action == 'b' ? MC_BOUNCE : action == 'c' ? MC_COMPOSE : 0) < 0){ cmd_cancelled(action == 'f' ? _("Forward") : action == 'r' ? _("Reply") : action == 'c' ? _("Composition") : _("Bounce")); state->next_screen = prev_screen; state->redrawer = redraw; state->mangled_screen = 1; } else{ /* * If default role was selected (NULL) we need to make * up a role which won't do anything, but will cause * compose_mail to think there's already a role so that * it won't try to confirm the default. */ if(role) role = combine_inherited_role(role); else{ role = (ACTION_S *) fs_get(sizeof(*role)); memset((void *) role, 0, sizeof(*role)); role->nick = cpystr("Default Role"); } if(state->role) fs_give((void **)&state->role); state->role = cpystr(role->nick); /* remember the role */ state->redrawer = NULL; switch(action){ case 'c': compose_mail(NULL, NULL, role, NULL, NULL); break; case 'r': (void) reply(state, role); break; case 'f': (void) forward(state, role); break; case 'b': (void) bounce(state, role); break; } if(role) free_action(&role); state->next_screen = prev_screen; state->redrawer = redraw; state->mangled_screen = 1; } } } /*---------------------------------------------------------------------- Do the dirty work of prompting the user for a folder name Args: nfldr should be a buffer at least MAILTMPLEN long dela -- a pointer to a SaveDel. If it is DontAsk on input, don't offer Delete prompt Del on input, offer Delete command with default of Delete NoDel NoDelete RetDel and RetNoDel are return values Result: ----*/ 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) { int rc, ku = -1, n = 0, flags, last_rc = 0, saveable_count = 0, done = 0; int delindex = 0, preindex = 0, r; char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN]; char *buf = tmp_20k_buf; char shortbuf[200]; char *folder, folder2[MAXPATH]; HelpType help; SaveDel del = DontAsk; SavePreserveOrder pre = DontAskPreserve; char *deltext = NULL; static HISTORY_S *history = NULL; CONTEXT_S *tc; ESCKEY_S ekey[10]; RULE_RESULT *rule; if(!cntxt) alpine_panic("no context ptr in save_prompt"); init_hist(&history, HISTSIZE); if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt))) return(0); /* message expunged! */ if (rule = get_result_rule(V_SAVE_RULES, FOR_SAVE, env)){ strncpy(folder2,rule->result,sizeof(folder2)-1); folder2[sizeof(folder2)-1] = '\0'; folder = folder2; if (rule->result) fs_give((void **)&rule->result); fs_give((void **)&rule); } /* how many context's can be saved to... */ for(tc = state->context_list; tc; tc = tc->next) if(!NEWS_TEST(tc)) saveable_count++; /* set up extra command option keys */ rc = 0; ekey[rc].ch = ctrl('T'); ekey[rc].rval = 2; ekey[rc].name = "^T"; /* TRANSLATORS: command means go to Folders list */ ekey[rc++].label = N_("To Fldrs"); if(saveable_count > 1){ ekey[rc].ch = ctrl('P'); ekey[rc].rval = 10; ekey[rc].name = "^P"; ekey[rc++].label = N_("Prev Collection"); ekey[rc].ch = ctrl('N'); ekey[rc].rval = 11; ekey[rc].name = "^N"; ekey[rc++].label = N_("Next Collection"); } if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){ ekey[rc].ch = TAB; ekey[rc].rval = 12; ekey[rc].name = "TAB"; /* TRANSLATORS: command asks alpine to complete the name when tab is typed */ ekey[rc++].label = N_("Complete"); } if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){ ekey[rc].ch = ctrl('X'); ekey[rc].rval = 14; ekey[rc].name = "^X"; /* TRANSLATORS: list all the matches */ ekey[rc++].label = N_("ListMatches"); } if(dela && (*dela == NoDel || *dela == Del)){ ekey[rc].ch = ctrl('R'); ekey[rc].rval = 15; ekey[rc].name = "^R"; delindex = rc++; del = *dela; } if(prea && (*prea == NoPreserve || *prea == Preserve)){ ekey[rc].ch = ctrl('W'); ekey[rc].rval = 16; ekey[rc].name = "^W"; preindex = rc++; pre = *prea; } if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ ekey[rc].ch = KEY_UP; ekey[rc].rval = 10; ekey[rc].name = ""; ekey[rc++].label = ""; ekey[rc].ch = KEY_DOWN; ekey[rc].rval = 11; ekey[rc].name = ""; ekey[rc++].label = ""; } else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ ekey[rc].ch = KEY_UP; ekey[rc].rval = 30; ekey[rc].name = ""; ku = rc; ekey[rc++].label = ""; ekey[rc].ch = KEY_DOWN; ekey[rc].rval = 31; ekey[rc].name = ""; ekey[rc++].label = ""; } ekey[rc].ch = -1; *nfldr = '\0'; help = NO_HELP; while(!done){ /* only show collection number if more than one available */ if(ps_global->context_list->next) snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ", deltext ? deltext : "", nmsgs, short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots), strsquish(buf, SIZEOF_20KBUF, folder, 25)); else snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ", deltext ? deltext : "", nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40)); prompt[sizeof(prompt)-1] = '\0'; /* * If the prompt won't fit, try removing deltext. */ if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){ if(ps_global->context_list->next) snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ", nmsgs, short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots), strsquish(buf, SIZEOF_20KBUF, folder, 25)); else snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ", nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40)); prompt[sizeof(prompt)-1] = '\0'; } /* * If the prompt still won't fit, remove the extra info contained * in nmsgs. */ if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){ if(ps_global->context_list->next) snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ", short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots), strsquish(buf, SIZEOF_20KBUF, folder, 25)); else snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ", strsquish(buf, SIZEOF_20KBUF, folder, 25)); prompt[sizeof(prompt)-1] = '\0'; } if(del != DontAsk) ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete"; if(pre != DontAskPreserve) ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order"; if(ku >= 0){ if(items_in_hist(history) > 1){ ekey[ku].name = HISTORY_UP_KEYNAME; ekey[ku].label = HISTORY_KEYLABEL; ekey[ku+1].name = HISTORY_DOWN_KEYNAME; ekey[ku+1].label = HISTORY_KEYLABEL; } else{ ekey[ku].name = ""; ekey[ku].label = ""; ekey[ku+1].name = ""; ekey[ku+1].label = ""; } } flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE; rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr, prompt, ekey, help, &flags); switch(rc){ case -1 : q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error reading folder name")); done--; break; case 0 : removing_trailing_white_space(nfldr); removing_leading_white_space(nfldr); if(*nfldr || *folder){ char *p, *name, *fullname = NULL; int exists, breakout = FALSE; if(!*nfldr){ strncpy(nfldr, folder, len_nfldr-1); nfldr[len_nfldr-1] = '\0'; } save_hist(history, nfldr, 0, (void *) *cntxt); if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))) name = nfldr; if(update_folder_spec(expanded, sizeof(expanded), name)){ strncpy(name = nfldr, expanded, len_nfldr-1); nfldr[len_nfldr-1] = '\0'; } exists = folder_name_exists(*cntxt, name, &fullname); if(exists == FEX_ERROR){ q_status_message1(SM_ORDER, 0, 3, _("Problem accessing folder \"%s\""), nfldr); done--; } else{ if(fullname){ strncpy(name = nfldr, fullname, len_nfldr-1); nfldr[len_nfldr-1] = '\0'; fs_give((void **) &fullname); breakout = TRUE; } if(exists & FEX_ISFILE){ done++; } else if((exists & FEX_ISDIR)){ char tmp[MAILTMPLEN]; tc = *cntxt; if(breakout){ CONTEXT_S *fake_context; size_t l; strncpy(tmp, name, sizeof(tmp)); tmp[sizeof(tmp)-2-1] = '\0'; if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){ if(l < sizeof(tmp)){ tmp[l] = tc->dir->delim; strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1)); } } else strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1); tmp[sizeof(tmp)-1] = '\0'; fake_context = new_context(tmp, 0); nfldr[0] = '\0'; done = display_folder_list(&fake_context, nfldr, 1, folders_for_save); free_context(&fake_context); } else if(tc->dir->delim && (p = strrindex(name, tc->dir->delim)) && *(p+1) == '\0') done = display_folder_list(cntxt, nfldr, 1, folders_for_save); else{ q_status_message1(SM_ORDER, 3, 3, _("\"%s\" is a directory"), name); if(tc->dir->delim && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){ strncpy(tmp, name, sizeof(tmp)); tmp[sizeof(tmp)-1] = '\0'; snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim); } } } else{ /* Doesn't exist, create! */ if((fullname = folder_as_breakout(*cntxt, name)) != NULL){ strncpy(name = nfldr, fullname, len_nfldr-1); nfldr[len_nfldr-1] = '\0'; fs_give((void **) &fullname); } switch(create_for_save(*cntxt, name)){ case 1 : /* success */ done++; break; case 0 : /* error */ case -1 : /* declined */ done--; break; } } } break; } /* else fall thru like they cancelled */ case 1 : cmd_cancelled("Save message"); done--; break; case 2 : r = display_folder_list(cntxt, nfldr, 0, folders_for_save); if(r) done++; break; case 3 : helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE); ps_global->mangled_screen = 1; break; case 4 : /* redraw */ break; case 10 : /* previous collection */ for(tc = (*cntxt)->prev; tc; tc = tc->prev) if(!NEWS_TEST(tc)) break; if(!tc){ CONTEXT_S *tc2; for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next) if(!NEWS_TEST(tc2)) tc = tc2; } *cntxt = tc; break; case 11 : /* next collection */ tc = (*cntxt); do if(((*cntxt) = (*cntxt)->next) == NULL) (*cntxt) = ps_global->context_list; while(NEWS_TEST(*cntxt) && (*cntxt) != tc); break; case 12 : /* file name completion */ if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){ if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){ r = display_folder_list(cntxt, nfldr, 1, folders_for_save); if(r) done++; /* bingo! */ else rc = 0; /* burn last_rc */ } else Writechar(BELL, 0); } break; case 14 : /* file name completion */ r = display_folder_list(cntxt, nfldr, 2, folders_for_save); if(r) done++; /* bingo! */ else rc = 0; /* burn last_rc */ break; case 15 : /* Delete / No Delete */ del = (del == NoDel) ? Del : NoDel; deltext = (del == NoDel) ? " (no delete)" : " (and delete)"; break; case 16 : /* Preserve Order or not */ pre = (pre == NoPreserve) ? Preserve : NoPreserve; break; case 30 : if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){ strncpy(nfldr, p, len_nfldr); nfldr[len_nfldr-1] = '\0'; if(history->hist[history->curindex]) *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt; } else Writechar(BELL, 0); break; case 31 : if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){ strncpy(nfldr, p, len_nfldr); nfldr[len_nfldr-1] = '\0'; if(history->hist[history->curindex]) *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt; } else Writechar(BELL, 0); break; default : alpine_panic("Unhandled case"); break; } last_rc = rc; } ps_global->mangled_footer = 1; if(done < 0) return(0); if(*nfldr){ strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1); ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0'; if(*cntxt) ps_global->last_save_context = *cntxt; } else{ strncpy(nfldr, folder, len_nfldr-1); nfldr[len_nfldr-1] = '\0'; } /* nickname? Copy real name to nfldr */ if(*cntxt && context_isambig(nfldr) && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){ strncpy(nfldr, p, len_nfldr-1); nfldr[len_nfldr-1] = '\0'; } if(dela && (*dela == NoDel || *dela == Del)) *dela = (del == NoDel) ? RetNoDel : RetDel; if(prea && (*prea == NoPreserve || *prea == Preserve)) *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve; return(1); } /*---------------------------------------------------------------------- Prompt user before implicitly creating a folder for saving Args: context - context to create folder in folder - folder name to create Result: 1 on proceed, -1 on decline, 0 on error ----*/ int create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive) { if(context && ps_global->context_list->next && context_isambig(folder)){ if(context->use & CNTXT_INCMNG){ snprintf(tmp_20k_buf,SIZEOF_20KBUF, _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"), folder, (strlen(folder) > 15) ? "..." : ""); q_status_message(SM_ORDER, 3, 3, tmp_20k_buf); return(0); /* error */ } snprintf(tmp_20k_buf,SIZEOF_20KBUF, _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"), folder, (strlen(folder) > 15) ? "..." : "", context->nickname, (strlen(context->nickname) > 15) ? "..." : ""); } else snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Folder \"%.40s%s\" doesn't exist. Create"), folder, strlen(folder) > 40 ? "..." : ""); if(want_to(tmp_20k_buf, 'y', 'n', NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){ cmd_cancelled("Save message"); return(-1); } return(1); } /*---------------------------------------------------------------------- Expunge messages from current folder Args: state -- pointer to struct holding a bunch of pine state msgmap -- table mapping msg nums to c-client sequence nums qline -- screen line to ask questions on agg -- boolean indicating we're to operate on aggregate set Result: ----*/ int cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg) { long del_count, prefilter_del_count = 0; int we_cancel = 0, rv = 0; char prompt[MAX_SCREEN_COLS+1]; char *sequence; COLOR_PAIR *lastc = NULL; dprint((2, "\n - expunge -\n")); del_count = 0; sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL; if(MCMD_ISAGG(agg)){ long i; MESSAGECACHE *mc; for(i = 1L; i <= stream->nmsgs; i++){ if((mc = mail_elt(stream, i)) != NULL && mc->sequence && mc->deleted) del_count++; } if(del_count == 0){ q_status_message(SM_ORDER, 0, 4, _("No selected messages are deleted")); return 0; } } else { if(!any_messages(msgmap, NULL, "to Expunge")) return rv; } if(IS_NEWS(stream) && stream->rdonly){ if(!MCMD_ISAGG(agg)) del_count = count_flagged(stream, F_DEL); if(del_count > 0L){ state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count, plural(del_count), MAX_SCREEN_COLS+1-40, pretty_fn(state->cur_folder)); prompt[sizeof(prompt)-1] = '\0'; if(F_ON(F_FULL_AUTO_EXPUNGE, state) || (F_ON(F_AUTO_EXPUNGE, state) && (state->context_current && (state->context_current->use & CNTXT_INCMNG)) && context_isambig(state->cur_folder)) || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){ if(F_ON(F_NEWS_CROSS_DELETE, state)) cross_delete_crossposts(stream); msgno_exclude_deleted(stream, msgmap, sequence); clear_index_cache(stream, 0); /* * This is kind of surprising at first. For most sort * orders, if the whole set is sorted, then any subset * is also sorted. Not so for threaded sorts. */ if(SORT_IS_THREADED(msgmap)) refresh_sort(stream, msgmap, SRT_NON); state->mangled_body = 1; state->mangled_header = 1; q_status_message2(SM_ORDER, 0, 4, "%s message%s excluded", long2string(del_count), plural(del_count)); } else any_messages(NULL, NULL, "Excluded"); } else any_messages(NULL, "deleted", "to Exclude"); return del_count; } else if(READONLY_FOLDER(stream)){ q_status_message(SM_ORDER, 0, 4, _("Can't expunge. Folder is read-only")); return del_count; } if(!MCMD_ISAGG(agg)){ prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT); mail_expunge_prefilter(stream, MI_NONE); del_count = count_flagged(stream, F_DEL|F_NOFILT); } if(del_count != 0){ int ret; unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder); /* MAX_SCREEN_COLS+1 = sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count, plural(del_count), MAX_SCREEN_COLS+1-40, pretty_fn((char *) fname)); if(fname) fs_give((void **)&fname); prompt[sizeof(prompt)-1] = '\0'; state->mangled_footer = 1; if(F_ON(F_FULL_AUTO_EXPUNGE, state) || (F_ON(F_AUTO_EXPUNGE, state) && ((!strucmp(state->cur_folder,state->inbox_name)) || (state->context_current->use & CNTXT_INCMNG)) && context_isambig(state->cur_folder)) || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y') ret = 'y'; if(ret == 'x') cmd_cancelled("Expunge"); if(ret != 'y') return 0; } dprint((8, "Expunge max:%ld cur:%ld kill:%d\n", mn_get_total(msgmap), mn_get_cur(msgmap), del_count)); lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR, state->VAR_TITLE_BACK_COLOR, PSC_REV|PSC_RET); PutLine0(0, 0, "**"); /* indicate delay */ if(lastc){ (void)pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0); fflush(stdout); we_cancel = busy_cue(_("Expunging"), NULL, 1); if(cmd_expunge_work(stream, msgmap, sequence)) state->mangled_body = 1; if(sequence) fs_give((void **)&sequence); if(we_cancel) cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1); lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR, state->VAR_TITLE_BACK_COLOR, PSC_REV|PSC_RET); PutLine0(0, 0, " "); /* indicate delay's over */ if(lastc){ (void)pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } fflush(stdout); if(sp_expunge_count(stream) > 0){ /* * This is kind of surprising at first. For most sort * orders, if the whole set is sorted, then any subset * is also sorted. Not so for threaded sorts. */ if(SORT_IS_THREADED(msgmap)) refresh_sort(stream, msgmap, SRT_NON); } else{ if(del_count){ unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder); q_status_message1(SM_ORDER, 0, 3, _("No messages expunged from folder \"%s\""), pretty_fn((char *) fname)); if(fname) fs_give((void **)&fname); } else if(!prefilter_del_count) q_status_message(SM_ORDER, 0, 3, _("No messages marked deleted. No messages expunged.")); } return del_count; } /*---------------------------------------------------------------------- Expunge_and_close callback to prompt user for confirmation Args: stream -- folder's stream folder -- name of folder containing folders deleted -- number of del'd msgs Result: 'y' to continue with expunge ----*/ int expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted) { long max_folder; int charcnt = 0; char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1]; char *short_folder_name; if(deleted == 1) charcnt = 1; else{ snprintf(temp, sizeof(temp), "%ld", deleted); charcnt = strlen(temp)+1; } max_folder = MAX(1,MAXPROMPT - (36+charcnt)); strncpy(temp, folder, sizeof(temp)); temp[sizeof(temp)-1] = '\0'; short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots); if(IS_NEWS(stream)) snprintf(prompt_b, sizeof(prompt_b), "Delete %s%ld message%s from \"%s\"", (deleted > 1L) ? "all " : "", deleted, plural(deleted), short_folder_name); else snprintf(prompt_b, sizeof(prompt_b), "Expunge the %ld deleted message%s from \"%s\"", deleted, deleted == 1 ? "" : "s", short_folder_name); return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM)); } /* * This is used with multiple append saves. Call it once before * the series of appends with SSCP_INIT and once after all are * done with SSCP_END. In between, it is called automatically * from save_fetch_append or save_fetch_append_cb when we need * to ask the user if he or she wants to continue even though * announced message size doesn't match the actual message size. * As of 2008-02-29 the gmail IMAP server has these size mismatches * on a regular basis even though the data is ok. */ int save_size_changed_prompt(long msgno, int flags) { int ret; char prompt[100]; static int remember_the_yes = 0; static int possible_corruption = 0; static ESCKEY_S save_size_opts[] = { {'y', 'y', "Y", "Yes"}, {'n', 'n', "N", "No"}, {'a', 'a', "A", "yes to All"}, {-1, 0, NULL, NULL} }; if(F_ON(F_IGNORE_SIZE, ps_global)) return 'y'; if(flags & SSCP_INIT || flags & SSCP_END){ if(flags & SSCP_END && possible_corruption) q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results"); remember_the_yes = 0; possible_corruption = 0; ps_global->noshow_error = 0; ps_global->noshow_warn = 0; return(0); } if(remember_the_yes){ snprintf(prompt, sizeof(prompt), "Message to save shrank! (msg # %ld): Continuing", msgno); q_status_message(SM_ORDER, 0, 3, prompt); display_message('x'); return(remember_the_yes); } snprintf(prompt, sizeof(prompt), "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno); ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts, 'n', 0, h_save_size_changed, RB_NORM|RB_NO_NEWMAIL); switch(ret){ case 'a': remember_the_yes = 'y'; possible_corruption++; return(remember_the_yes); case 'y': possible_corruption++; return('y'); default: possible_corruption = 0; ps_global->noshow_error = 1; ps_global->noshow_warn = 1; break; } return('n'); } /*---------------------------------------------------------------------- Expunge_and_close callback that happens once the decision to expunge and close has been made and before expunging and closing begins Args: stream -- folder's stream folder -- name of folder containing folders deleted -- number of del'd msgs Result: 'y' to continue with expunge ----*/ void expunge_and_close_begins(int flags, char *folder) { if(!(flags & EC_NO_CLOSE)){ unsigned char *fname = folder_name_decoded((unsigned char *)folder); q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname); flush_status_messages(1); if(fname) fs_give((void **)&fname); } } /*---------------------------------------------------------------------- Export a message to a plain file in users home directory Args: state -- pointer to struct holding a bunch of pine state msgmap -- table mapping msg nums to c-client sequence nums qline -- screen line to ask questions on agg -- boolean indicating we're to operate on aggregate set Result: ----*/ int cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt) { char filename[MAXPATH+1], full_filename[MAXPATH+1], *err; char nmsgs[80]; int r, leading_nl, failure = 0, orig_errno = 0, rflags = GER_NONE; int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0; ENVELOPE *env; MESSAGECACHE *mc; BODY *b; long i, count = 0L, start_of_append = 0, rawno; gf_io_t pc; STORE_S *store; struct variable *vars = state ? ps_global->vars : NULL; ESCKEY_S export_opts[5]; static HISTORY_S *history = NULL; if(ps_global->restricted){ q_status_message(SM_ORDER, 0, 3, "Alpine demo can't export messages to files"); return rv; } if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) return rv; export_opts[i = 0].ch = ctrl('T'); export_opts[i].rval = 10; export_opts[i].name = "^T"; export_opts[i++].label = N_("To Files"); #if !defined(DOS) && !defined(MAC) && !defined(OS2) if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){ export_opts[i].ch = ctrl('V'); export_opts[i].rval = 12; export_opts[i].name = "^V"; /* TRANSLATORS: this is an abbreviation for Download Messages */ export_opts[i++].label = N_("Downld Msg"); } #endif /* !(DOS || MAC) */ if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ export_opts[i].ch = ctrl('I'); export_opts[i].rval = 11; export_opts[i].name = "TAB"; export_opts[i++].label = N_("Complete"); } #if 0 /* Commented out since it's not yet support! */ if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){ export_opts[i].ch = ctrl('X'); export_opts[i].rval = 14; export_opts[i].name = "^X"; export_opts[i++].label = N_("ListMatches"); } #endif /* * If message has attachments, add a toggle that will allow the user * to save all of the attachments to a single directory, using the * names provided with the attachments or part names. What we'll do is * export the message as usual, and then export the attachments into * a subdirectory that did not exist before. The subdir will be named * something based on the name of the file being saved to, but a * unique, new name. */ if(!MCMD_ISAGG(aopt) && state->mail_stream && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L && rawno <= state->mail_stream->nmsgs && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b)) && b && b->type == TYPEMULTIPART && b->subtype && strucmp(b->subtype, "ALTERNATIVE") != 0){ PART *part; part = b->nested.part; /* 1st part */ if(part && part->next) flags |= GE_ALLPARTS; } export_opts[i].ch = -1; filename[0] = '\0'; if(mn_total_cur(msgmap) <= 1L){ snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap)); nmsgs[sizeof(nmsgs)-1] = '\0'; } else{ snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap))); nmsgs[sizeof(nmsgs)-1] = '\0'; } r = get_export_filename(state, filename, NULL, full_filename, sizeof(filename), nmsgs, "EXPORT", export_opts, &rflags, qline, flags, &history); if(r < 0){ switch(r){ case -1: cmd_cancelled("Export message"); break; case -2: q_status_message1(SM_ORDER, 0, 2, _("Can't export to file outside of %s"), VAR_OPER_DIR); break; } goto fini; } #if !defined(DOS) && !defined(MAC) && !defined(OS2) else if(r == 12){ /* Download */ char cmd[MAXPATH], *tfp = NULL; int next = 0; PIPE_S *syspipe; STORE_S *so; gf_io_t pc; if(ps_global->restricted){ q_status_message(SM_ORDER | SM_DING, 3, 3, "Download disallowed in restricted mode"); goto fini; } err = NULL; tfp = temp_nam(NULL, "pd"); build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX, ps_global->VAR_DOWNLOAD_CMD, tfp); dprint((1, "Download cmd called: \"%s\"\n", cmd)); if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){ gf_set_so_writec(&pc, so); for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){ if(!(state->mail_stream && (rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, rawno)) && mc->valid)) mc = NULL; if(!(env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i), &b)) || !bezerk_delimiter(env, mc, pc, next++) || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)), env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){ q_status_message(SM_ORDER | SM_DING, 3, 3, err = "Error writing tempfile for download"); break; } } gf_clear_so_writec(so); if(so_give(&so)){ /* close file */ if(!err) err = "Error writing tempfile for download"; } if(!err){ if((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_USER | PIPE_RESET, 0, pipe_callback, pipe_report_error)) != NULL) (void) close_system_pipe(&syspipe, NULL, pipe_callback); else q_status_message(SM_ORDER | SM_DING, 3, 3, err = _("Error running download command")); } } else q_status_message(SM_ORDER | SM_DING, 3, 3, err = "Error building temp file for download"); if(tfp){ our_unlink(tfp); fs_give((void **)&tfp); } if(!err) q_status_message(SM_ORDER, 0, 3, _("Download Command Completed")); goto fini; } #endif /* !(DOS || MAC) */ if(rflags & GER_APPEND) leading_nl = 1; else leading_nl = 0; dprint((5, "Opening file \"%s\" for export\n", full_filename ? full_filename : "?")); if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){ q_status_message2(SM_ORDER | SM_DING, 3, 4, /* TRANSLATORS: error opening file "" to export message: */ _("Error opening file \"%s\" to export message: %s"), full_filename, error_description(errno)); goto fini; } else gf_set_so_writec(&pc, store); err = NULL; for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){ env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i), &b); if(!env) { err = _("Can't export message. Error accessing mail folder"); failure = 1; break; } if(!(state->mail_stream && (rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, rawno)) && mc->valid)) mc = NULL; start_of_append = so_tell(store); if(!bezerk_delimiter(env, mc, pc, leading_nl) || !format_message(mn_m2raw(msgmap, i), env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){ orig_errno = errno; /* save in case things are really bad */ failure = 1; /* pop out of here */ break; } leading_nl = 1; } gf_clear_so_writec(store); if(so_give(&store)) /* release storage */ failure++; if(failure){ our_truncate(full_filename, (off_t)start_of_append); if(err){ dprint((1, "FAILED Export: fetch(%ld): %s\n", i, err ? err : "?")); q_status_message(SM_ORDER | SM_DING, 3, 4, err); } else{ dprint((1, "FAILED Export: file \"%s\" : %s\n", full_filename ? full_filename : "?", error_description(orig_errno))); q_status_message2(SM_ORDER | SM_DING, 3, 4, /* TRANSLATORS: Error exporting to : */ _("Error exporting to \"%s\" : %s"), filename, error_description(orig_errno)); } } else{ if(rflags & GER_ALLPARTS && full_filename[0]){ char dir[MAXPATH+1]; char lfile[MAXPATH+1]; int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2; ATTACH_S *a; /* * Now we want to save all of the attachments to a subdirectory. * To make it easier for us and probably easier for the user, and * to prevent the user from shooting himself in the foot, we * make a new subdirectory so that we can't possibly step on * any existing files, and we don't need any interaction with the * user while saving. * * We'll just use the directory name full_filename.d or if that * already exists and isn't empty, we'll try adding a suffix to * that until we get something to use. */ if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){ q_status_message1(SM_ORDER | SM_DING, 3, 4, _("Can't save attachments, filename too long: %s"), full_filename); goto fini; } ok = 0; snprintf(dir, sizeof(dir), "%s.d", full_filename); dir[sizeof(dir)-1] = '\0'; do { tries++; switch(r = is_writable_dir(dir)){ case 0: /* exists and is a writable dir */ /* * We could figure out if it is empty and use it in * that case, but that sounds like a lot of work, so * just fall through to default. */ default: if(strlen(full_filename) + strlen(".d") + 1 + 1 + strlen(long2string((long) tries)) > sizeof(dir)){ q_status_message(SM_ORDER | SM_DING, 3, 4, "Problem saving attachments"); goto fini; } snprintf(dir, sizeof(dir), "%s.d_%s", full_filename, long2string((long) tries)); dir[sizeof(dir)-1] = '\0'; break; case 3: /* doesn't exist, that's good! */ /* make new directory */ ok++; break; } } while(!ok && tries < 1000); if(tries >= 1000){ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Problem saving attachments")); goto fini; } /* create the new directory */ if(our_mkdir(dir, 0700)){ q_status_message2(SM_ORDER | SM_DING, 3, 4, _("Problem saving attachments: %s: %s"), dir, error_description(errno)); goto fini; } if(!(state->mail_stream && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L && rawno <= state->mail_stream->nmsgs && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b)) && b)){ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Problem reading message")); goto fini; } zero_atmts(state->atmts); describe_mime(b, "", 1, 1, 0, 0); a = state->atmts; if(a && a->description) /* skip main body part */ a++; for(; a->description != NULL; a++){ /* skip over these parts of the message */ if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a)) continue; lfile[0] = '\0'; (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL); if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */ snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6, a->number ? a->number : "?"); lfile[sizeof(lfile)-1] = '\0'; } if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1 > sizeof(filename)){ dprint((2, "FAILED Att Export: name too long: %s\n", dir, S_FILESEP, lfile)); errs++; continue; } /* although files are being saved in a unique directory, there is * no guarantee that attachment names have unique names, so we have * to make sure that we are not constantly rewriting the same file name * over and over. In order to avoid this we test if the file already exists, * and if so, we write a counter name in the file name, just before the * extension of the file, and separate it with an underscore. */ snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile); filename[sizeof(filename)-1] = '\0'; while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){ char *ext; snprintf(filename, sizeof(filename), "%d", counter); if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2 > sizeof(filename)){ dprint((2, "FAILED Att Export: name too long: %s\n", dir, S_FILESEP, lfile)); errs++; continue; } if((ext = strrchr(lfile, '.')) != NULL) *ext = '\0'; snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s", dir, S_FILESEP, lfile, ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : ""); filename[sizeof(filename)-1] = '\0'; } if(write_attachment_to_file(state->mail_stream, rawno, a, GER_NONE, filename) == 1) saved++; else errs++; } if(errs){ if(saved) q_status_message1(SM_ORDER, 3, 3, "Errors saving some attachments, %s attachments saved", long2string((long) saved)); else q_status_message(SM_ORDER, 3, 3, _("Problems saving attachments")); } else{ if(saved) q_status_message2(SM_ORDER, 0, 3, /* TRANSLATORS: Saved attachments to */ _("Saved %s attachments to %s"), long2string((long) saved), dir); else q_status_message(SM_ORDER, 3, 3, _("No attachments to save")); } } else if(mn_total_cur(msgmap) > 1L) q_status_message4(SM_ORDER,0,3, "%s message%s %s to file \"%s\"", long2string(count), plural(count), rflags & GER_OVER ? "overwritten" : rflags & GER_APPEND ? "appended" : "exported", filename); else q_status_message3(SM_ORDER,0,3, "Message %s %s to file \"%s\"", long2string(mn_get_cur(msgmap)), rflags & GER_OVER ? "overwritten" : rflags & GER_APPEND ? "appended" : "exported", filename); rv++; } fini: if(MCMD_ISAGG(aopt)) restore_selected(msgmap); return rv; } /* * Ask user what file to export to. Export from srcstore to that file. * * Args ps -- pine struct * srctext -- pointer to source text * srctype -- type of that source text * prompt_msg -- see get_export_filename * lister_msg -- " * * Returns: != 0 : error * 0 : ok */ int simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg) { int r = 1, rflags = GER_NONE; char filename[MAXPATH+1], full_filename[MAXPATH+1]; STORE_S *store = NULL; struct variable *vars = ps ? ps->vars : NULL; static HISTORY_S *history = NULL; static ESCKEY_S simple_export_opts[] = { {ctrl('T'), 10, "^T", N_("To Files")}, {-1, 0, NULL, NULL}, {-1, 0, NULL, NULL}}; if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){ simple_export_opts[r].ch = ctrl('I'); simple_export_opts[r].rval = 11; simple_export_opts[r].name = "TAB"; simple_export_opts[r].label = N_("Complete"); } if(!srctext){ q_status_message(SM_ORDER, 0, 2, _("Error allocating space")); r = -3; goto fini; } simple_export_opts[++r].ch = -1; filename[0] = '\0'; full_filename[0] = '\0'; r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename), prompt_msg, lister_msg, simple_export_opts, &rflags, -FOOTER_ROWS(ps), GE_IS_EXPORT, &history); if(r < 0) goto fini; else if(!full_filename[0]){ r = -1; goto fini; } dprint((5, "Opening file \"%s\" for export\n", full_filename ? full_filename : "?")); if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){ char *pipe_err; gf_io_t pc, gc; gf_set_so_writec(&pc, store); gf_set_readc(&gc, srctext, (srctype == CharStar) ? strlen((char *)srctext) : 0L, srctype, 0); gf_filter_init(); if((pipe_err = gf_pipe(gc, pc)) != NULL){ q_status_message2(SM_ORDER | SM_DING, 3, 3, /* TRANSLATORS: Problem saving to : */ _("Problem saving to \"%s\": %s"), filename, pipe_err); r = -3; } else r = 0; gf_clear_so_writec(store); if(so_give(&store)){ q_status_message2(SM_ORDER | SM_DING, 3, 3, _("Problem saving to \"%s\": %s"), filename, error_description(errno)); r = -3; } } else{ q_status_message2(SM_ORDER | SM_DING, 3, 4, _("Error opening file \"%s\" for export: %s"), full_filename, error_description(errno)); r = -3; } fini: switch(r){ case 0: /* overloading full_filename */ snprintf(full_filename, sizeof(full_filename), "%c%s", (prompt_msg && prompt_msg[0]) ? (islower((unsigned char)prompt_msg[0]) ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0]) : 'T', (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext"); full_filename[sizeof(full_filename)-1] = '\0'; q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"", full_filename, rflags & GER_OVER ? "overwritten" : rflags & GER_APPEND ? "appended" : "exported", filename); break; case -1: cmd_cancelled("Export"); break; case -2: q_status_message1(SM_ORDER, 0, 2, _("Can't export to file outside of %s"), VAR_OPER_DIR); break; } ps->mangled_footer = 1; return(r); } /* * Ask user what file to export to. * * filename -- On input, this is the filename to start with. On exit, * this is the filename chosen. (but this isn't used) * deefault -- This is the default value if user hits return. The * prompt will have [deefault] added to it automatically. * full_filename -- This is the full filename on exit. * len -- Minimum length of _both_ filename and full_filename. * prompt_msg -- Message to insert in prompt. * lister_msg -- Message to insert in file_lister. * opts -- Key options. * There is a tangled relationship between the callers * and this routine as far as opts are concerned. Some * of the opts are handled here. In particular, r == 3, * r == 10, r == 11, and r == 13 are all handled here. * Don't use those values unless you want what happens * here. r == 12 and others are handled by the caller. * rflags -- Return flags * GER_OVER - overwrite of existing file * GER_APPEND - append of existing file * else file did not exist before * * GER_ALLPARTS - AllParts toggle was turned on * * qline -- Command line to prompt on. * flags -- Logically OR'd flags * GE_IS_EXPORT - The command was an Export command * so the prompt should include * EXPORT:. * GE_SEQ_SENSITIVE - The command that got us here is * sensitive to sequence number changes * caused by unsolicited expunges. * GE_NO_APPEND - We will not allow append to an * existing file, only removal of the * file if it exists. * GE_IS_IMPORT - We are selecting for reading. * No overwriting or checking for * existence at all. Don't use this * together with GE_NO_APPEND. * GE_ALLPARTS - Turn on AllParts toggle. * GE_BINARY - Turn on Binary toggle. * * Returns: -1 cancelled * -2 prohibited by VAR_OPER_DIR * -3 other error, already reported here * 0 ok * 12 user chose 12 command from opts */ 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) { char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1]; char precolon[MAXPATH+1], postcolon[MAXPATH+1]; char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill; int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0; int allparts = 0, binary = 0; char prompt_buf[400]; char def[500]; ESCKEY_S *opts = NULL; struct variable *vars = ps->vars; static HISTORY_S *dir_hist = NULL; static char *last; int pos, hist_len = 0; /* we will fake a history with the ps_global->VAR_HISTORY variable * We fake that we combine this variable into a history variable * by stacking VAR_HISTORY on top of dir_hist. We keep track of this * by looking at the variable pos. */ if(ps_global->VAR_HISTORY != NULL) for(hist_len = 0; ps_global->VAR_HISTORY[hist_len] && ps_global->VAR_HISTORY[hist_len][0]; hist_len++) ; pos = hist_len + items_in_hist(dir_hist); if(flags & GE_ALLPARTS || history || dir_hist){ /* * Copy the opts and add one to the end of the list. */ for(i = 0; optsarg[i].ch != -1; i++) ; if(dir_hist || hist_len > 0) i += 2; if(history) i += dir_hist || hist_len > 0 ? 2 : 4; if(flags & GE_ALLPARTS) i++; if(flags & GE_BINARY) i++; opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts)); memset(opts, 0, (i+1) * sizeof(*opts)); for(i = 0; optsarg[i].ch != -1; i++){ opts[i].ch = optsarg[i].ch; opts[i].rval = optsarg[i].rval; opts[i].name = optsarg[i].name; /* no need to make a copy */ opts[i].label = optsarg[i].label; /* " */ } if(flags & GE_ALLPARTS){ allparts = i; opts[i].ch = ctrl('P'); opts[i].rval = 13; opts[i].name = "^P"; /* TRANSLATORS: Export all attachment parts */ opts[i++].label = N_("AllParts"); } if(flags & GE_BINARY){ binary = i; opts[i].ch = ctrl('R'); opts[i].rval = 15; opts[i].name = "^R"; opts[i++].label = N_("Binary"); } rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, filename); #ifndef _WINDOWS /* In the Windows operating system we always return the UTF8 encoded name */ if(strcmp(tmp_20k_buf, filename)){ opts[i].ch = ctrl('N'); opts[i].rval = 40; opts[i].name = "^N"; opts[i++].label = "Name UTF8"; } #else strncpy(filename, tmp_20k_buf, len); filename[len-1] = '\0'; #endif /* _WINDOWS */ if(dir_hist || hist_len > 0){ opts[i].ch = ctrl('Y'); opts[i].rval = 32; opts[i].name = ""; kp = i; opts[i++].label = ""; opts[i].ch = ctrl('V'); opts[i].rval = 33; opts[i].name = ""; opts[i++].label = ""; } if(history){ opts[i].ch = KEY_UP; opts[i].rval = 30; opts[i].name = ""; ku = i; opts[i++].label = ""; opts[i].ch = KEY_DOWN; opts[i].rval = 31; opts[i].name = ""; opts[i++].label = ""; } opts[i].ch = -1; if(history) init_hist(history, HISTSIZE); init_hist(&dir_hist, HISTSIZE); /* reset history to the end */ } else opts = optsarg; if(rflags) *rflags = GER_NONE; if(F_ON(F_USE_CURRENT_DIR, ps)) dir[0] = '\0'; else if(VAR_OPER_DIR){ strncpy(dir, VAR_OPER_DIR, sizeof(dir)); dir[sizeof(dir)-1] = '\0'; } #if defined(DOS) || defined(OS2) else if(VAR_FILE_DIR){ strncpy(dir, VAR_FILE_DIR, sizeof(dir)); dir[sizeof(dir)-1] = '\0'; } #endif else{ dir[0] = '~'; dir[1] = '\0'; homedir=1; } strncpy(orig_dir, dir, sizeof(orig_dir)); orig_dir[sizeof(orig_dir)-1] = '\0'; postcolon[0] = '\0'; strncpy(precolon, dir, sizeof(precolon)); precolon[sizeof(precolon)-1] = '\0'; if(deefault){ strncpy(def, deefault, sizeof(def)-1); def[sizeof(def)-1] = '\0'; removing_leading_and_trailing_white_space(def); } else def[0] = '\0'; avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH; /*---------- Prompt the user for the file name -------------*/ while(1){ int oeflags; char dirb[50], fileb[50]; int l1, l2, l3, l4, l5, needed; char *p, p1[100], p2[100], *p3, p4[100], p5[100]; snprintf(p1, sizeof(p1), "%sCopy ", (flags & GE_IS_EXPORT) ? "EXPORT: " : (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: "); p1[sizeof(p1)-1] = '\0'; l1 = strlen(p1); strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1); p2[sizeof(p2)-1] = '\0'; l2 = strlen(p2); if(rflags && *rflags & GER_ALLPARTS) p3 = " (and atts)"; else p3 = ""; l3 = strlen(p3); snprintf(p4, sizeof(p4), " %s file%s%s", (flags & GE_IS_IMPORT) ? "from" : "to", is_absolute_path(filename) ? "" : " in ", is_absolute_path(filename) ? "" : (!dir[0] ? "current directory" : (dir[0] == '~' && !dir[1]) ? "home directory" : short_str(dir,dirb,sizeof(dirb),30,FrontDots))); p4[sizeof(p4)-1] = '\0'; l4 = strlen(p4); snprintf(p5, sizeof(p5), "%s%s%s: ", *def ? " [" : "", *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "", *def ? "]" : ""); p5[sizeof(p5)-1] = '\0'; l5 = strlen(p5); if((needed = l1+l2+l3+l4+l5-avail) > 0){ snprintf(p4, sizeof(p4), " %s file%s%s", (flags & GE_IS_IMPORT) ? "from" : "to", is_absolute_path(filename) ? "" : " in ", is_absolute_path(filename) ? "" : (!dir[0] ? "current dir" : (dir[0] == '~' && !dir[1]) ? "home dir" : short_str(dir,dirb,sizeof(dirb),10,FrontDots))); p4[sizeof(p4)-1] = '\0'; l4 = strlen(p4); } if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){ snprintf(p5, sizeof(p5), "%s%s%s: ", *def ? " [" : "", *def ? short_str(def,fileb,sizeof(fileb), MAX(15,l5-5-needed),EndDots) : "", *def ? "]" : ""); p5[sizeof(p5)-1] = '\0'; l5 = strlen(p5); } if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){ /* * 14 is about the shortest we can make this, because there are * fixed length strings of length 14 coming in here. */ p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots); if(p != p2){ strncpy(p2, p, sizeof(p2)-1); p2[sizeof(p2)-1] = '\0'; } l2 = strlen(p2); } if((needed = l1+l2+l3+l4+l5-avail) > 0){ strncpy(p1, "Copy ", sizeof(p1)-1); p1[sizeof(p1)-1] = '\0'; l1 = strlen(p1); } if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){ snprintf(p5, sizeof(p5), "%s%s%s: ", *def ? " [" : "", *def ? short_str(def,fileb, sizeof(fileb), MAX(10,l5-5-needed),EndDots) : "", *def ? "]" : ""); p5[sizeof(p5)-1] = '\0'; l5 = strlen(p5); } if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){ if(needed <= l3 - strlen(" (+ atts)")) p3 = " (+ atts)"; else if(needed <= l3 - strlen(" (atts)")) p3 = " (atts)"; else if(needed <= l3 - strlen(" (+)")) p3 = " (+)"; else if(needed <= l3 - strlen("+")) p3 = "+"; else p3 = ""; l3 = strlen(p3); } snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5); prompt_buf[sizeof(prompt_buf)-1] = '\0'; if(kp >= 0){ if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */ opts[kp].name = "^Y"; opts[kp].label = "Prev Dir"; opts[kp+1].name = "^V"; opts[kp+1].label = "Next Dir"; } else{ opts[kp].name = ""; opts[kp].label = ""; opts[kp+1].name = ""; opts[kp+1].label = ""; } } if(ku >= 0){ if(items_in_hist(*history) > 0){ opts[ku].name = HISTORY_UP_KEYNAME; opts[ku].label = HISTORY_KEYLABEL; opts[ku+1].name = HISTORY_DOWN_KEYNAME; opts[ku+1].label = HISTORY_KEYLABEL; } else{ opts[ku].name = ""; opts[ku].label = ""; opts[ku+1].name = ""; opts[ku+1].label = ""; } } oeflags = OE_APPEND_CURRENT | ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0); r = optionally_enter(filename, qline, 0, len, prompt_buf, opts, NO_HELP, &oeflags); dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r)); /*--- Help ----*/ if(r == 3){ /* * Helps may not be right if you add another caller or change * things. Check it out. */ if(flags & GE_IS_IMPORT) helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE); else if(flags & GE_ALLPARTS) helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE); else helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE); ps->mangled_screen = 1; continue; } else if(r == 10 || r == 11){ /* Browser or File Completion */ if(filename[0]=='~'){ if(filename[1] == C_FILESEP && filename[2]!='\0'){ precolon[0] = '~'; precolon[1] = '\0'; for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++) filename[i] = filename[i+2]; filename[i] = '\0'; strncpy(dir, precolon, sizeof(dir)-1); dir[sizeof(dir)-1] = '\0'; } else if(filename[1]=='\0' || (filename[1] == C_FILESEP && filename[2] == '\0')){ precolon[0] = '~'; precolon[1] = '\0'; filename[0] = '\0'; strncpy(dir, precolon, sizeof(dir)-1); dir[sizeof(dir)-1] = '\0'; } } else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){ if(homedir){ precolon[0] = '~'; precolon[1] = '\0'; strncpy(dir, precolon, sizeof(dir)-1); dir[sizeof(dir)-1] = '\0'; } else{ precolon[0] = '\0'; dir[0] = '\0'; } } l = MAXPATH; dir2[0] = '\0'; strncpy(tmp, filename, sizeof(tmp)-1); tmp[sizeof(tmp)-1] = '\0'; if(*tmp && is_absolute_path(tmp)) fnexpand(tmp, sizeof(tmp)); if(strncmp(tmp,postcolon, strlen(postcolon))) postcolon[0] = '\0'; if(*tmp && (fn = last_cmpnt(tmp))){ l -= fn - tmp; strncpy(filename2, fn, sizeof(filename2)-1); filename2[sizeof(filename2)-1] = '\0'; if(is_absolute_path(tmp)){ strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1)); dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0'; #ifdef _WINDOWS if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){ dir2[2] = '\\'; dir2[3] = '\0'; } #endif strncpy(postcolon, dir2, sizeof(postcolon)-1); postcolon[sizeof(postcolon)-1] = '\0'; precolon[0] = '\0'; } else{ char *p = NULL; /* * Just building the directory name in dir2, * full_filename is overloaded. */ snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp); full_filename[len-1] = '\0'; strncpy(postcolon, full_filename, sizeof(postcolon)-1); postcolon[sizeof(postcolon)-1] = '\0'; build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH) : (dir[0] == '~' && !dir[1]) ? ps->home_dir : dir, full_filename, sizeof(dir2)); if(p) free(p); } } else{ if(is_absolute_path(tmp)){ strncpy(dir2, tmp, sizeof(dir2)-1); dir2[sizeof(dir2)-1] = '\0'; #ifdef _WINDOWS if(dir2[2]=='\0' && dir2[1]==':'){ dir2[2]='\\'; dir2[3]='\0'; strncpy(postcolon,dir2,sizeof(postcolon)-1); postcolon[sizeof(postcolon)-1] = '\0'; } #endif filename2[0] = '\0'; precolon[0] = '\0'; } else{ strncpy(filename2, tmp, sizeof(filename2)-1); filename2[sizeof(filename2)-1] = '\0'; if(!dir[0]){ if(getcwd(dir2, sizeof(dir2)) == NULL) alpine_panic(_("getcwd() call failed at get_export_filename")); } else if(dir[0] == '~' && !dir[1]){ strncpy(dir2, ps->home_dir, sizeof(dir2)-1); dir2[sizeof(dir2)-1] = '\0'; } else{ strncpy(dir2, dir, sizeof(dir2)-1); dir2[sizeof(dir2)-1] = '\0'; } postcolon[0] = '\0'; } } build_path(full_filename, dir2, filename2, len); if(!strcmp(full_filename, dir2)) filename2[0] = '\0'; if(full_filename[strlen(full_filename)-1] == C_FILESEP && isdir(full_filename,NULL,NULL)){ if(strlen(full_filename) == 1) strncpy(postcolon, full_filename, sizeof(postcolon)-1); else if(filename2[0]) strncpy(postcolon, filename2, sizeof(postcolon)-1); postcolon[sizeof(postcolon)-1] = '\0'; strncpy(dir2, full_filename, sizeof(dir2)-1); dir2[sizeof(dir2)-1] = '\0'; filename2[0] = '\0'; } #ifdef _WINDOWS /* use full_filename even if not a valid directory */ else if(full_filename[strlen(full_filename)-1] == C_FILESEP){ strncpy(postcolon, filename2, sizeof(postcolon)-1); postcolon[sizeof(postcolon)-1] = '\0'; strncpy(dir2, full_filename, sizeof(dir2)-1); dir2[sizeof(dir2)-1] = '\0'; filename2[0] = '\0'; } #endif if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1 && strcmp(dir2+1, ":\\")) /* last condition to prevent stripping of '\\' in windows partition */ dir2[strlen(dir2)-1] = '\0'; if(r == 10){ /* File Browser */ r = file_lister(lister_msg ? lister_msg : "EXPORT", dir2, sizeof(dir2), filename2, sizeof(filename2), TRUE, (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE); #ifdef _WINDOWS /* Windows has a special "feature" in which entering the file browser will change the working directory if the directory is changed at all (even clicking "Cancel" will change the working directory). */ if(F_ON(F_USE_CURRENT_DIR, ps)) (void)getcwd(dir2,sizeof(dir2)); #endif if(isdir(dir2,NULL,NULL)){ strncpy(precolon, dir2, sizeof(precolon)-1); precolon[sizeof(precolon)-1] = '\0'; } strncpy(postcolon, filename2, sizeof(postcolon)-1); postcolon[sizeof(postcolon)-1] = '\0'; if(r == 1){ build_path(full_filename, dir2, filename2, len); if(isdir(full_filename, NULL, NULL)){ strncpy(dir, full_filename, sizeof(dir)-1); dir[sizeof(dir)-1] = '\0'; filename[0] = '\0'; } else{ fn = last_cmpnt(full_filename); strncpy(dir, full_filename, MIN(fn - full_filename, sizeof(dir)-1)); dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0'; if(fn - full_filename > 1) dir[fn - full_filename - 1] = '\0'; } if(!strcmp(dir, ps->home_dir)){ dir[0] = '~'; dir[1] = '\0'; } strncpy(filename, fn, len-1); filename[len-1] = '\0'; } } else{ /* File Completion */ if(!pico_fncomplete(dir2, filename2, sizeof(filename2))) Writechar(BELL, 0); strncat(postcolon, filename2, sizeof(postcolon)-1-strlen(postcolon)); postcolon[sizeof(postcolon)-1] = '\0'; was_abs_path = is_absolute_path(filename); if(!strcmp(dir, ps->home_dir)){ dir[0] = '~'; dir[1] = '\0'; } } strncpy(filename, postcolon, len-1); filename[len-1] = '\0'; strncpy(dir, precolon, sizeof(dir)-1); dir[sizeof(dir)-1] = '\0'; if(filename[0] == '~' && !filename[1]){ dir[0] = '~'; dir[1] = '\0'; filename[0] = '\0'; } continue; } else if(r == 12){ /* Download, caller handles it */ ret = r; goto done; } else if(r == 13){ /* toggle AllParts bit */ if(rflags){ if(*rflags & GER_ALLPARTS){ *rflags &= ~GER_ALLPARTS; opts[allparts].label = N_("AllParts"); } else{ *rflags |= GER_ALLPARTS; /* opposite of All Parts, No All Parts */ opts[allparts].label = N_("NoAllParts"); } } continue; } #if 0 else if(r == 14){ /* List file names matching partial? */ continue; } #endif else if(r == 15){ /* toggle Binary bit */ if(rflags){ if(*rflags & GER_BINARY){ *rflags &= ~GER_BINARY; opts[binary].label = N_("Binary"); } else{ *rflags |= GER_BINARY; opts[binary].label = N_("No Binary"); } } continue; } else if(r == 1){ /* Cancel */ ret = -1; goto done; } else if(r == 4){ continue; } else if(r >= 30 && r <= 33){ char *p = NULL; if(r == 30 || r == 31){ if(history){ if(r == 30) p = get_prev_hist(*history, filename, 0, NULL); else if (r == 31) p = get_next_hist(*history, filename, 0, NULL); } } if(r == 32 || r == 33){ int nitems = items_in_hist(dir_hist); if(dir_hist || hist_len > 0){ if(r == 32){ if(pos > 0) p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems); else p = last; } else if (r == 33){ if(pos < hist_len + nitems) p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems); } if(p == NULL || *p == '\0') p = orig_dir; } } last = p; /* save it! */ if(p != NULL && *p != '\0'){ if(r == 30 || r == 31){ if((fn = last_cmpnt(p)) != NULL){ strncpy(dir, p, MIN(fn - p, sizeof(dir)-1)); dir[MIN(fn - p, sizeof(dir)-1)] = '\0'; if(fn - p > 1) dir[fn - p - 1] = '\0'; strncpy(filename, fn, len-1); filename[len-1] = '\0'; } } else { /* r == 32 || r == 33 */ strncpy(dir, p, sizeof(dir)-1); dir[sizeof(dir)-1] = '\0'; } if(!strcmp(dir, ps->home_dir)){ dir[0] = '~'; dir[1] = '\0'; } } else Writechar(BELL, 0); continue; } #ifndef _WINDOWS else if(r == 40){ rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, filename); strncpy(filename, tmp_20k_buf, len); filename[len-1] = '\0'; continue; } #endif /* _WINDOWS */ else if(r != 0){ Writechar(BELL, 0); continue; } removing_leading_and_trailing_white_space(filename); if(!*filename){ if(!*def){ /* Cancel */ ret = -1; goto done; } strncpy(filename, def, len-1); filename[len-1] = '\0'; } #if defined(DOS) || defined(OS2) if(is_absolute_path(filename)){ fixpath(filename, len); } #else if(filename[0] == '~'){ if(fnexpand(filename, len) == NULL){ char *p = strindex(filename, '/'); if(p != NULL) *p = '\0'; q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error expanding file name: \"%s\" unknown user"), filename); continue; } } #endif if(is_absolute_path(filename)){ strncpy(full_filename, filename, len-1); full_filename[len-1] = '\0'; } else{ if(!dir[0]) build_path(full_filename, (char *)getcwd(dir,sizeof(dir)), filename, len); else if(dir[0] == '~' && !dir[1]) build_path(full_filename, ps->home_dir, filename, len); else build_path(full_filename, dir, filename, len); } if((ill = filter_filename(full_filename, &fatal, ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){ if(fatal){ q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill); continue; } else{ /* BUG: we should beep when the key's pressed rather than bitch later */ /* Warn and ask for confirmation. */ snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway", ill, (flags & GE_IS_EXPORT) ? "Export" : "Save"); prompt_buf[sizeof(prompt_buf)-1] = '\0'; if(want_to(prompt_buf, 'n', 0, NO_HELP, ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y') continue; } } break; /* Must have got an OK file name */ } if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){ ret = -2; goto done; } if(!can_access(full_filename, ACCESS_EXISTS)){ int rbflags; static ESCKEY_S access_opts[] = { /* TRANSLATORS: asking user if they want to overwrite (replace contents of) a file or append to the end of the file */ {'o', 'o', "O", N_("Overwrite")}, {'a', 'a', "A", N_("Append")}, {-1, 0, NULL, NULL}}; rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0); if(flags & GE_NO_APPEND){ r = strlen(filename); snprintf(prompt_buf, sizeof(prompt_buf), /* TRANSLATORS: asking user whether to overwrite a file or not, File already exists. Overwrite it ? */ _("File \"%s%s\" already exists. Overwrite it "), (r > 20) ? "..." : "", filename + ((r > 20) ? r - 20 : 0)); prompt_buf[sizeof(prompt_buf)-1] = '\0'; if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){ if(rflags) *rflags |= GER_OVER; if(our_unlink(full_filename) < 0){ q_status_message2(SM_ORDER | SM_DING, 3, 5, /* TRANSLATORS: Cannot remove old : */ _("Cannot remove old %s: %s"), full_filename, error_description(errno)); } } else{ ret = -1; goto done; } } else if(!(flags & GE_IS_IMPORT)){ r = strlen(filename); snprintf(prompt_buf, sizeof(prompt_buf), /* TRANSLATORS: File already exists. Overwrite or append to it ? */ _("File \"%s%s\" already exists. Overwrite or append to it ? "), (r > 20) ? "..." : "", filename + ((r > 20) ? r - 20 : 0)); prompt_buf[sizeof(prompt_buf)-1] = '\0'; switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global), access_opts, 'a', 'x', NO_HELP, rbflags)){ case 'o' : if(rflags) *rflags |= GER_OVER; if(our_truncate(full_filename, (off_t)0) < 0) /* trouble truncating, but we'll give it a try anyway */ q_status_message2(SM_ORDER | SM_DING, 3, 5, /* TRANSLATORS: Warning: Cannot truncate old : */ _("Warning: Cannot truncate old %s: %s"), full_filename, error_description(errno)); break; case 'a' : if(rflags) *rflags |= GER_APPEND; break; case 'x' : default : ret = -1; goto done; } } } done: if(history && ret == 0){ save_hist(*history, full_filename, 0, NULL); strncpy(tmp, full_filename, MAXPATH); tmp[MAXPATH] = '\0'; if((fn = strrchr(tmp, C_FILESEP)) != NULL) *fn = '\0'; else tmp[0] = '\0'; if(tmp[0]) save_hist(dir_hist, tmp, 0, NULL); } if(opts && opts != optsarg) fs_give((void **) &opts); return(ret); } /*---------------------------------------------------------------------- parse the config'd upload/download command Args: cmd -- buffer to return command fit for shellin' prefix -- cfg_str -- fname -- file name to build into the command Returns: pointer to cmd_str buffer or NULL on real bad error NOTE: One SIDE EFFECT is that any defined "prefix" string in the cfg_str is written to standard out right before a successful return of this function. The call immediately following this function darn well better be the shell exec... ----*/ char * build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname) { char *p; int fname_found = 0; if(prefix && *prefix){ /* loop thru replacing all occurrences of _FILE_ */ p = strncpy(cmd, prefix, cmdlen); cmd[cmdlen-1] = '\0'; while((p = strstr(p, "_FILE_"))) rplstr(p, cmdlen-(p-cmd), 6, fname); fputs(cmd, stdout); } /* loop thru replacing all occurrences of _FILE_ */ p = strncpy(cmd, cfg_str, cmdlen); cmd[cmdlen-1] = '\0'; while((p = strstr(p, "_FILE_"))){ rplstr(p, cmdlen-(p-cmd), 6, fname); fname_found = 1; } if(!fname_found) snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname); cmd[cmdlen-1] = '\0'; dprint((4, "\n - build_updown_cmd = \"%s\" -\n", cmd ? cmd : "?")); return(cmd); } /*---------------------------------------------------------------------- Write a berzerk format message delimiter using the given putc function Args: e -- envelope of message to write pc -- function to use Returns: TRUE if we could write it, FALSE if there was a problem NOTE: follows delimiter with OS-dependent newline ----*/ int bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline) { MESSAGECACHE telt; time_t when; char *p; /* write "[\n]From mailbox[@host] " */ if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1) && gf_puts("From ", pc) && gf_puts((env && env->from) ? env->from->mailbox : "the-concourse-on-high", pc) && gf_puts((env && env->from && env->from->host) ? "@" : "", pc) && gf_puts((env && env->from && env->from->host) ? env->from->host : "", pc) && (*pc)(' '))) return(0); if(mc && mc->valid) when = mail_longdate(mc); else if(env && env->date && env->date[0] && mail_parse_date(&telt,env->date)) when = mail_longdate(&telt); else when = time(0); p = ctime(&when); while(p && *p && *p != '\n') /* write date */ if(!(*pc)(*p++)) return(0); if(!gf_puts(NEWLINE, pc)) /* write terminating newline */ return(0); return(1); } /*---------------------------------------------------------------------- Execute command to jump to a given message number Args: qline -- Line to ask question on Result: returns true if the use selected a new message, false otherwise ----*/ long jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index) { char jump_num_string[80], *j, prompt[70]; HelpType help; int rc; static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL}, /* TRANSLATORS: go to First Message */ {ctrl('Y'), 10, "^Y", N_("First Msg")}, {ctrl('V'), 11, "^V", N_("Last Msg")}, {-1, 0, NULL, NULL} }; dprint((4, "\n - jump_to -\n")); #ifdef DEBUG if(sparms && sparms->jump_is_debug) return(get_level(qline, first_num, sparms)); #endif if(!any_messages(msgmap, NULL, "to Jump to")) return(0L); if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){ jump_num_string[0] = first_num; jump_num_string[1] = '\0'; } else jump_num_string[0] = '\0'; if(mn_total_cur(msgmap) > 1L){ snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered", comatose(mn_total_cur(msgmap))); prompt[sizeof(prompt)-1] = '\0'; if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n') return(0L); } snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx ? "Thread" : "Message"); prompt[sizeof(prompt)-1] = '\0'; help = NO_HELP; while(1){ int flags = OE_APPEND_CURRENT; rc = optionally_enter(jump_num_string, qline, 0, sizeof(jump_num_string), prompt, jump_to_key, help, &flags); if(rc == 3){ help = help == NO_HELP ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump) : NO_HELP; continue; } else if(rc == 10 || rc == 11){ char warning[100]; long closest; closest = closest_jump_target(rc == 10 ? 1L : ((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap)), ps_global->mail_stream, msgmap, 0, in_index, warning, sizeof(warning)); /* ignore warning */ return(closest); } /* * If we take out the *jump_num_string nonempty test in this if * then the closest_jump_target routine will offer a jump to the * last message. However, it is slow because you have to wait for * the status message and it is annoying for people who hit J command * by mistake and just want to hit return to do nothing, like has * always worked. So the test is there for now. Hubert 2002-08-19 * * Jumping to first/last message is now possible through ^Y/^V * commands above. jpf 2002-08-21 * (and through "end" hubert 2006-07-07) */ if(rc == 0 && *jump_num_string != '\0'){ removing_leading_and_trailing_white_space(jump_num_string); for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++) ; if(*j != '\0'){ if(!strucmp("end", j)) return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap)); q_status_message(SM_ORDER | SM_DING, 2, 2, _("Invalid number entered. Use only digits 0-9")); jump_num_string[0] = '\0'; } else{ char warning[100]; long closest, jump_num; if(*jump_num_string) jump_num = atol(jump_num_string); else jump_num = -1L; warning[0] = '\0'; closest = closest_jump_target(jump_num, ps_global->mail_stream, msgmap, *jump_num_string ? 0 : 1, in_index, warning, sizeof(warning)); if(warning[0]) q_status_message(SM_ORDER | SM_DING, 2, 2, warning); if(closest == jump_num) return(jump_num); if(closest == 0L) jump_num_string[0] = '\0'; else strncpy(jump_num_string, long2string(closest), sizeof(jump_num_string)); } continue; } if(rc != 4) break; } return(0L); } /* * cmd_delete_action - handle msgno advance and such after single message deletion */ char * cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index) { int opts; long msgno; char *rv = NULL; msgno = mn_get_cur(msgmap); advance_cur_after_delete(state, state->mail_stream, msgmap, in_index); if(IS_NEWS(state->mail_stream) || ((state->context_current->use & CNTXT_INCMNG) && context_isambig(state->cur_folder))){ opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID); if(in_index == View) opts &= ~NSF_SKIP_CHID; (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts); if(!(opts & NSF_FLAG_MATCH)){ char nextfolder[MAXPATH]; strncpy(nextfolder, state->cur_folder, sizeof(nextfolder)); nextfolder[sizeof(nextfolder)-1] = '\0'; rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder, state->context_current, NULL, NULL) ? ". Press TAB for next folder." : ". No more folders to TAB to."; } } return(rv); } /* * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing */ char * cmd_delete_index(struct pine *state, MSGNO_S *msgmap) { return(cmd_delete_action(state, msgmap,MsgIndx)); } /* * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing */ char * cmd_delete_view(struct pine *state, MSGNO_S *msgmap) { return(cmd_delete_action(state, msgmap, View)); } void advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index) { long new_msgno, msgno; int opts; new_msgno = msgno = mn_get_cur(msgmap); opts = NSF_TRUST_FLAGS; if(F_ON(F_DEL_SKIPS_DEL, state)){ if(THREADING() && sp_viewing_a_thread(stream)) opts |= NSF_SKIP_CHID; new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts); } else{ mn_inc_cur(stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); new_msgno = mn_get_cur(msgmap); if(new_msgno != msgno) opts |= NSF_FLAG_MATCH; } /* * Viewing_a_thread is the complicated case because we want to ignore * other threads at first and then look in other threads if we have to. * By ignoring other threads we also ignore collapsed partial threads * in our own thread. */ if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){ long rawno, orig_thrdno; PINETHRD_S *thrd, *topthrd = NULL; rawno = mn_m2raw(msgmap, msgno); thrd = fetch_thread(stream, rawno); if(thrd && thrd->top) topthrd = fetch_thread(stream, thrd->top); orig_thrdno = topthrd ? topthrd->thrdno : -1L; opts = NSF_TRUST_FLAGS; new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts); /* * If we got a match, new_msgno may be a message in * a different thread from the one we are viewing, or it could be * in a collapsed part of this thread. */ if(opts & NSF_FLAG_MATCH){ int ret; char pmt[128]; topthrd = NULL; thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno)); if(thrd && thrd->top) topthrd = fetch_thread(stream, thrd->top); /* * If this match is in the same thread we're already in * then we're done, else we have to ask the user and maybe * switch threads. */ if(!(orig_thrdno > 0L && topthrd && topthrd->thrdno == orig_thrdno)){ if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){ if(in_index == View) snprintf(pmt, sizeof(pmt), "View message in thread number %.10s", topthrd ? comatose(topthrd->thrdno) : "?"); else snprintf(pmt, sizeof(pmt), "View thread number %.10s", topthrd ? comatose(topthrd->thrdno) : "?"); ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM); } else ret = 'y'; if(ret == 'y'){ unview_thread(state, stream, msgmap); mn_set_cur(msgmap, new_msgno); if(THRD_AUTO_VIEW() && (count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE) == 1) && view_thread(state, stream, msgmap, 1)){ if(current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; state->view_skipped_index = 1; state->next_screen = mail_view_screen; } else{ view_thread(state, stream, msgmap, 1); if(current_index_state) msgmap->top_after_thrd = current_index_state->msg_at_top; state->next_screen = SCREEN_FUN_NULL; } } else new_msgno = msgno; /* stick with original */ } } } mn_set_cur(msgmap, new_msgno); if(in_index != View) adjust_cur_to_visible(stream, msgmap); } #ifdef DEBUG long get_level(int qline, UCS first_num, SCROLL_S *sparms) { char debug_num_string[80], *j, prompt[70]; HelpType help; int rc; long debug_num; if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){ debug_num_string[0] = first_num; debug_num_string[1] = '\0'; debug_num = atol(debug_num_string); *(int *)(sparms->proc.data.p) = debug_num; q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s", comatose(debug_num)); return(1L); } else debug_num_string[0] = '\0'; snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9)); prompt[sizeof(prompt)-1] = '\0'; help = NO_HELP; while(1){ int flags = OE_APPEND_CURRENT; rc = optionally_enter(debug_num_string, qline, 0, sizeof(debug_num_string), prompt, NULL, help, &flags); if(rc == 3){ help = help == NO_HELP ? h_oe_debuglevel : NO_HELP; continue; } if(rc == 0){ removing_leading_and_trailing_white_space(debug_num_string); for(j=debug_num_string; isdigit((unsigned char)*j); j++) ; if(*j != '\0'){ q_status_message(SM_ORDER | SM_DING, 2, 2, _("Invalid number entered. Use only digits 0-9")); debug_num_string[0] = '\0'; } else{ debug_num = atol(debug_num_string); if(debug_num < 0) q_status_message(SM_ORDER | SM_DING, 2, 2, _("Number should be >= 0")); else if(debug_num > MAX(debug,9)) q_status_message1(SM_ORDER | SM_DING, 2, 2, _("Maximum is %s"), comatose(MAX(debug,9))); else{ *(int *)(sparms->proc.data.p) = debug_num; q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s", comatose(debug_num)); return(1L); } } continue; } if(rc != 4) break; } return(0L); } #endif /* DEBUG */ /* * Returns the message number closest to target that isn't hidden. * Make warning at least 100 chars. * A return of 0 means there is no message to jump to. */ long closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen) { long i, start, closest = 0L; char buf[80]; long maxnum; warning[0] = '\0'; maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap); if(no_target){ target = maxnum; start = 1L; snprintf(warning, warninglen, "No %s number entered, jump to end? ", (in_index == ThrdIndx) ? "thread" : "message"); warning[warninglen-1] = '\0'; } else if(target < 1L) start = 1L - target; else if(target > maxnum) start = target - maxnum; else start = 1L; if(target > 0L && target <= maxnum) if(in_index == ThrdIndx || !msgline_hidden(stream, msgmap, target, 0)) return(target); for(i = start; target+i <= maxnum || target-i > 0L; i++){ if(target+i > 0L && target+i <= maxnum && (in_index == ThrdIndx || !msgline_hidden(stream, msgmap, target+i, 0))){ closest = target+i; break; } if(target-i > 0L && target-i <= maxnum && (in_index == ThrdIndx || !msgline_hidden(stream, msgmap, target-i, 0))){ closest = target-i; break; } } strncpy(buf, long2string(closest), sizeof(buf)); buf[sizeof(buf)-1] = '\0'; if(closest == 0L) strncpy(warning, "Nothing to jump to", warninglen); else if(target < 1L) snprintf(warning, warninglen, "%s number (%s) must be at least %s", (in_index == ThrdIndx) ? "Thread" : "Message", long2string(target), buf); else if(target > maxnum) snprintf(warning, warninglen, "%s number (%s) may be no more than %s", (in_index == ThrdIndx) ? "Thread" : "Message", long2string(target), buf); else if(!no_target) snprintf(warning, warninglen, "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)", long2string(target), buf); warning[warninglen-1] = '\0'; return(closest); } /*---------------------------------------------------------------------- Prompt for folder name to open, expand the name and return it Args: qline -- Screen line to prompt on allow_list -- if 1, allow ^T to bring up collection lister Result: returns the folder name or NULL pine structure mangled_footer flag is set may call the collection lister in which case mangled screen will be set This prompts the user for the folder to open, possibly calling up the collection lister if the user types ^T. ----------------------------------------------------------------------*/ char * broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context) { HelpType help; static char newfolder[MAILTMPLEN]; char expanded[MAXPATH+1], prompt[MAX_SCREEN_COLS+1], *last_folder, *p; unsigned char *f1, *f2, *f3; static HISTORY_S *history = NULL; CONTEXT_S *tc, *tc2; ESCKEY_S ekey[9]; int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0; /* * the idea is to provide a clue for the context the file name * will be saved in (if a non-imap names is typed), and to * only show the previous if it was also in the same context */ help = NO_HELP; *expanded = '\0'; *newfolder = '\0'; last_folder = NULL; if(notrealinbox) (*notrealinbox) = 1; init_hist(&history, HISTSIZE); tc = broach_get_folder(context ? *context : NULL, &inbox, NULL); /* set up extra command option keys */ rc = 0; ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ; ekey[rc].rval = (allow_list) ? 2 : 0; ekey[rc].name = (allow_list) ? "^T" : ""; ekey[rc++].label = (allow_list) ? N_("ToFldrs") : ""; if(ps_global->context_list->next){ ekey[rc].ch = ctrl('P'); ekey[rc].rval = 10; ekey[rc].name = "^P"; ekey[rc++].label = N_("Prev Collection"); ekey[rc].ch = ctrl('N'); ekey[rc].rval = 11; ekey[rc].name = "^N"; ekey[rc++].label = N_("Next Collection"); } ekey[rc].ch = ctrl('W'); ekey[rc].rval = 17; ekey[rc].name = "^W"; ekey[rc++].label = N_("INBOX"); if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ ekey[rc].ch = TAB; ekey[rc].rval = 12; ekey[rc].name = "TAB"; ekey[rc++].label = N_("Complete"); } if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){ ekey[rc].ch = ctrl('X'); ekey[rc].rval = 14; ekey[rc].name = "^X"; ekey[rc++].label = N_("ListMatches"); } if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ ekey[rc].ch = KEY_UP; ekey[rc].rval = 10; ekey[rc].name = ""; ekey[rc++].label = ""; ekey[rc].ch = KEY_DOWN; ekey[rc].rval = 11; ekey[rc].name = ""; ekey[rc++].label = ""; } else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ ekey[rc].ch = KEY_UP; ekey[rc].rval = 30; ekey[rc].name = ""; ku = rc; ekey[rc++].label = ""; ekey[rc].ch = KEY_DOWN; ekey[rc].rval = 31; ekey[rc].name = ""; ekey[rc++].label = ""; } ekey[rc].ch = -1; while(!done) { /* * Figure out next default value for this context. The idea * is that in each context the last folder opened is cached. * It's up to pick it out and display it. This is fine * and dandy if we've currently got the inbox open, BUT * if not, make the inbox the default the first time thru. */ if(!inbox){ last_folder = ps_global->inbox_name; inbox = 1; /* pretend we're in inbox from here on out */ } else last_folder = (ps_global->last_unambig_folder[0]) ? ps_global->last_unambig_folder : ((tc->last_folder[0]) ? tc->last_folder : NULL); if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */ unsigned char *fname = folder_name_decoded((unsigned char *)last_folder); snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5, fname ? (char *) fname : last_folder); if(fname) fs_give((void **)&fname); } else *expanded = '\0'; expanded[sizeof(expanded)-1] = '\0'; /* only show collection number if more than one available */ if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ", NEWS_TEST(tc) ? "news group" : "folder", tc->nickname, MAX_SCREEN_COLS+1-50, expanded, *expanded ? " " : ""); else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded, *expanded ? " " : ""); prompt[sizeof(prompt)-1] = '\0'; if(utf8_width(prompt) > MAXPROMPT){ if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ", tc->nickname, MAX_SCREEN_COLS+1-50, expanded, *expanded ? " " : ""); else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded, *expanded ? " " : ""); prompt[sizeof(prompt)-1] = '\0'; if(utf8_width(prompt) > MAXPROMPT){ if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ", tc->nickname, MAX_SCREEN_COLS+1-50, expanded, *expanded ? " " : ""); else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */ snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded, *expanded ? " " : ""); prompt[sizeof(prompt)-1] = '\0'; } } if(ku >= 0){ if(items_in_hist(history) > 1){ ekey[ku].name = HISTORY_UP_KEYNAME; ekey[ku].label = HISTORY_KEYLABEL; ekey[ku+1].name = HISTORY_DOWN_KEYNAME; ekey[ku+1].label = HISTORY_KEYLABEL; } else{ ekey[ku].name = ""; ekey[ku].label = ""; ekey[ku+1].name = ""; ekey[ku+1].label = ""; } } /* is there any other way to do this? The point is that we * are trying to hide mutf7 from the user, and use the utf8 * equivalent. So we create a variable f to take place of * newfolder, including content and size. f2 is copy of f1 * that has to freed. Sigh! */ f3 = (unsigned char *) cpystr(newfolder); f1 = fs_get(sizeof(newfolder)); f2 = folder_name_decoded(f3); if(f3) fs_give((void **)&f3); strncpy((char *)f1, (char *)f2, sizeof(newfolder)); f1[sizeof(newfolder)-1] = '\0'; if(f2) fs_give((void **)&f2); flags = OE_APPEND_CURRENT; rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder), (char *) prompt, ekey, help, &flags); f2 = folder_name_encoded(f1); strncpy(newfolder, (char *)f2, sizeof(newfolder)); if(f1) fs_give((void **)&f1); if(f2) fs_give((void **)&f2); ps_global->mangled_footer = 1; switch(rc){ case -1 : /* o_e says error! */ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error reading folder name")); return(NULL); case 0 : /* o_e says normal entry */ removing_trailing_white_space(newfolder); removing_leading_white_space(newfolder); if(*newfolder){ char *name, *fullname = NULL; int exists, breakout = 0; save_hist(history, newfolder, 0, tc); if(!(name = folder_is_nick(newfolder, FOLDERS(tc), FN_WHOLE_NAME))) name = newfolder; if(update_folder_spec(expanded, sizeof(expanded), name)){ strncpy(name = newfolder, expanded, sizeof(newfolder)); newfolder[sizeof(newfolder)-1] = '\0'; } exists = folder_name_exists(tc, name, &fullname); if(fullname){ strncpy(name = newfolder, fullname, sizeof(newfolder)); newfolder[sizeof(newfolder)-1] = '\0'; fs_give((void **) &fullname); breakout = TRUE; } /* * if we know the things a folder, open it. * else if we know its a directory, visit it. * else we're not sure (it either doesn't really * exist or its unLISTable) so try opening it anyway */ if(exists & FEX_ISFILE){ done++; break; } else if((exists & FEX_ISDIR)){ if(breakout){ CONTEXT_S *fake_context; char tmp[MAILTMPLEN]; size_t l; strncpy(tmp, name, sizeof(tmp)); tmp[sizeof(tmp)-2-1] = '\0'; if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){ if(l < sizeof(tmp)){ tmp[l] = tc->dir->delim; strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1)); } } else strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1); tmp[sizeof(tmp)-1] = '\0'; fake_context = new_context(tmp, 0); newfolder[0] = '\0'; done = display_folder_list(&fake_context, newfolder, 1, folders_for_goto); free_context(&fake_context); break; } else if(!(tc->use & CNTXT_INCMNG)){ done = display_folder_list(&tc, newfolder, 1, folders_for_goto); break; } } else if((exists & FEX_ERROR)){ q_status_message1(SM_ORDER, 0, 3, _("Problem accessing folder \"%s\""), newfolder); return(NULL); } else{ done++; break; } if(exists == FEX_ERROR) q_status_message1(SM_ORDER, 0, 3, _("Problem accessing folder \"%s\""), newfolder); else if(tc->use & CNTXT_INCMNG) q_status_message1(SM_ORDER, 0, 3, _("Can't find Incoming Folder: %s"), newfolder); else if(context_isambig(newfolder)) q_status_message2(SM_ORDER, 0, 3, _("Can't find folder \"%s\" in %s"), newfolder, (void *) tc->nickname); else q_status_message1(SM_ORDER, 0, 3, _("Can't find folder \"%s\""), newfolder); return(NULL); } else if(last_folder){ if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX && !strucmp(last_folder, ps_global->inbox_name) && tc == ((ps_global->context_list->use & CNTXT_INCMNG) ? ps_global->context_list->next : ps_global->context_list)){ if(notrealinbox) (*notrealinbox) = 0; tc = ps_global->context_list; } strncpy(newfolder, last_folder, sizeof(newfolder)); newfolder[sizeof(newfolder)-1] = '\0'; save_hist(history, newfolder, 0, tc); done++; break; } /* fall thru like they cancelled */ case 1 : /* o_e says user cancel */ cmd_cancelled("Open folder"); return(NULL); case 2 : /* o_e says user wants list */ r = display_folder_list(&tc, newfolder, 0, folders_for_goto); if(r) done++; break; case 3 : /* o_e says user wants help */ help = help == NO_HELP ? h_oe_broach : NO_HELP; break; case 4 : /* redraw */ break; case 10 : /* Previous collection */ tc2 = ps_global->context_list; while(tc2->next && tc2->next != tc) tc2 = tc2->next; tc = tc2; break; case 11 : /* Next collection */ tc = (tc->next) ? tc->next : ps_global->context_list; break; case 12 : /* file name completion */ if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){ if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){ r = display_folder_list(&tc, newfolder, 1,folders_for_goto); if(r) done++; /* bingo! */ else rc = 0; /* burn last_rc */ } else Writechar(BELL, 0); } break; case 14 : /* file name completion */ r = display_folder_list(&tc, newfolder, 2, folders_for_goto); if(r) done++; /* bingo! */ else rc = 0; /* burn last_rc */ break; case 17 : /* GoTo INBOX */ done++; strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1); newfolder[sizeof(newfolder)-1] = '\0'; if(notrealinbox) (*notrealinbox) = 0; tc = ps_global->context_list; save_hist(history, newfolder, 0, tc); break; case 30 : if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){ strncpy(newfolder, p, sizeof(newfolder)); newfolder[sizeof(newfolder)-1] = '\0'; if(history->hist[history->curindex]) tc = history->hist[history->curindex]->cntxt; } else Writechar(BELL, 0); break; case 31 : if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){ strncpy(newfolder, p, sizeof(newfolder)); newfolder[sizeof(newfolder)-1] = '\0'; if(history->hist[history->curindex]) tc = history->hist[history->curindex]->cntxt; } else Writechar(BELL, 0); break; default : alpine_panic("Unhandled case"); break; } last_rc = rc; } dprint((2, "broach folder, name entered \"%s\"\n", newfolder ? newfolder : "?")); /*-- Just check that we can expand this. It gets done for real later --*/ strncpy(expanded, newfolder, sizeof(expanded)); expanded[sizeof(expanded)-1] = '\0'; if(!expand_foldername(expanded, sizeof(expanded))) { dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n", expanded ? expanded : "?")); return(NULL); } *context = tc; return(newfolder); } /*---------------------------------------------------------------------- Check to see if user wants to reopen dead stream. Args: ps -- reopenp -- Result: 1 if the folder was successfully updatedn 0 if not necessary ----*/ int ask_mailbox_reopen(struct pine *ps, int *reopenp) { if(((ps->mail_stream->dtb && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL) || (ps->mail_stream->rdonly && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY))) && (ps->reopen_rule == REOPEN_ASK_ASK_Y || ps->reopen_rule == REOPEN_ASK_ASK_N || ps->reopen_rule == REOPEN_ASK_NO_Y || ps->reopen_rule == REOPEN_ASK_NO_N)) || ((ps->mail_stream->dtb && ps->mail_stream->rdonly && !(ps->mail_stream->dtb->flags & DR_LOCAL)) && (ps->reopen_rule == REOPEN_YES_ASK_Y || ps->reopen_rule == REOPEN_YES_ASK_N || ps->reopen_rule == REOPEN_ASK_ASK_Y || ps->reopen_rule == REOPEN_ASK_ASK_N))){ int deefault; switch(ps->reopen_rule){ case REOPEN_YES_ASK_Y: case REOPEN_ASK_ASK_Y: case REOPEN_ASK_NO_Y: deefault = 'y'; break; default: deefault = 'n'; break; } switch(want_to("Re-open folder to check for new messages", deefault, 'x', h_reopen_folder, WT_NORM)){ case 'y': (*reopenp)++; break; case 'x': return(-1); } } return(0); } /*---------------------------------------------------------------------- Check to see if user input is in form of old c-client mailbox speck Args: old -- new -- Result: 1 if the folder was successfully updatedn 0 if not necessary ----*/ int update_folder_spec(char *new, size_t newlen, char *old) { char *p, *orignew; int nntp = 0; orignew = new; if(*(p = old) == '*') /* old form? */ old++; if(*old == '{') /* copy host spec */ do switch(*new = *old++){ case '\0' : return(FALSE); case '/' : if(!struncmp(old, "nntp", 4)) nntp++; break; default : break; } while(*new++ != '}' && (new-orignew) < newlen-1); if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){ /* * OK, some heuristics here. If it looks like a newsgroup * then we plunk it into the #news namespace else we * assume that they're trying to get at a #public folder... */ for(p = old; *p && (isalnum((unsigned char) *p) || strindex(".-", *p)); p++) ; sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew)); strncpy(new, old, newlen-(new-orignew)); return(TRUE); } orignew[newlen-1] = '\0'; return(FALSE); } /*---------------------------------------------------------------------- Open the requested folder in the requested context Args: state -- usual pine state struct newfolder -- folder to open new_context -- folder context might live in stream -- candidate for recycling Result: New folder open or not (if error), and we're set to enter the index screen. ----*/ void visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context, MAILSTREAM *stream, long unsigned int flags) { dprint((9, "visit_folder(%s, %s)\n", newfolder ? newfolder : "?", (new_context && new_context->context) ? new_context->context : "(NULL)")); if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL, flags) >= 0 || !sp_flagged(state->mail_stream, SP_LOCKED)) state->next_screen = mail_index_screen; else state->next_screen = folder_screen; } /*---------------------------------------------------------------------- Move read messages from folder if listed in archive Args: ----*/ int read_msg_prompt(long int n, char *f) { char buf[MAX_SCREEN_COLS+1]; snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f); buf[sizeof(buf)-1] = '\0'; return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y'); } /*---------------------------------------------------------------------- Print current message[s] or folder index Args: state -- pointer to struct holding a bunch of pine state msgmap -- table mapping msg nums to c-client sequence nums aopt -- aggregate options in_index -- boolean indicating we're called from Index Screen Filters the original header and sends stuff to printer ---*/ int cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index) { char prompt[250]; long i, msgs, rawno; int next = 0, do_index = 0, rv = 0; ENVELOPE *e; BODY *b; MESSAGECACHE *mc; if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) return rv; msgs = mn_total_cur(msgmap); if((in_index != View) && F_ON(F_PRINT_INDEX, state)){ char m[10]; int ans; static ESCKEY_S prt_opts[] = { {'i', 'i', "I", N_("Index")}, {'m', 'm', "M", NULL}, {-1, 0, NULL, NULL}}; if(in_index == ThrdIndx){ /* TRANSLATORS: This is a question, Print Index ? */ if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y') ans = 'i'; else ans = 'x'; } else{ snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : ""); m[sizeof(m)-1] = '\0'; prt_opts[1].label = m; snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ", (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "", (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m); prompt[sizeof(prompt)-1] = '\0'; ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x', NO_HELP, RB_NORM|RB_SEQ_SENSITIVE); } switch(ans){ case 'x' : cmd_cancelled("Print"); if(MCMD_ISAGG(aopt)) restore_selected(msgmap); return rv; case 'i': do_index = 1; break; default : case 'm': break; } } if(do_index) snprintf(prompt, sizeof(prompt), "%sFolder Index", (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : ""); else if(msgs > 1L) snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs)); else snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap))); prompt[sizeof(prompt)-1] = '\0'; if(open_printer(prompt) < 0){ if(MCMD_ISAGG(aopt)) restore_selected(msgmap); return rv; } if(do_index){ TITLE_S *tc; tc = format_titlebar(); /* Print titlebar... */ print_text1("%s\n\n", tc ? tc->titlebar_line : ""); /* then all the index members... */ if(!print_index(state, msgmap, MCMD_ISAGG(aopt))) q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error printing folder index")); else rv++; } else{ rv++; for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){ if(next && F_ON(F_AGG_PRINT_FF, state)) if(!print_char(FORMFEED)){ rv = 0; break; } if(!(state->mail_stream && (rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, rawno)) && mc->valid)) mc = NULL; if(!(e=pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap,i), &b)) || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global) && !bezerk_delimiter(e, mc, print_char, next)) || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)), e, b, NULL, FM_NEW_MESS | FM_NOINDENT, print_char)){ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error printing message")); rv = 0; break; } } } close_printer(); if(MCMD_ISAGG(aopt)) restore_selected(msgmap); return rv; } /*---------------------------------------------------------------------- Pipe message text Args: state -- various pine state bits msgmap -- Message number mapping table aopt -- option flags Filters the original header and sends stuff to specified command ---*/ int cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt) { ENVELOPE *e; MESSAGECACHE *mc; BODY *b; PIPE_S *syspipe; char *resultfilename = NULL, prompt[80], *p; int done = 0, rv = 0; gf_io_t pc; int fourlabel = -1, j = 0, next = 0, ku; int pipe_rv; /* rv of proc to separate from close_system_pipe rv */ long i, rawno; unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */ static HISTORY_S *history = NULL; int capture = 1, raw = 0, delimit = 0, newpipe = 0; char pipe_command[MAXPATH]; ESCKEY_S pipe_opt[8]; if(ps_global->restricted){ q_status_message(SM_ORDER | SM_DING, 0, 4, "Alpine demo can't pipe messages"); return rv; } else if(!any_messages(msgmap, NULL, "to Pipe")) return rv; pipe_command[0] = '\0'; init_hist(&history, HISTSIZE); flagsforhist = (raw ? 0x8 : 0) + (delimit ? 0x4 : 0) + (newpipe ? 0x2 : 0) + (capture ? 0x1 : 0); if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){ strncpy(pipe_command, p, sizeof(pipe_command)); pipe_command[sizeof(pipe_command)-1] = '\0'; if(history->hist[history->curindex]){ flagsforhist = history->hist[history->curindex]->flags; raw = (flagsforhist & 0x8) ? 1 : 0; delimit = (flagsforhist & 0x4) ? 1 : 0; newpipe = (flagsforhist & 0x2) ? 1 : 0; capture = (flagsforhist & 0x1) ? 1 : 0; } } pipe_opt[j].ch = 0; pipe_opt[j].rval = 0; pipe_opt[j].name = ""; pipe_opt[j++].label = ""; pipe_opt[j].ch = ctrl('W'); pipe_opt[j].rval = 10; pipe_opt[j].name = "^W"; pipe_opt[j++].label = NULL; pipe_opt[j].ch = ctrl('Y'); pipe_opt[j].rval = 11; pipe_opt[j].name = "^Y"; pipe_opt[j++].label = NULL; pipe_opt[j].ch = ctrl('R'); pipe_opt[j].rval = 12; pipe_opt[j].name = "^R"; pipe_opt[j++].label = NULL; if(MCMD_ISAGG(aopt)){ if(!pseudo_selected(state->mail_stream, msgmap)) return rv; else{ fourlabel = j; pipe_opt[j].ch = ctrl('T'); pipe_opt[j].rval = 13; pipe_opt[j].name = "^T"; pipe_opt[j++].label = NULL; } } pipe_opt[j].ch = KEY_UP; pipe_opt[j].rval = 30; pipe_opt[j].name = ""; ku = j; pipe_opt[j++].label = ""; pipe_opt[j].ch = KEY_DOWN; pipe_opt[j].rval = 31; pipe_opt[j].name = ""; pipe_opt[j++].label = ""; pipe_opt[j].ch = -1; while (!done) { int flags; snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ", raw ? "RAW " : "", MCMD_ISAGG(aopt) ? "s" : " ", MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)), (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "", capture ? "" : "uncaptured", (!capture && delimit) ? "," : "", delimit ? "delimited" : "", ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "", (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "", (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : ""); prompt[sizeof(prompt)-1] = '\0'; pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text"); pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output"); pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter"); if(fourlabel > 0) pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes"); /* * 2 is really 1 because there will be one real entry and * one entry of "" because of the get_prev_hist above. */ if(items_in_hist(history) > 2){ pipe_opt[ku].name = HISTORY_UP_KEYNAME; pipe_opt[ku].label = HISTORY_KEYLABEL; pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME; pipe_opt[ku+1].label = HISTORY_KEYLABEL; } else{ pipe_opt[ku].name = ""; pipe_opt[ku].label = ""; pipe_opt[ku+1].name = ""; pipe_opt[ku+1].label = ""; } flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE; switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0, sizeof(pipe_command), prompt, pipe_opt, NO_HELP, &flags)){ case -1 : q_status_message(SM_ORDER | SM_DING, 3, 4, _("Internal problem encountered")); done++; break; case 10 : /* flip raw bit */ raw = !raw; break; case 11 : /* flip capture bit */ capture = !capture; break; case 12 : /* flip delimit bit */ delimit = !delimit; break; case 13 : /* flip newpipe bit */ newpipe = !newpipe; break; case 30 : flagsforhist = (raw ? 0x8 : 0) + (delimit ? 0x4 : 0) + (newpipe ? 0x2 : 0) + (capture ? 0x1 : 0); if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){ strncpy(pipe_command, p, sizeof(pipe_command)); pipe_command[sizeof(pipe_command)-1] = '\0'; if(history->hist[history->curindex]){ flagsforhist = history->hist[history->curindex]->flags; raw = (flagsforhist & 0x8) ? 1 : 0; delimit = (flagsforhist & 0x4) ? 1 : 0; newpipe = (flagsforhist & 0x2) ? 1 : 0; capture = (flagsforhist & 0x1) ? 1 : 0; } } else Writechar(BELL, 0); break; case 31 : flagsforhist = (raw ? 0x8 : 0) + (delimit ? 0x4 : 0) + (newpipe ? 0x2 : 0) + (capture ? 0x1 : 0); if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){ strncpy(pipe_command, p, sizeof(pipe_command)); pipe_command[sizeof(pipe_command)-1] = '\0'; if(history->hist[history->curindex]){ flagsforhist = history->hist[history->curindex]->flags; raw = (flagsforhist & 0x8) ? 1 : 0; delimit = (flagsforhist & 0x4) ? 1 : 0; newpipe = (flagsforhist & 0x2) ? 1 : 0; capture = (flagsforhist & 0x1) ? 1 : 0; } } else Writechar(BELL, 0); break; case 0 : if(pipe_command[0]){ flagsforhist = (raw ? 0x8 : 0) + (delimit ? 0x4 : 0) + (newpipe ? 0x2 : 0) + (capture ? 0x1 : 0); save_hist(history, pipe_command, flagsforhist, NULL); flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR; flags |= (raw ? PIPE_RAW : 0); if(!capture){ #ifndef _WINDOWS ClearScreen(); fflush(stdout); clear_cursor_pos(); ps_global->mangled_screen = 1; ps_global->in_init_seq = 1; #endif flags |= PIPE_RESET; } if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command, (flags & PIPE_RESET) ? NULL : &resultfilename, flags, &pc))) done++; for(i = mn_first_cur(msgmap); i > 0L && !done; i = mn_next_cur(msgmap)){ e = pine_mail_fetchstructure(ps_global->mail_stream, mn_m2raw(msgmap, i), &b); if(!(state->mail_stream && (rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, rawno)) && mc->valid)) mc = NULL; if((newpipe && !(syspipe = cmd_pipe_open(pipe_command, (flags & PIPE_RESET) ? NULL : &resultfilename, flags, &pc))) || (delimit && !bezerk_delimiter(e, mc, pc, next++))) done++; if(!done){ if(raw){ char *pipe_err; prime_raw_pipe_getc(ps_global->mail_stream, mn_m2raw(msgmap, i), -1L, 0L); gf_filter_init(); gf_link_filter(gf_nvtnl_local, NULL); if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){ q_status_message1(SM_ORDER|SM_DING, 3, 3, _("Internal Error: %s"), pipe_err); done++; } } else if(!format_message(mn_m2raw(msgmap, i), e, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)) done++; } if(newpipe) if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1) done++; } if(!capture) ps_global->in_init_seq = 0; if(!newpipe) if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1) done++; if(done) /* say we had a problem */ q_status_message(SM_ORDER | SM_DING, 3, 3, _("Error piping message")); else if(resultfilename){ rv++; /* only display if no error */ display_output_file(resultfilename, "PIPE MESSAGE", NULL, DOF_EMPTY); fs_give((void **)&resultfilename); } else{ rv++; q_status_message(SM_ORDER, 0, 2, _("Pipe command completed")); } done++; break; } /* else fall thru as if cancelled */ case 1 : cmd_cancelled("Pipe command"); done++; break; case 3 : helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE); ps_global->mangled_screen = 1; break; case 2 : /* no place to escape to */ case 4 : /* can't suspend */ default : break; } } ps_global->mangled_footer = 1; if(MCMD_ISAGG(aopt)) restore_selected(msgmap); return rv; } /*---------------------------------------------------------------------- Screen to offer list management commands contained in message Args: state -- pointer to struct holding a bunch of pine state msgmap -- table mapping msg nums to c-client sequence nums aopt -- aggregate options Result: NOTE: Inspired by contrib from Jeremy Blackman ----*/ void rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno) { int winner = 0; char *h, *hdrs[MLCMD_COUNT + 1]; long index_no = mn_raw2m(msgmap, msgno); RFC2369_S data[MLCMD_COUNT]; /* for each header field */ if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){ memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT); if(rfc2369_parse_fields(h, &data[0])){ STORE_S *explain; if((explain = list_mgmt_text(data, index_no)) != NULL){ list_mgmt_screen(explain); ps_global->mangled_screen = 1; so_give(&explain); winner++; } } fs_give((void **) &h); } if(!winner) q_status_message1(SM_ORDER, 0, 3, "Message %s contains no list management information", comatose(index_no)); } STORE_S * list_mgmt_text(RFC2369_S *data, long int msgno) { STORE_S *store; int i, j, n, fields = 0; static char *rfc2369_intro1 = "

Mail List Commands

Message "; static char *rfc2369_intro2[] = { N_(" has information associated with it "), N_("that explains how to participate in an email list. An "), N_("email list is represented by a single email address that "), N_("users sharing a common interest can send messages to (known "), N_("as posting) which are then redistributed to all members "), N_("of the list (sometimes after review by a moderator)."), N_("

List participation commands in this message include:"), NULL }; if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){ /* Insert introductory text */ so_puts(store, rfc2369_intro1); so_puts(store, comatose(msgno)); for(i = 0; rfc2369_intro2[i]; i++) so_puts(store, _(rfc2369_intro2[i])); so_puts(store, "

"); for(i = 0; i < MLCMD_COUNT; i++) if(data[i].data[0].value || data[i].data[0].comment || data[i].data[0].error){ if(!fields++) so_puts(store, "

    "); so_puts(store, "
  • "); so_puts(store, (n = (data[i].data[1].value || data[i].data[1].comment)) ? "Methods to " : "A method to "); so_puts(store, data[i].field.description); so_puts(store, ". "); if(n) so_puts(store, "
      "); for(j = 0; j < MLCMD_MAXDATA && (data[i].data[j].comment || data[i].data[j].value || data[i].data[j].error); j++){ so_puts(store, n ? "

    1. " : "

      "); if(data[i].data[j].comment){ so_puts(store, _("With the provided comment:

      ")); so_puts(store, data[i].data[j].comment); so_puts(store, "

      "); } if(data[i].data[j].value){ if(i == MLCMD_POST && !strucmp(data[i].data[j].value, "NO")){ so_puts(store, _("Posting is not allowed on this list")); } else{ so_puts(store, "Select HERE to "); so_puts(store, (data[i].field.action) ? data[i].field.action : "try it"); } so_puts(store, "."); } if(data[i].data[j].error){ so_puts(store, "

      Unfortunately, Alpine can not offer"); so_puts(store, " to take direct action based upon it"); so_puts(store, " because it was improperly formatted."); so_puts(store, " The unrecognized data associated with"); so_puts(store, " the \""); so_puts(store, data[i].field.name); so_puts(store, "\" header field was:

      "); so_puts(store, data[i].data[j].error); so_puts(store, "
      "); } so_puts(store, "

      "); } if(n) so_puts(store, "

    "); } if(fields) so_puts(store, "
"); so_puts(store, ""); } return(store); } void list_mgmt_screen(STORE_S *html) { int cmd = MC_NONE; long offset = 0L; char *error = NULL; STORE_S *store; HANDLE_S *handles = NULL; gf_io_t gc, pc; do{ so_seek(html, 0L, 0); gf_set_so_readc(&gc, html); init_handles(&handles); if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){ gf_set_so_writec(&pc, store); gf_filter_init(); gf_link_filter(gf_html2plain, gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols, non_messageview_margin(), &handles, NULL, 0)); error = gf_pipe(gc, pc); gf_clear_so_writec(store); if(!error){ SCROLL_S sargs; memset(&sargs, 0, sizeof(SCROLL_S)); sargs.text.text = so_text(store); sargs.text.src = CharStar; sargs.text.desc = "list commands"; sargs.text.handles = handles; if(offset){ sargs.start.on = Offset; sargs.start.loc.offset = offset; } sargs.bar.title = _("MAIL LIST COMMANDS"); sargs.bar.style = MessageNumber; sargs.resize_exit = 1; sargs.help.text = h_special_list_commands; sargs.help.title = _("HELP FOR LIST COMMANDS"); sargs.keys.menu = &listmgr_keymenu; setbitmap(sargs.keys.bitmap); if(!handles){ clrbitn(LM_TRY_KEY, sargs.keys.bitmap); clrbitn(LM_PREV_KEY, sargs.keys.bitmap); clrbitn(LM_NEXT_KEY, sargs.keys.bitmap); } cmd = scrolltool(&sargs); offset = sargs.start.loc.offset; } so_give(&store); } free_handles(&handles); gf_clear_so_readc(html); } while(cmd == MC_RESIZE); } /*---------------------------------------------------------------------- Prompt the user for the type of select desired NOTE: any and all functions that successfully exit the second switch() statement below (currently "select_*() functions"), *MUST* update the folder's MESSAGECACHE element's "searched" bits to reflect the search result. Functions using mail_search() get this for free, the others must update 'em by hand. Returns -1 if canceled without changing selection 0 if selection may have changed ----*/ int aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index) { long i, diff, old_tot, msgno, raw; int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1; ESCKEY_S *sel_opts; MESSAGECACHE *mc; SEARCHSET *limitsrch = NULL; PINETHRD_S *thrd; extern MAILSTREAM *mm_search_stream; extern long mm_search_count; hidden = any_lflagged(msgmap, MN_HIDE) > 0L; mm_search_stream = state->mail_stream; mm_search_count = 0L; if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)) sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5; else sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2; if(THREADING()){ sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH; } else{ if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){ sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH; sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval; sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name; sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label; sel_opts[SEL_OPTS_XGMEXT].ch = -1; } else sel_opts[SEL_OPTS_THREAD].ch = -1; } if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){ if(THRD_INDX()){ i = 0; thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); /* check if whole thread is selected or not */ if(thrd && count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT) == count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE)) i = 1; sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd"); } else{ i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap), MN_SLCT); sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur"); } sel_opts += 2; /* disable extra options */ if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)) q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP, RB_NORM); else q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP, RB_NORM); switch(q){ case 'f' : /* flip selection */ msgno = 0L; for(i = 1L; i <= mn_get_total(msgmap); i++){ ret = 0; q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT); set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q); if(hidden){ set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q); if(!msgno && q) mn_reset_cur(msgmap, msgno = i); } } return(ret); case 'n' : /* narrow selection */ narrow++; q = 0; break; case 'r' : /* replace selection */ p = 1; /* set flag we want to replace */ sel_opts -= 2; /* re-enable first two options */ case 'b' : /* broaden selection */ q = 0; /* offer criteria prompt */ break; case 'c' : /* Un/Select Current */ case 'a' : /* Unselect All */ case 'x' : /* cancel */ break; default : q_status_message(SM_ORDER | SM_DING, 3, 3, "Unsupported Select option"); return(ret); } } if(!q){ while(1){ if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)) q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP, RB_NORM); else q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP, RB_NORM|RB_RET_HELP); if(q == 3){ helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE); ps_global->mangled_screen = 1; } else break; } } /* When we are replacing the search, unselect all messages first, unless * we are cancelling, in whose case, leave the screen as is, because we * are cancelling! */ if(p == 1 && q != 'x'){ msgno = any_lflagged(msgmap, MN_SLCT); diff = (!msgno) ? mn_get_total(msgmap) : 0L; agg_select_all(state->mail_stream, msgmap, &diff, any_lflagged(msgmap, MN_SLCT) <= 0L); } /* * The purpose of this is to add the appropriate searchset to the * search so that the search can be limited to only looking at what * it needs to look at. That is, if we are narrowing then we only need * to look at messages which are already selected, and if we are * broadening, then we only need to look at messages which are not * yet selected. This routine will work whether or not * limiting_searchset properly limits the search set. In particular, * the searchset returned by limiting_searchset may include messages * which really shouldn't be included. We do that because a too-large * searchset will break some IMAP servers. It is even possible that it * becomes inefficient to send the whole set. If the select function * frees limitsrch, it should be sure to set it to NULL so we won't * try freeing it again here. */ limitsrch = limiting_searchset(state->mail_stream, narrow); /* * NOTE: See note about MESSAGECACHE "searched" bits above! */ switch(q){ case 'x': /* cancel */ cmd_cancelled("Select command"); return(ret); case 'c' : /* select/unselect current */ (void) select_by_current(state, msgmap, in_index); ret = 0; return(ret); case 'a' : /* select/unselect all */ msgno = any_lflagged(msgmap, MN_SLCT); diff = (!msgno) ? mn_get_total(msgmap) : 0L; ret = 0; agg_select_all(state->mail_stream, msgmap, &diff, any_lflagged(msgmap, MN_SLCT) <= 0L); q_status_message4(SM_ORDER,0,2, "%s%s message%s %sselected", msgno ? "" : "All ", comatose(diff), plural(diff), msgno ? "UN" : ""); return(ret); case 'n' : /* Select by Number */ ret = 0; if(THRD_INDX()) rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch); else rv = select_by_number(state->mail_stream, msgmap, &limitsrch); break; case 'd' : /* Select by Date */ ret = 0; rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch); break; case 't' : /* Text */ ret = 0; rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch); break; case 'z' : /* Size */ ret = 0; rv = select_by_size(state->mail_stream, &limitsrch); break; case 's' : /* Status */ ret = 0; rv = select_by_status(state->mail_stream, &limitsrch); break; case 'k' : /* Keyword */ ret = 0; rv = select_by_keyword(state->mail_stream, &limitsrch); break; case 'r' : /* Rule */ ret = 0; rv = select_by_rule(state->mail_stream, &limitsrch); break; case 'h' : /* Thread */ ret = 0; rv = select_by_thread(state->mail_stream, msgmap, &limitsrch); break; case 'g' : /* X-GM-EXT-1 */ ret = 0; rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch); break; default : q_status_message(SM_ORDER | SM_DING, 3, 3, "Unsupported Select option"); return(ret); } if(limitsrch) mail_free_searchset(&limitsrch); if(rv) /* bad return value.. */ return(ret); /* error already displayed */ if(narrow) /* make sure something was selected */ for(i = 1L; i <= mn_get_total(msgmap); i++) if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream && raw <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){ if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)) break; else mm_search_count--; } diff = 0L; if(mm_search_count){ /* * loop thru all the messages, adjusting local flag bits * based on their "searched" bit... */ for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++) if(narrow){ /* turning OFF selectedness if the "searched" bit isn't lit. */ if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){ if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream && raw <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, raw)) && !mc->searched){ diff--; set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0); if(hidden) set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1); } /* adjust current message in case we unselect and hide it */ else if(msgno < mn_get_cur(msgmap) && (!THRD_INDX() || !get_lflag(state->mail_stream, msgmap, i, MN_CHID))) msgno = i; } } else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream && raw <= state->mail_stream->nmsgs && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){ /* turn ON selectedness if "searched" bit is lit. */ if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){ diff++; set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1); if(hidden) set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0); } } /* if we're zoomed and the current message was unselected */ if(narrow && msgno && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE)) mn_reset_cur(msgmap, msgno); } if(!diff){ if(narrow) q_status_message4(SM_ORDER, 3, 3, "%s. %s message%s remain%s selected.", mm_search_count ? "No change resulted" : "No messages in intersection", comatose(old_tot), plural(old_tot), (old_tot == 1L) ? "s" : ""); else if(old_tot) q_status_message(SM_ORDER, 3, 3, _("No change resulted. Matching messages already selected.")); else q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Select failed. No %smessages selected."), old_tot ? _("additional ") : ""); } else if(old_tot){ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Select matched %ld message%s. %s %smessage%s %sselected.", (diff > 0) ? diff : old_tot + diff, plural((diff > 0) ? diff : old_tot + diff), comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff), (diff > 0) ? "total " : "", plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff), (diff > 0) ? "" : "UN"); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; q_status_message(SM_ORDER, 3, 3, tmp_20k_buf); } else q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"), comatose(diff), plural(diff)); return(ret); } /*---------------------------------------------------------------------- Toggle the state of the current message Args: state -- pointer pine's state variables msgmap -- message collection to operate on in_index -- in the message index view Returns: TRUE if current marked selected, FALSE otw ----*/ int select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index) { long cur; int all_selected = 0; unsigned long was, tot, rawno; PINETHRD_S *thrd; cur = mn_get_cur(msgmap); if(THRD_INDX()){ thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur)); if(!thrd) return 0; was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT); tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE); if(was == tot) all_selected++; if(all_selected){ set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0); if(any_lflagged(msgmap, MN_HIDE) > 0L){ set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1); /* * See if there's anything left to zoom on. If so, * pick an adjacent one for highlighting, else make * sure nothing is left hidden... */ if(any_lflagged(msgmap, MN_SLCT)){ mn_inc_cur(state->mail_stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(state->mail_stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); if(mn_get_cur(msgmap) == cur) mn_dec_cur(state->mail_stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(state->mail_stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); } else /* clear all hidden flags */ (void) unzoom_index(state, state->mail_stream, msgmap); } } else set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1); q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected", comatose(all_selected ? was : tot-was), plural(all_selected ? was : tot-was), all_selected ? "UN" : ""); } /* collapsed thread */ else if(THREADING() && ((rawno = mn_m2raw(msgmap, cur)) != 0L) && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL) && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){ /* * This doesn't work quite the same as the colon command works, but * it is arguably doing the correct thing. The difference is * that aggregate_select will zoom after selecting back where it * was called from, but selecting a thread with colon won't zoom. * Maybe it makes sense to zoom after a select but not after a colon * command even though they are very similar. */ thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state)); } else{ if((all_selected = get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */ set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0); if(any_lflagged(msgmap, MN_HIDE) > 0L){ set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1); /* * See if there's anything left to zoom on. If so, * pick an adjacent one for highlighting, else make * sure nothing is left hidden... */ if(any_lflagged(msgmap, MN_SLCT)){ mn_inc_cur(state->mail_stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(state->mail_stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); if(mn_get_cur(msgmap) == cur) mn_dec_cur(state->mail_stream, msgmap, (in_index == View && THREADING() && sp_viewing_a_thread(state->mail_stream)) ? MH_THISTHD : (in_index == View) ? MH_ANYTHD : MH_NONE); } else /* clear all hidden flags */ (void) unzoom_index(state, state->mail_stream, msgmap); } } else set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1); q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected", long2string(cur), all_selected ? "UN" : ""); } return(!all_selected); } /*---------------------------------------------------------------------- Prompt the user for the command to perform on selected messages Args: state -- pointer pine's state variables msgmap -- message collection to operate on q_line -- line on display to write prompts Returns: 1 if the selected messages are suitably commanded, 0 if the choice to pick the command was declined ----*/ int apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, UCS preloadkeystroke, int flags, int q_line) { int i = 8, /* number of static entries in sel_opts3 */ rv = 0, cmd, we_cancel = 0, agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG; char prompt[80]; PAT_STATE pstate; /* * To do this "right", we really ought to have access to the keymenu * here and change the typed command into a real command by running * it through menu_command. Then the switch below would be against * results from menu_command. If we did that we'd also pass the * results of menu_command in as preloadkeystroke instead of passing * the keystroke itself. But we don't have the keymenu handy, * so we have to fake it. The only complication that we run into * is that KEY_DEL is an escape sequence so we change a typed * KEY_DEL esc seq into the letter D. */ if(!preloadkeystroke){ if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */ sel_opts3[i].ch = '*'; sel_opts3[i].rval = '*'; sel_opts3[i].name = "*"; sel_opts3[i++].label = N_("Flag"); } if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */ sel_opts3[i].ch = '|'; sel_opts3[i].rval = '|'; sel_opts3[i].name = "|"; sel_opts3[i++].label = N_("Pipe"); } if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */ sel_opts3[i].ch = 'b'; sel_opts3[i].rval = 'b'; sel_opts3[i].name = "B"; sel_opts3[i++].label = N_("Bounce"); } if(flags & AC_FROM_THREAD){ if(flags & (AC_COLL | AC_EXPN)){ sel_opts3[i].ch = '/'; sel_opts3[i].rval = '/'; sel_opts3[i].name = "/"; sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse") : N_("Expand"); } sel_opts3[i].ch = ';'; sel_opts3[i].rval = ';'; sel_opts3[i].name = ";"; if(flags & AC_UNSEL) sel_opts3[i++].label = N_("UnSelect"); else sel_opts3[i++].label = N_("Select"); } if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */ sel_opts3[i].ch = 'y'; sel_opts3[i].rval = '%'; sel_opts3[i].name = ""; sel_opts3[i++].label = ""; } if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */ sel_opts3[i].ch = 'x'; sel_opts3[i].rval = 'x'; sel_opts3[i].name = "X"; sel_opts3[i++].label = N_("Expunge"); } if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){ sel_opts3[i].ch = '#'; sel_opts3[i].rval = '#'; sel_opts3[i].name = "#"; sel_opts3[i++].label = N_("Set Role"); } sel_opts3[i].ch = KEY_DEL; /* also invisible */ sel_opts3[i].rval = 'd'; sel_opts3[i].name = ""; sel_opts3[i++].label = ""; sel_opts3[i].ch = -1; snprintf(prompt, sizeof(prompt), "%s command : ", (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY"); prompt[sizeof(prompt)-1] = '\0'; cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP, RB_SEQ_SENSITIVE); if(isupper(cmd)) cmd = tolower(cmd); } else{ if(preloadkeystroke == KEY_DEL) cmd = 'd'; else{ if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke)) cmd = tolower((int) preloadkeystroke); else cmd = (int) preloadkeystroke; /* shouldn't happen */ } } switch(cmd){ case 'd' : /* delete */ we_cancel = busy_cue(NULL, NULL, 1); rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */ if(we_cancel) cancel_busy_cue(0); break; case 'u' : /* undelete */ we_cancel = busy_cue(NULL, NULL, 1); rv = cmd_undelete(state, msgmap, agg); if(we_cancel) cancel_busy_cue(0); break; case 'r' : /* reply */ rv = cmd_reply(state, msgmap, agg, NULL); break; case 'f' : /* Forward */ rv = cmd_forward(state, msgmap, agg, NULL); break; case '%' : /* print */ rv = cmd_print(state, msgmap, agg, MsgIndx); break; case 't' : /* take address */ rv = cmd_take_addr(state, msgmap, agg); break; case 's' : /* save */ rv = cmd_save(state, stream, msgmap, agg, MsgIndx); break; case 'e' : /* export */ rv = cmd_export(state, msgmap, q_line, agg); break; case '|' : /* pipe */ rv = cmd_pipe(state, msgmap, agg); break; case '*' : /* flag */ we_cancel = busy_cue(NULL, NULL, 1); rv = cmd_flag(state, msgmap, agg); if(we_cancel) cancel_busy_cue(0); break; case 'b' : /* bounce */ rv = cmd_bounce(state, msgmap, agg, NULL); break; case '/' : collapse_or_expand(state, stream, msgmap, F_ON(F_SLASH_COLL_ENTIRE, ps_global) ? 0L : mn_get_cur(msgmap)); break; case ':' : select_thread_stmp(state, stream, msgmap); break; case 'x' : /* Expunge */ rv = cmd_expunge(state, stream, msgmap, agg); break; case 'c' : /* cancel */ cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command" : "Apply command"); break; case '#' : if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){ static ESCKEY_S choose_role[] = { {'r', 'r', "R", N_("Reply")}, {'f', 'f', "F", N_("Forward")}, {'b', 'b', "B", N_("Bounce")}, {-1, 0, NULL, NULL} }; int action; ACTION_S *role = NULL; action = radio_buttons(_("Reply, Forward or Bounce using a role? "), -FOOTER_ROWS(state), choose_role, 'r', 'x', h_role_aggregate, RB_NORM); if(action == 'r' || action == 'f' || action == 'b'){ void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL; redraw = state->redrawer; state->redrawer = NULL; prev_screen = state->prev_screen; role = NULL; state->next_screen = SCREEN_FUN_NULL; if(role_select_screen(state, &role, action == 'f' ? MC_FORWARD : action == 'r' ? MC_REPLY : action == 'b' ? MC_BOUNCE : 0) < 0){ cmd_cancelled(action == 'f' ? _("Forward") : action == 'r' ? _("Reply") : _("Bounce")); state->next_screen = prev_screen; state->redrawer = redraw; state->mangled_screen = 1; } else{ if(role) role = combine_inherited_role(role); else{ role = (ACTION_S *) fs_get(sizeof(*role)); memset((void *) role, 0, sizeof(*role)); role->nick = cpystr("Default Role"); } state->redrawer = NULL; switch(action){ case 'r': (void) cmd_reply(state, msgmap, agg, role); break; case 'f': (void) cmd_forward(state, msgmap, agg, role); break; case 'b': (void) cmd_bounce(state, msgmap, agg, role); break; } if(role) free_action(&role); if(redraw) (*redraw)(); state->next_screen = prev_screen; state->redrawer = redraw; state->mangled_screen = 1; } } } break; case 'z' : /* default */ q_status_message(SM_INFO, 0, 2, "Cancelled, there is no default command"); break; default: break; } return(rv); } /* * Select by message number ranges. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) { int r, end, cur; long n1, n2, raw; char number1[16], number2[16], numbers[80], *p, *t; HelpType help; MESSAGECACHE *mc; numbers[0] = '\0'; ps_global->mangled_footer = 1; help = NO_HELP; while(1){ int flags = OE_APPEND_CURRENT; r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, sizeof(numbers), _(select_num), NULL, help, &flags); if(r == 4) continue; if(r == 3){ help = (help == NO_HELP) ? h_select_by_num : NO_HELP; continue; } for(t = p = numbers; *p ; p++) /* strip whitespace */ if(!isspace((unsigned char)*p)) *t++ = *p; *t = '\0'; if(r == 1 || numbers[0] == '\0'){ cmd_cancelled("Selection by number"); return(1); } else break; } for(n1 = 1; n1 <= stream->nmsgs; n1++) if((mc = mail_elt(stream, n1)) != NULL) mc->searched = 0; /* clear searched bits */ for(p = numbers; *p ; p++){ t = number1; while(*p && isdigit((unsigned char)*p)) *t++ = *p++; *t = '\0'; end = cur = 0; if(number1[0] == '\0'){ if(*p == '-'){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid number range, missing number before \"-\": %s"), numbers); return(1); } else if(!strucmp("end", p)){ end = 1; p += strlen("end"); } else if(!strucmp("$", p)){ end = 1; p++; } else if(*p == '.'){ cur = 1; p++; } else{ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid message number: %s"), numbers); return(1); } } if(end) n1 = mn_get_total(msgmap); else if(cur) n1 = mn_get_cur(msgmap); else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("\"%s\" out of message number range"), long2string(n1)); return(1); } t = number2; if(*p == '-'){ while(*++p && isdigit((unsigned char)*p)) *t++ = *p; *t = '\0'; end = cur = 0; if(number2[0] == '\0'){ if(!strucmp("end", p)){ end = 1; p += strlen("end"); } else if(!strucmp(p, "$")){ end = 1; p++; } else if(*p == '.'){ cur = 1; p++; } else{ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid number range, missing number after \"-\": %s"), numbers); return(1); } } if(end) n2 = mn_get_total(msgmap); else if(cur) n2 = mn_get_cur(msgmap); else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("\"%s\" out of message number range"), long2string(n2)); return(1); } if(n2 <= n1){ char t[20]; strncpy(t, long2string(n1), sizeof(t)); t[sizeof(t)-1] = '\0'; q_status_message2(SM_ORDER | SM_DING, 0, 2, _("Invalid reverse message number range: %s-%s"), t, long2string(n2)); return(1); } for(;n1 <= n2; n1++){ raw = mn_m2raw(msgmap, n1); if(raw > 0L && (!(limitsrch && *limitsrch) || in_searchset(*limitsrch, (unsigned long) raw))) mm_searched(stream, raw); } } else{ raw = mn_m2raw(msgmap, n1); if(raw > 0L && (!(limitsrch && *limitsrch) || in_searchset(*limitsrch, (unsigned long) raw))) mm_searched(stream, raw); } if(*p == '\0') break; } return(0); } /* * Select by thread number ranges. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset) { int r, end, cur; long n1, n2; char number1[16], number2[16], numbers[80], *p, *t; HelpType help; PINETHRD_S *thrd = NULL, *th; MESSAGECACHE *mc; numbers[0] = '\0'; ps_global->mangled_footer = 1; help = NO_HELP; while(1){ int flags = OE_APPEND_CURRENT; r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, sizeof(numbers), _(select_num), NULL, help, &flags); if(r == 4) continue; if(r == 3){ help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP; continue; } for(t = p = numbers; *p ; p++) /* strip whitespace */ if(!isspace((unsigned char)*p)) *t++ = *p; *t = '\0'; if(r == 1 || numbers[0] == '\0'){ cmd_cancelled("Selection by number"); return(1); } else break; } for(n1 = 1; n1 <= stream->nmsgs; n1++) if((mc = mail_elt(stream, n1)) != NULL) mc->searched = 0; /* clear searched bits */ for(p = numbers; *p ; p++){ t = number1; while(*p && isdigit((unsigned char)*p)) *t++ = *p++; *t = '\0'; end = cur = 0; if(number1[0] == '\0'){ if(*p == '-'){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid number range, missing number before \"-\": %s"), numbers); return(1); } else if(!strucmp("end", p)){ end = 1; p += strlen("end"); } else if(!strucmp(p, "$")){ end = 1; p++; } else if(*p == '.'){ cur = 1; p++; } else{ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid thread number: %s"), numbers); return(1); } } if(end) n1 = msgmap->max_thrdno; else if(cur){ th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); n1 = th->thrdno; } else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("\"%s\" out of thread number range"), long2string(n1)); return(1); } t = number2; if(*p == '-'){ while(*++p && isdigit((unsigned char)*p)) *t++ = *p; *t = '\0'; end = 0; if(number2[0] == '\0'){ if(!strucmp("end", p)){ end = 1; p += strlen("end"); } else if(!strucmp("$", p)){ end = 1; p++; } else if(*p == '.'){ cur = 1; p++; } else{ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid number range, missing number after \"-\": %s"), numbers); return(1); } } if(end) n2 = msgmap->max_thrdno; else if(cur){ th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); n2 = th->thrdno; } else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("\"%s\" out of thread number range"), long2string(n2)); return(1); } if(n2 <= n1){ char t[20]; strncpy(t, long2string(n1), sizeof(t)); t[sizeof(t)-1] = '\0'; q_status_message2(SM_ORDER | SM_DING, 0, 2, _("Invalid reverse message number range: %s-%s"), t, long2string(n2)); return(1); } for(;n1 <= n2; n1++){ thrd = find_thread_by_number(stream, msgmap, n1, thrd); if(thrd) set_search_bit_for_thread(stream, thrd, msgset); } } else{ thrd = find_thread_by_number(stream, msgmap, n1, NULL); if(thrd) set_search_bit_for_thread(stream, thrd, msgset); } if(*p == '\0') break; } return(0); } /* * Select by message dates. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch) { int r, we_cancel = 0, when = 0; char date[100], defdate[100], prompt[128]; time_t seldate = time(0); struct tm *seldate_tm; SEARCHPGM *pgm; HelpType help; static struct _tense { char *preamble, *range, *scope; } tense[] = { {"were ", "SENT SINCE", " (inclusive)"}, {"were ", "SENT BEFORE", " (exclusive)"}, {"were ", "SENT ON", "" }, {"", "ARRIVED SINCE", " (inclusive)"}, {"", "ARRIVED BEFORE", " (exclusive)"}, {"", "ARRIVED ON", "" } }; date[0] = '\0'; ps_global->mangled_footer = 1; help = NO_HELP; /* * If talking to an old server, default to SINCE instead of * SENTSINCE, which was added later. */ if(is_imap_stream(stream) && !modern_imap_stream(stream)) when = 3; while(1){ int flags = OE_APPEND_CURRENT; seldate_tm = localtime(&seldate); snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday, month_abbrev(seldate_tm->tm_mon + 1), seldate_tm->tm_year + 1900); defdate[sizeof(defdate)-1] = '\0'; snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ", tense[when].preamble, tense[when].range, tense[when].scope, defdate); prompt[sizeof(prompt)-1] = '\0'; r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date), prompt, sel_date_opt, help, &flags); switch (r){ case 1 : cmd_cancelled("Selection by date"); return(1); case 3 : help = (help == NO_HELP) ? h_select_date : NO_HELP; continue; case 4 : continue; case 11 : { MESSAGECACHE *mc; long rawno; if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){ /* cache not filled in yet? */ if(mc->day == 0){ char seq[20]; if(stream->dtb && stream->dtb->flags & DR_NEWS){ strncpy(seq, ulong2string(mail_uid(stream, rawno)), sizeof(seq)); seq[sizeof(seq)-1] = '\0'; mail_fetch_overview(stream, seq, NULL); } else{ strncpy(seq, long2string(rawno), sizeof(seq)); seq[sizeof(seq)-1] = '\0'; mail_fetch_fast(stream, seq, 0L); } } /* mail_date returns fixed field width date */ mail_date(date, mc); date[11] = '\0'; } } continue; case 12 : /* set default to PREVIOUS day */ seldate -= 86400; continue; case 13 : /* set default to NEXT day */ seldate += 86400; continue; case 14 : when = (when+1) % (sizeof(tense) / sizeof(struct _tense)); continue; default: break; } removing_leading_white_space(date); removing_trailing_white_space(date); if(!*date){ strncpy(date, defdate, sizeof(date)); date[sizeof(date)-1] = '\0'; } break; } if((pgm = mail_newsearchpgm()) != NULL){ MESSAGECACHE elt; short converted_date; if(mail_parse_date(&elt, (unsigned char *) date)){ converted_date = mail_shortdate(elt.year, elt.month, elt.day); switch(when){ case 0: pgm->sentsince = converted_date; break; case 1: pgm->sentbefore = converted_date; break; case 2: pgm->senton = converted_date; break; case 3: pgm->since = converted_date; break; case 4: pgm->before = converted_date; break; case 5: pgm->on = converted_date; break; } pgm->msgno = (limitsrch ? *limitsrch : NULL); if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); if(we_cancel) cancel_busy_cue(0); /* we know this was freed in mail_search, let caller know */ if(limitsrch) *limitsrch = NULL; } else{ mail_free_searchpgm(&pgm); q_status_message1(SM_ORDER, 3, 3, _("Invalid date entered: %s"), date); return(1); } } return(0); } /* * Select by searching in message headers or body * using the x-gm-ext-1 capaility. This function * reads the input from the user and passes it to * the server directly. We need a x-gm-ext-1 variable * in the search pgm to carry this information to the * server. * * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch) { int r, we_cancel = 0, rv; char tmp[128]; char namehdr[MAILTMPLEN]; ESCKEY_S ekey[8]; HelpType help; ps_global->mangled_footer = 1; ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1; strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1); tmp[sizeof(tmp)-1] = '\0'; namehdr[0] = '\0'; help = NO_HELP; while (1){ int flags = OE_APPEND_CURRENT; r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0, sizeof(namehdr), tmp, ekey, help, &flags); if(r == 4) continue; if(r == 3){ help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP; continue; } if (r == 1){ cmd_cancelled("Selection by content"); return(1); } removing_leading_white_space(namehdr); if ((namehdr[0] != '\0') && isspace((unsigned char) namehdr[strlen(namehdr) - 1])) removing_trailing_white_space(namehdr); if (namehdr[0] != '\0') break; } if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch); if(we_cancel) cancel_busy_cue(0); return(rv); } /* * Select by searching in message headers or body. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch) { int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0; int not = 0, me = 0; char sstring[80], tmp[128]; char *p, *sval = NULL; char buftmp[MAILTMPLEN], namehdr[80]; ESCKEY_S ekey[8]; ENVELOPE *env = NULL; HelpType help; unsigned flagsforhist = 0; static HISTORY_S *history = NULL; static char *recip = "RECIPIENTS"; static char *partic = "PARTICIPANTS"; static char *match_me = N_("[Match_My_Addresses]"); static char *dont_match_me = N_("[Don't_Match_My_Addresses]"); ps_global->mangled_footer = 1; ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1; while(1){ type = radio_buttons(not ? _(sel_text_not) : _(sel_text), -FOOTER_ROWS(ps_global), sel_text_opt, 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP); if(type == '!') not = !not; else if(type == 3){ helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS", HLPD_SIMPLE); ps_global->mangled_screen = 1; } else break; } /* * prepare some friendly defaults... */ switch(type){ case 't' : /* address fields, offer To or From */ case 'f' : case 'c' : case 'r' : case 'p' : sval = (type == 't') ? "TO" : (type == 'f') ? "FROM" : (type == 'c') ? "CC" : (type == 'r') ? recip : partic; ekey[ekeyi].ch = ctrl('T'); ekey[ekeyi].name = "^T"; ekey[ekeyi].rval = 10; /* TRANSLATORS: use Current To Address */ ekey[ekeyi++].label = N_("Cur To"); ekey[ekeyi].ch = ctrl('R'); ekey[ekeyi].name = "^R"; ekey[ekeyi].rval = 11; /* TRANSLATORS: use Current From Address */ ekey[ekeyi++].label = N_("Cur From"); ekey[ekeyi].ch = ctrl('W'); ekey[ekeyi].name = "^W"; ekey[ekeyi].rval = 12; /* TRANSLATORS: use Current Cc Address */ ekey[ekeyi++].label = N_("Cur Cc"); ekey[ekeyi].ch = ctrl('Y'); ekey[ekeyi].name = "^Y"; ekey[ekeyi].rval = 13; /* TRANSLATORS: Match Me means match my address */ ekey[ekeyi++].label = N_("Match Me"); ekey[ekeyi].ch = 0; ekey[ekeyi].name = ""; ekey[ekeyi].rval = 0; ekey[ekeyi++].label = ""; break; case 's' : sval = "SUBJECT"; ekey[ekeyi].ch = ctrl('X'); ekey[ekeyi].name = "^X"; ekey[ekeyi].rval = 14; /* TRANSLATORS: use Current Subject */ ekey[ekeyi++].label = N_("Cur Subject"); break; case 'a' : sval = "TEXT"; break; case 'b' : sval = "BODYTEXT"; break; case 'h' : strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1); tmp[sizeof(tmp)-1] = '\0'; flags = OE_APPEND_CURRENT; namehdr[0] = '\0'; r = 'x'; while (r == 'x'){ int done = 0; r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0, sizeof(namehdr), tmp, ekey, NO_HELP, &flags); if (r == 1){ cmd_cancelled("Selection by text"); return(1); } removing_leading_white_space(namehdr); while(!done){ while ((namehdr[0] != '\0') && /* remove trailing ":" */ (namehdr[strlen(namehdr) - 1] == ':')) namehdr[strlen(namehdr) - 1] = '\0'; if ((namehdr[0] != '\0') && isspace((unsigned char) namehdr[strlen(namehdr) - 1])) removing_trailing_white_space(namehdr); else done++; } if (strchr(namehdr,' ') || strchr(namehdr,'\t') || strchr(namehdr,':')) namehdr[0] = '\0'; if (namehdr[0] == '\0') r = 'x'; } sval = namehdr; break; case 'x': break; default: dprint((1,"\n - BOTCH: select_text unrecognized option\n")); return(1); } ekey[ekeyi].ch = KEY_UP; ekey[ekeyi].rval = 30; ekey[ekeyi].name = ""; ku = ekeyi; ekey[ekeyi++].label = ""; ekey[ekeyi].ch = KEY_DOWN; ekey[ekeyi].rval = 31; ekey[ekeyi].name = ""; ekey[ekeyi++].label = ""; ekey[ekeyi].ch = -1; if(type != 'x'){ init_hist(&history, HISTSIZE); if(ekey[0].ch > -1 && msgno > 0L && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno), NULL))) ekey[0].ch = -1; sstring[0] = '\0'; help = NO_HELP; r = type; while(r != 'x'){ if(not) /* TRANSLATORS: character String in message to NOT match : " */ snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval); else snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval); if(items_in_hist(history) > 0){ ekey[ku].name = HISTORY_UP_KEYNAME; ekey[ku].label = HISTORY_KEYLABEL; ekey[ku+1].name = HISTORY_DOWN_KEYNAME; ekey[ku+1].label = HISTORY_KEYLABEL; } else{ ekey[ku].name = ""; ekey[ku].label = ""; ekey[ku+1].name = ""; ekey[ku+1].label = ""; } flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE; r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0, 79, tmp, ekey, help, &flags); if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me))))) me = 0; switch(r){ case 3 : help = (help == NO_HELP) ? (not ? ((type == 'f') ? h_select_txt_not_from : (type == 't') ? h_select_txt_not_to : (type == 'c') ? h_select_txt_not_cc : (type == 's') ? h_select_txt_not_subj : (type == 'a') ? h_select_txt_not_all : (type == 'r') ? h_select_txt_not_recip : (type == 'p') ? h_select_txt_not_partic : (type == 'b') ? h_select_txt_not_body : NO_HELP) : ((type == 'f') ? h_select_txt_from : (type == 't') ? h_select_txt_to : (type == 'c') ? h_select_txt_cc : (type == 's') ? h_select_txt_subj : (type == 'a') ? h_select_txt_all : (type == 'r') ? h_select_txt_recip : (type == 'p') ? h_select_txt_partic : (type == 'b') ? h_select_txt_body : NO_HELP)) : NO_HELP; case 4 : continue; case 10 : /* To: default */ if(env && env->to && env->to->mailbox){ snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox, env->to->host ? "@" : "", env->to->host ? env->to->host : ""); sstring[sizeof(sstring)-1] = '\0'; } continue; case 11 : /* From: default */ if(env && env->from && env->from->mailbox){ snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox, env->from->host ? "@" : "", env->from->host ? env->from->host : ""); sstring[sizeof(sstring)-1] = '\0'; } continue; case 12 : /* Cc: default */ if(env && env->cc && env->cc->mailbox){ snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox, env->cc->host ? "@" : "", env->cc->host ? env->cc->host : ""); sstring[sizeof(sstring)-1] = '\0'; } continue; case 13 : /* Match my addresses */ me++; snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me)); continue; case 14 : /* Subject: default */ if(env && env->subject && env->subject[0]){ char *q = NULL; q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, env->subject); snprintf(sstring, sizeof(sstring), "%s", q); sstring[sizeof(sstring)-1] = '\0'; } continue; case 30 : flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0); if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){ strncpy(sstring, p, sizeof(sstring)); sstring[sizeof(sstring)-1] = '\0'; if(history->hist[history->curindex]){ flagsforhist = history->hist[history->curindex]->flags; not = (flagsforhist & 0x1) ? 1 : 0; me = (flagsforhist & 0x2) ? 1 : 0; } } else Writechar(BELL, 0); continue; case 31 : flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0); if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){ strncpy(sstring, p, sizeof(sstring)); sstring[sizeof(sstring)-1] = '\0'; if(history->hist[history->curindex]){ flagsforhist = history->hist[history->curindex]->flags; not = (flagsforhist & 0x1) ? 1 : 0; me = (flagsforhist & 0x2) ? 1 : 0; } } else Writechar(BELL, 0); continue; default : break; } if(r == 1 || sstring[0] == '\0') r = 'x'; break; } } if(type == 'x' || r == 'x'){ cmd_cancelled("Selection by text"); return(1); } if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0); save_hist(history, sstring, flagsforhist, NULL); rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch); if(we_cancel) cancel_busy_cue(0); return(rv); } /* * Select by message size. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch) { int r, large = 1, we_cancel = 0; unsigned long n, mult = 1L, numerator = 0L, divisor = 1L; char size[16], numbers[80], *p, *t; HelpType help; SEARCHPGM *pgm; long flags = (SE_NOPREFETCH | SE_FREE); numbers[0] = '\0'; ps_global->mangled_footer = 1; help = NO_HELP; while(1){ int flgs = OE_APPEND_CURRENT; sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger; r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, sizeof(numbers), large ? _(select_size_larger_msg) : _(select_size_smaller_msg), sel_size_opt, help, &flgs); if(r == 4) continue; if(r == 14){ large = 1 - large; continue; } if(r == 3){ help = (help == NO_HELP) ? (large ? h_select_by_larger_size : h_select_by_smaller_size) : NO_HELP; continue; } for(t = p = numbers; *p ; p++) /* strip whitespace */ if(!isspace((unsigned char)*p)) *t++ = *p; *t = '\0'; if(r == 1 || numbers[0] == '\0'){ cmd_cancelled("Selection by size"); return(1); } else break; } if(numbers[0] == '-'){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid size entered: %s"), numbers); return(1); } t = size; p = numbers; while(*p && isdigit((unsigned char)*p)) *t++ = *p++; *t = '\0'; if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){ size[0] = '0'; size[1] = '\0'; } if(size[0] == '\0'){ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid size entered: %s"), numbers); return(1); } n = strtoul(size, (char **)NULL, 10); size[0] = '\0'; if(*p == '.'){ /* * We probably ought to just use atof() to convert 1.1 into a * double, but since we haven't used atof() anywhere else I'm * reluctant to use it because of portability concerns. */ p++; t = size; while(*p && isdigit((unsigned char)*p)){ *t++ = *p++; divisor *= 10; } *t = '\0'; if(size[0]) numerator = strtoul(size, (char **)NULL, 10); } switch(*p){ case 'g': case 'G': mult *= 1000; /* fall through */ case 'm': case 'M': mult *= 1000; /* fall through */ case 'k': case 'K': mult *= 1000; break; } n = n * mult + (numerator * mult) / divisor; pgm = mail_newsearchpgm(); if(large) pgm->larger = n; else pgm->smaller = n; if(is_imap_stream(stream) && !modern_imap_stream(stream)) flags |= SE_NOSERVER; if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); pgm->msgno = (limitsrch ? *limitsrch : NULL); pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); /* we know this was freed in mail_search, let caller know */ if(limitsrch) *limitsrch = NULL; if(we_cancel) cancel_busy_cue(0); return(0); } /* * visible_searchset -- return c-client search set unEXLDed * sequence numbers */ SEARCHSET * visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap) { long n, run; SEARCHSET *full_set = NULL, **set; /* * If we're talking to anything other than a server older than * imap 4rev1, build a searchset otherwise it'll choke. */ if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){ if(any_lflagged(msgmap, MN_EXLD)){ for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++) if(get_lflag(stream, NULL, n, MN_EXLD)){ if(run){ /* previous NOT excluded? */ if(run > 1L) (*set)->last = n - 1L; set = &(*set)->next; run = 0L; } } else if(run++){ /* next in run */ (*set)->last = n; } else{ /* start of run */ *set = mail_newsearchset(); (*set)->first = n; } } else{ full_set = mail_newsearchset(); full_set->first = 1L; full_set->last = stream->nmsgs; } } return(full_set); } /* * Select by message status bits. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch) { int s, not = 0, we_cancel = 0, rv; while(1){ s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag), -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x', NO_HELP, RB_NORM|RB_RET_HELP); if(s == 'x'){ cmd_cancelled("Selection by status"); return(1); } else if(s == 3){ helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"), HLPD_SIMPLE); ps_global->mangled_screen = 1; } else if(s == '!') not = !not; else break; } if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); rv = agg_flag_select(stream, not, s, limitsrch); if(we_cancel) cancel_busy_cue(0); return(rv); } /* * Select by rule. Usually srch, indexcolor, and roles would be most useful. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch) { char rulenick[1000], *nick; PATGRP_S *patgrp; int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH | ROLE_DO_INCOLS | ROLE_DO_ROLES | ROLE_DO_SCORES | ROLE_DO_OTHER | ROLE_DO_FILTER; rulenick[0] = '\0'; ps_global->mangled_footer = 1; do{ int oe_flags; oe_flags = OE_APPEND_CURRENT; r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0, sizeof(rulenick), not ? _("Rule to NOT match: ") : _("Rule to match: "), sel_key_opt, NO_HELP, &oe_flags); if(r == 14){ /* select rulenick from a list */ if((nick=choose_a_rule(rflags)) != NULL){ strncpy(rulenick, nick, sizeof(rulenick)-1); rulenick[sizeof(rulenick)-1] = '\0'; fs_give((void **) &nick); } else r = 4; } else if(r == '!') not = !not; if(r == 3){ helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE); ps_global->mangled_screen = 1; } else if(r == 1){ cmd_cancelled("Selection by Rule"); return(1); } removing_leading_and_trailing_white_space(rulenick); }while(r == 3 || r == 4 || r == '!'); /* * The approach of requiring a nickname instead of just allowing the * user to select from the list of rules has the drawback that a rule * may not have a nickname, or there may be more than one rule with * the same nickname. However, it has the benefit of allowing the user * to type in the nickname and, most importantly, allows us to set * up the ! (not). We could incorporate the ! into the selection * screen, but this is easier and also allows the typing of nicks. * User can just set up nicknames if they want to use this feature. */ patgrp = nick_to_patgrp(rulenick, rflags); if(patgrp){ if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL, get_msg_score, (not ? MP_NOT : 0) | SE_NOPREFETCH); free_patgrp(&patgrp); if(we_cancel) cancel_busy_cue(0); } if(limitsrch && *limitsrch){ mail_free_searchset(limitsrch); *limitsrch = NULL; } return(0); } /* * Allow user to choose a rule from their list of rules. * * Returns an allocated rule nickname on success, NULL otherwise. */ char * choose_a_rule(int rflags) { char *choice = NULL; char **rule_list, **lp; int cnt = 0; PAT_S *pat; PAT_STATE pstate; if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){ q_status_message(SM_ORDER, 3, 3, _("No rules available. Use Setup/Rules to add some.")); return(choice); } /* * Build a list of rules to choose from. */ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) cnt++; if(cnt <= 0){ q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules")); return(choice); } lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list)); memset(rule_list, 0, (cnt+1) * sizeof(*rule_list)); for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) *lp++ = cpystr((pat->patgrp && pat->patgrp->nick) ? pat->patgrp->nick : "?"); /* TRANSLATORS: SELECT A RULE is a screen title TRANSLATORS: Print something1 using something2. "rules" is something1 */ choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"), _("rules"), h_select_rule_screen, _("HELP FOR SELECTING A RULE NICKNAME"), NULL); if(!choice) q_status_message(SM_ORDER, 1, 4, "No choice"); free_list_array(&rule_list); return(choice); } /* * Select by current thread. * Sets searched bits in mail_elts for this entire thread * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) { long n; PINETHRD_S *thrd = NULL; int ret = 1; MESSAGECACHE *mc; if(!stream) return(ret); for(n = 1L; n <= stream->nmsgs; n++) if((mc = mail_elt(stream, n)) != NULL) mc->searched = 0; /* clear searched bits */ thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); if(thrd && thrd->top && thrd->top != thrd->rawno) thrd = fetch_thread(stream, thrd->top); /* * This doesn't unselect if the thread is already selected * (like select current does), it always selects. * There is no way to select ! this thread. */ if(thrd){ set_search_bit_for_thread(stream, thrd, limitsrch); ret = 0; } return(ret); } /* * Select by message keywords. * Sets searched bits in mail_elts * * Args limitsrch -- limit search to this searchset * * Returns 0 on success. */ int select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch) { int r, not = 0, we_cancel = 0; char keyword[MAXUSERFLAG+1], *kword; char *error = NULL, *p, *prompt; HelpType help; SEARCHPGM *pgm; keyword[0] = '\0'; ps_global->mangled_footer = 1; help = NO_HELP; do{ int oe_flags; if(error){ q_status_message(SM_ORDER, 3, 4, error); fs_give((void **) &error); } if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){ if(not) prompt = _("Keyword (or keyword initial) to NOT match: "); else prompt = _("Keyword (or keyword initial) to match: "); } else{ if(not) prompt = _("Keyword to NOT match: "); else prompt = _("Keyword to match: "); } oe_flags = OE_APPEND_CURRENT; r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0, sizeof(keyword), prompt, sel_key_opt, help, &oe_flags); if(r == 14){ /* select keyword from a list */ if((kword=choose_a_keyword()) != NULL){ strncpy(keyword, kword, sizeof(keyword)-1); keyword[sizeof(keyword)-1] = '\0'; fs_give((void **) &kword); } else r = 4; } else if(r == '!') not = !not; if(r == 3) help = help == NO_HELP ? h_select_keyword : NO_HELP; else if(r == 1){ cmd_cancelled("Selection by keyword"); return(1); } removing_leading_and_trailing_white_space(keyword); }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error)); if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){ p = initial_to_keyword(keyword); if(p != keyword){ strncpy(keyword, p, sizeof(keyword)-1); keyword[sizeof(keyword)-1] = '\0'; } } /* * We want to check the keyword, not the nickname of the keyword, * so convert it to the keyword if necessary. */ p = nick_to_keyword(keyword); if(p != keyword){ strncpy(keyword, p, sizeof(keyword)-1); keyword[sizeof(keyword)-1] = '\0'; } pgm = mail_newsearchpgm(); if(not){ pgm->unkeyword = mail_newstringlist(); pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword); pgm->unkeyword->text.size = strlen(keyword); } else{ pgm->keyword = mail_newstringlist(); pgm->keyword->text.data = (unsigned char *) cpystr(keyword); pgm->keyword->text.size = strlen(keyword); } if(ps_global && ps_global->ttyo){ blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); ps_global->mangled_footer = 1; } we_cancel = busy_cue(_("Selecting"), NULL, 1); pgm->msgno = (limitsrch ? *limitsrch : NULL); pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE); /* we know this was freed in mail_search, let caller know */ if(limitsrch) *limitsrch = NULL; if(we_cancel) cancel_busy_cue(0); return(0); } /* * Allow user to choose a keyword from their list of keywords. * * Returns an allocated keyword on success, NULL otherwise. */ char * choose_a_keyword(void) { char *choice = NULL; char **keyword_list, **lp; int cnt; KEYWORD_S *kw; /* * Build a list of keywords to choose from. */ for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next) cnt++; if(cnt <= 0){ q_status_message(SM_ORDER, 3, 4, _("No keywords defined, use \"keywords\" option in Setup/Config")); return(choice); } lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list)); memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list)); for(kw = ps_global->keywords; kw; kw = kw->next) *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : ""); /* TRANSLATORS: SELECT A KEYWORD is a screen title TRANSLATORS: Print something1 using something2. "keywords" is something1 */ choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"), _("keywords"), h_select_keyword_screen, _("HELP FOR SELECTING A KEYWORD"), NULL); if(!choice) q_status_message(SM_ORDER, 1, 4, "No choice"); free_list_array(&keyword_list); return(choice); } /* * Allow user to choose a list of keywords from their list of keywords. * * Returns allocated list. */ char ** choose_list_of_keywords(void) { LIST_SEL_S *listhead, *ls, *p; char **ret = NULL; int cnt, i; KEYWORD_S *kw; /* * Build a list of keywords to choose from. */ p = listhead = NULL; for(kw = ps_global->keywords; kw; kw = kw->next){ ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : ""); if(p){ p->next = ls; p = p->next; } else listhead = p = ls; } if(!listhead) return(ret); /* TRANSLATORS: SELECT KEYWORDS is a screen title Print something1 using something2. "keywords" is something1 */ if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE, _("SELECT KEYWORDS"), _("keywords"), h_select_multkeyword_screen, _("HELP FOR SELECTING KEYWORDS"), NULL)){ for(cnt = 0, p = listhead; p; p = p->next) if(p->selected) cnt++; ret = (char **) fs_get((cnt+1) * sizeof(*ret)); memset(ret, 0, (cnt+1) * sizeof(*ret)); for(i = 0, p = listhead; p; p = p->next) if(p->selected) ret[i++] = cpystr(p->item ? p->item : ""); } free_list_sel(&listhead); return(ret); } /* * Allow user to choose a charset * * Returns an allocated charset on success, NULL otherwise. */ char * choose_a_charset(int which_charsets) { char *choice = NULL; char **charset_list, **lp; const CHARSET *cs; int cnt; /* * Build a list of charsets to choose from. */ for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){ if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL)) && ((which_charsets & CAC_ALL) || (which_charsets & CAC_POSTING && cs->flags & CF_POSTING) || (which_charsets & CAC_DISPLAY && cs->type != CT_2022 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY)))) cnt++; } if(cnt <= 0){ q_status_message(SM_ORDER, 3, 4, _("No charsets found? Enter charset manually.")); return(choice); } lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list)); memset(charset_list, 0, (cnt+1) * sizeof(*charset_list)); for(cs = utf8_charset(NIL); cs && cs->name; cs++){ if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL)) && ((which_charsets & CAC_ALL) || (which_charsets & CAC_POSTING && cs->flags & CF_POSTING) || (which_charsets & CAC_DISPLAY && cs->type != CT_2022 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY)))) *lp++ = cpystr(cs->name); } /* TRANSLATORS: SELECT A CHARACTER SET is a screen title TRANSLATORS: Print something1 using something2. "character sets" is something1 */ choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"), _("character sets"), h_select_charset_screen, _("HELP FOR SELECTING A CHARACTER SET"), NULL); if(!choice) q_status_message(SM_ORDER, 1, 4, "No choice"); free_list_array(&charset_list); return(choice); } /* * Allow user to choose a list of character sets and/or scripts * * Returns allocated list. */ char ** choose_list_of_charsets(void) { LIST_SEL_S *listhead, *ls, *p; char **ret = NULL; int cnt, i, got_one; const CHARSET *cs; SCRIPT *s; char *q, *t; long width, limit; char buf[1024], *folded; /* * Build a list of charsets to choose from. */ p = listhead = NULL; /* this width is determined by select_from_list_screen() */ width = ps_global->ttyo->screen_cols - 4; /* first comes a list of scripts (sets of character sets) */ for(s = utf8_script(NIL); s && s->name; s++){ limit = sizeof(buf)-1; q = buf; memset(q, 0, limit+1); if(s->name) sstrncpy(&q, s->name, limit); if(s->description){ sstrncpy(&q, " (", limit-(q-buf)); sstrncpy(&q, s->description, limit-(q-buf)); sstrncpy(&q, ")", limit-(q-buf)); } /* add the list of charsets that are in this script */ got_one = 0; for(cs = utf8_charset(NIL); cs && cs->name && (q-buf) < limit; cs++){ if(cs->script & s->script){ /* * Filter out some un-useful members of the list. * UTF-7 and UTF-8 weren't actually in the list at the * time this was written. Just making sure. */ if(!strucmp(cs->name, "ISO-2022-JP-2") || !strucmp(cs->name, "UTF-7") || !strucmp(cs->name, "UTF-8")) continue; if(got_one) sstrncpy(&q, " ", limit-(q-buf)); else{ got_one = 1; sstrncpy(&q, " {", limit-(q-buf)); } sstrncpy(&q, cs->name, limit-(q-buf)); } } if(got_one) sstrncpy(&q, "}", limit-(q-buf)); /* fold this line so that it can all be seen on the screen */ folded = fold(buf, width, width, "", " ", FLD_NONE); if(folded){ t = folded; while(t && *t && (q = strindex(t, '\n')) != NULL){ *q = '\0'; ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); if(t == folded) ls->item = cpystr(s->name); else ls->flags = SFL_NOSELECT; ls->display_item = cpystr(t); t = q+1; if(p){ p->next = ls; p = p->next; } else{ /* add a heading */ listhead = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(listhead, 0, sizeof(*listhead)); listhead->flags = SFL_NOSELECT; listhead->display_item = cpystr(_("Scripts representing groups of related character sets")); listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(listhead->next, 0, sizeof(*listhead)); listhead->next->flags = SFL_NOSELECT; listhead->next->display_item = cpystr(repeat_char(width, '-')); listhead->next->next = ls; p = ls; } } fs_give((void **) &folded); } } ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); ls->flags = SFL_NOSELECT; if(p){ p->next = ls; p = p->next; } else listhead = p = ls; ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); ls->flags = SFL_NOSELECT; ls->display_item = cpystr(_("Individual character sets, may be mixed with scripts")); p->next = ls; p = p->next; ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); ls->flags = SFL_NOSELECT; ls->display_item = cpystr(repeat_char(width, '-')); p->next = ls; p = p->next; /* then comes a list of individual character sets */ for(cs = utf8_charset(NIL); cs && cs->name; cs++){ ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); ls->item = cpystr(cs->name); if(p){ p->next = ls; p = p->next; } else listhead = p = ls; } if(!listhead) return(ret); /* TRANSLATORS: SELECT CHARACTER SETS is a screen title Print something1 using something2. "character sets" is something1 */ if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE, _("SELECT CHARACTER SETS"), _("character sets"), h_select_multcharsets_screen, _("HELP FOR SELECTING CHARACTER SETS"), NULL)){ for(cnt = 0, p = listhead; p; p = p->next) if(p->selected) cnt++; ret = (char **) fs_get((cnt+1) * sizeof(*ret)); memset(ret, 0, (cnt+1) * sizeof(*ret)); for(i = 0, p = listhead; p; p = p->next) if(p->selected) ret[i++] = cpystr(p->item ? p->item : ""); } free_list_sel(&listhead); return(ret); } /* Report quota summary resources in an IMAP server */ void cmd_quota (struct pine *state) { QUOTALIST *imapquota; NETMBX mb; STORE_S *store; SCROLL_S sargs; if(!state->mail_stream || !is_imap_stream(state->mail_stream)){ q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders"); return; } if (state->mail_stream && !sp_dead_stream(state->mail_stream) && state->mail_stream->mailbox && *state->mail_stream->mailbox && mail_valid_net_parse(state->mail_stream->mailbox, &mb)) imap_getquotaroot(state->mail_stream, mb.mailbox); if(!state->quota) /* failed ? */ return; /* go back... */ if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){ q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space."); return; } so_puts(store, "Quota Report for "); so_puts(store, state->mail_stream->original_mailbox); so_puts(store, "\n\n"); for (imapquota = state->quota; imapquota; imapquota = imapquota->next){ so_puts(store, _("Resource : ")); so_puts(store, imapquota->name); so_writec('\n', store); so_puts(store, _("Usage : ")); so_puts(store, long2string(imapquota->usage)); if(!strucmp(imapquota->name,"STORAGE")) so_puts(store, " KiB "); if(!strucmp(imapquota->name,"MESSAGE")){ so_puts(store, _(" message")); if(imapquota->usage != 1) so_puts(store, _("s ")); /* plural */ else so_puts(store, _(" ")); } so_writec('(', store); so_puts(store, long2string(100*imapquota->usage/imapquota->limit)); so_puts(store, "%)\n"); so_puts(store, _("Limit : ")); so_puts(store, long2string(imapquota->limit)); if(!strucmp(imapquota->name,"STORAGE")) so_puts(store, " KiB\n\n"); if(!strucmp(imapquota->name,"MESSAGE")){ so_puts(store, _(" message")); if(imapquota->usage != 1) so_puts(store, _("s\n\n")); /* plural */ else so_puts(store, _("\n\n")); } } memset(&sargs, 0, sizeof(SCROLL_S)); sargs.text.text = so_text(store); sargs.text.src = CharStar; sargs.text.desc = _("Quota Resources Summary"); sargs.bar.title = _("QUOTA SUMMARY"); sargs.proc.tool = NULL; sargs.help.text = h_quota_command; sargs.help.title = NULL; sargs.keys.menu = NULL; setbitmap(sargs.keys.bitmap); scrolltool(&sargs); so_give(&store); if (state->quota) mail_free_quotalist(&(state->quota)); } /*---------------------------------------------------------------------- Prompt the user for the type of sort he desires Args: state -- pine state pointer q1 -- Line to prompt on Returns 0 if it was cancelled, 1 otherwise. ----*/ int select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) { char prompt[200], tmp[3], *p; int s, i; int deefault = 'a', retval = 1; HelpType help; ESCKEY_S sorts[14]; #ifdef _WINDOWS DLG_SORTPARAM sortsel; if (mswin_usedialog ()) { sortsel.reverse = mn_get_revsort (state->msgmap); sortsel.cursort = mn_get_sort (state->msgmap); /* assumption here that HelpType is char ** */ sortsel.helptext = h_select_sort; sortsel.rval = 0; if ((retval = os_sortdialog (&sortsel))) { *sort = sortsel.cursort; *rev = sortsel.reverse; } return (retval); } #endif /*----- String together the prompt ------*/ tmp[1] = '\0'; if(F_ON(F_USE_FK,ps_global)) strncpy(prompt, _("Choose type of sort : "), sizeof(prompt)); else strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "), sizeof(prompt)); for(i = 0; state->sort_types[i] != EndofList; i++) { sorts[i].rval = i; p = sorts[i].label = sort_name(state->sort_types[i]); while(*(p+1) && islower((unsigned char)*p)) p++; sorts[i].ch = tolower((unsigned char)(tmp[0] = *p)); sorts[i].name = cpystr(tmp); if(mn_get_sort(state->msgmap) == state->sort_types[i]) deefault = sorts[i].rval; } sorts[i].ch = 'r'; sorts[i].rval = 'r'; sorts[i].name = cpystr("R"); if(F_ON(F_USE_FK,ps_global)) sorts[i].label = N_("Reverse"); else sorts[i].label = ""; sorts[++i].ch = -1; help = h_select_sort; if((F_ON(F_USE_FK,ps_global) && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x', help,RB_NORM)) != 'x')) || (F_OFF(F_USE_FK,ps_global) && ((s = radio_buttons(prompt,ql,sorts,deefault,'x', help,RB_NORM)) != 'x'))){ state->mangled_body = 1; /* signal screen's changed */ if(s == 'r') *rev = !mn_get_revsort(state->msgmap); else *sort = state->sort_types[s]; if(F_ON(F_SHOW_SORT, ps_global)) ps_global->mangled_header = 1; } else{ retval = 0; cmd_cancelled("Sort"); } while(--i >= 0) fs_give((void **)&sorts[i].name); blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); return(retval); } /*--------------------------------------------------------------------- Build list of folders in the given context for user selection Args: c -- pointer to pointer to folder's context context f -- folder prefix to display sublist -- whether or not to use 'f's contents as prefix lister -- function used to do the actual display Returns: malloc'd string containing sequence, else NULL if no messages in msgmap with local "selected" flag. ----*/ int display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int)) { int rc; CONTEXT_S *tc; void (*redraw)(void) = ps_global->redrawer; push_titlebar_state(); tc = *c; if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0) *c = tc; ClearScreen(); pop_titlebar_state(); redraw_titlebar(); if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */ (*ps_global->redrawer)(); if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global)) return(1); return(0); } /* * Allow user to choose a single item from a list of strings. * * Args list -- Array of strings to choose from, NULL terminated. * displist -- Array of strings to display instead of displaying list. * Indices correspond to the list array. Display the displist * but return the item from list if displist non-NULL. * title -- For conf_scroll_screen * pdesc -- For conf_scroll_screen * help -- For conf_scroll_screen * htitle -- For conf_scroll_screen * * Returns an allocated copy of the chosen item or NULL. */ char * choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help, char *htitle, char *cursor_location) { LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL; char **t, **dl; char *ret = NULL, *choice = NULL; /* build the LIST_SEL_S list */ p = listhead = NULL; for(t = list, dl = displist; *t; t++, dl++){ ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); memset(ls, 0, sizeof(*ls)); ls->item = cpystr(*t); if(displist) ls->display_item = cpystr(*dl); if(cursor_location && (cursor_location == (*t))) starting_val = ls; if(p){ p->next = ls; p = p->next; } else listhead = p = ls; } if(!listhead) return(ret); if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc, help, htitle, starting_val)) for(p = listhead; !choice && p; p = p->next) if(p->selected) choice = p->item; if(choice) ret = cpystr(choice); free_list_sel(&listhead); return(ret); } void free_list_sel(LIST_SEL_S **lsel) { if(lsel && *lsel){ free_list_sel(&(*lsel)->next); if((*lsel)->item) fs_give((void **) &(*lsel)->item); if((*lsel)->display_item) fs_give((void **) &(*lsel)->display_item); fs_give((void **) lsel); } } /* * file_lister - call pico library's file lister */ int file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags) { PICO pbf; int rv; void (*redraw)(void) = ps_global->redrawer; standard_picobuf_setup(&pbf); push_titlebar_state(); if(!newmail) pbf.newmail = NULL; /* BUG: what about help command and text? */ pbf.pine_anchor = title; rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags); standard_picobuf_teardown(&pbf); fix_windsize(ps_global); init_signals(); /* has it's own signal stuff */ /* Restore display's titlebar and body */ pop_titlebar_state(); redraw_titlebar(); if((ps_global->redrawer = redraw) != NULL) (*ps_global->redrawer)(); return(rv); } /*---------------------------------------------------------------------- Print current folder index ---*/ int print_index(struct pine *state, MSGNO_S *msgmap, int agg) { long i; ICE_S *ice; char buf[MAX_SCREEN_COLS+1]; for(i = 1L; i <= mn_get_total(msgmap); i++){ if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT)) continue; if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0)) continue; ice = build_header_line(state, state->mail_stream, msgmap, i, NULL); if(ice){ /* * I don't understand why we'd want to mark the current message * instead of printing out the first character of the status * so I'm taking it out and including the first character of the * line instead. Hubert 2006-02-09 * if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' ')) return(0); */ if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i), print_char) || !gf_puts(NEWLINE, print_char)) return(0); } } return(1); } #ifdef _WINDOWS /* * windows callback to get/set header mode state */ int header_mode_callback(set, args) int set; long args; { return(ps_global->full_header); } /* * windows callback to get/set zoom mode state */ int zoom_mode_callback(set, args) int set; long args; { return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0); } /* * windows callback to get/set zoom mode state */ int any_selected_callback(set, args) int set; long args; { return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0); } /* * */ int flag_callback(set, flags) int set; long flags; { MESSAGECACHE *mc; int newflags = 0; long msgno; int permflag = 0; switch (set) { case 1: /* Important */ permflag = ps_global->mail_stream->perm_flagged; break; case 2: /* New */ permflag = ps_global->mail_stream->perm_seen; break; case 3: /* Answered */ permflag = ps_global->mail_stream->perm_answered; break; case 4: /* Deleted */ permflag = ps_global->mail_stream->perm_deleted; break; } if(!(any_messages(ps_global->msgmap, NULL, "to Flag") && can_set_flag(ps_global, "flag", permflag))) return(0); if(sp_io_error_on_stream(ps_global->mail_stream)){ sp_set_io_error_on_stream(ps_global->mail_stream, 0); pine_mail_check(ps_global->mail_stream); /* forces write */ return(0); } msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap)); if(msgno > 0L && ps_global->mail_stream && msgno <= ps_global->mail_stream->nmsgs && (mc = mail_elt(ps_global->mail_stream, msgno)) && mc->valid){ /* * NOTE: code below is *VERY* sensitive to the order of * the messages defined in resource.h for flag handling. * Don't change it unless you know what you're doing. */ if(set){ char *flagstr; long mflag; switch(set){ case 1 : /* Important */ flagstr = "\\FLAGGED"; mflag = (mc->flagged) ? 0L : ST_SET; break; case 2 : /* New */ flagstr = "\\SEEN"; mflag = (mc->seen) ? 0L : ST_SET; break; case 3 : /* Answered */ flagstr = "\\ANSWERED"; mflag = (mc->answered) ? 0L : ST_SET; break; case 4 : /* Deleted */ flagstr = "\\DELETED"; mflag = (mc->deleted) ? 0L : ST_SET; break; default : /* bogus */ return(0); } mail_flag(ps_global->mail_stream, long2string(msgno), flagstr, mflag); if(ps_global->redrawer) (*ps_global->redrawer)(); } else{ /* Important */ if(mc->flagged) newflags |= 0x0001; /* New */ if(!mc->seen) newflags |= 0x0002; /* Answered */ if(mc->answered) newflags |= 0x0004; /* Deleted */ if(mc->deleted) newflags |= 0x0008; } } return(newflags); } /* * BUG: Should teach this about keywords */ MPopup * flag_submenu(mc) MESSAGECACHE *mc; { static MPopup flag_submenu[] = { {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}}, {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}}, {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}}, {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}}, {tTail} }; /* Important */ flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal; /* New */ flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked; /* Answered */ flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal; /* Deleted */ flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal; return(flag_submenu); } #endif /* _WINDOWS */