diff options
author | Eduardo Chappa <chappa@washington.edu> | 2013-05-31 17:08:22 -0600 |
---|---|---|
committer | Eduardo Chappa <chappa@washington.edu> | 2013-05-31 17:08:22 -0600 |
commit | 81e994d7907f850506ddc248f84761a54995e58c (patch) | |
tree | 3bc4993b48ddeec45dee51323437200ab975887c | |
parent | 077522d7e058133f9de99d0d74481566b21c5a98 (diff) | |
download | alpine-81e994d7907f850506ddc248f84761a54995e58c.tar.xz |
* Fix not allow remote execution by adding PIPE_NOSHELL to the opening of a url by
a browser.
123 files changed, 12257 insertions, 908 deletions
diff --git a/README.maildir b/README.maildir new file mode 100644 index 00000000..b4ac49b1 --- /dev/null +++ b/README.maildir @@ -0,0 +1,149 @@ +--------------------------------------- + +Maildir Driver for Alpine 2.0 +By Eduardo Chappa +<chappa@gmx.com> + +--------------------------------------- +1. General Information About This Patch +--------------------------------------- + +This patch adds support for the maildir format to Alpine. We take the +approach that this patch is one more driver among the number of formats +supported by Alpine (more generally c-client). This approach differs from +older versions of similar patches, in that once a maildir patch was +applied, it was assumed that all your folders would be created in the +maildir format. + +This patch does not assume that maildir is a preferred format, instead +puts maildir in equal footing with other formats (mbox, mbx, mix, etc), +and so a maildir folder in the mail/ collection is treated in the same way +as any other folder in any other format. In other words, just by reading +the name of a folder, or opening it, or doing any operation with it, you +can not know in which format the folder is. + +This implies that if you want to add a folder in the maildir format to the +mail/ collection, then you must add by pressing "A" in the folder list +collection and enter "#driver.md/mail/name_maildir_folder". + +If you only want to use maildir, however, you can do so too. In this case, +you must create a maildir collection. In that collection, only maildir +folders will be listed. If there is any folder in any other format, that +folder will be ignored. In another words, any folder listed there is in +maildir format and can be accessed through that collection, conversely, +any folder not listed there is not in maildir format and there is no way +to access it using this collection. + +In order to create a maildir collection, you could press M S L, and "A" to +add a collection. Fill in the required fields as follows: + +Nickname : Anything +Server : +Path : #md/relative/path/to/maildir/collection/ +View : + +For example, if "path" is set to "#md/mail/", then Alpine will look for your +maildir folders that are in ~/mail/. + +The code in this patch is mostly based in code for the unix driver plus +some combinations of the mh, mbx and nntp drivers for the c-client +library. Those drivers were designed by Mark Crispin, and bugs in this +code are not his bugs, but my own. + + I got all the specification for this patch from +http://cr.yp.to/proto/maildir.html. If you know of a place with a better +specification for maildir format please let me know. The method this patch +uses to create a unique filename for a message is one of the "old +fashioned" methods. I realize that this is old fashioned, but it is +portable, and portability is the main reason why I decided to use an old +fashioned method (most methods are not portable. See the word +"Unfortunately" in that document). + +-------------- +2. Other Goals +-------------- + + It is intended that this code will work well with any application +written using the c-client library. Of paramount importance is to make the +associated imap server work well when the server accesses a folder in +Maildir format. The program mailutil should also work flawlessly with this +implemetation of the driver. + + It is intended that this driver be fast and stable. We intend not to +patch Alpine to make this driver do its work, unless such patching is for +fixing bugs in Alpine or to pass parameters to the driver. + +------------------------------------------------------------------------ +3. What are the known bugs of this implementation of the Maildir driver? +------------------------------------------------------------------------ + + I don't know any at this time. There have been bugs before, though, but +I try to fix bugs as soon as they are reported. + +---------- +4. On UIDs +---------- + + This patch keeps uids in the name of the file that contains the message, +by adding a ",u=" string to the file name to save the uid of a message. A +file is kept between sessions to save information on the last uid assigned +and its time of validity. Only one session with writing access can write +uids, all others must wait for the other session to assign them. The +session assigning uids creates a ".uidtemp" file which other sessions must +not disturb. + + Uid support appeared in Alpine 1.00 (snapshot 925), and is experimental, +please report any problems. + +---------------------------------------------- +5. Configuring Alpine and Setting up a Maildir +---------------------------------------------- + +Once this approach was chosen, it implied the following: + + * This patch assumes that your INBOX is located at "$HOME/Maildir". + This is a directory which should have three subdirectories "cur", + "tmp" and "new". Mail is delivered to 'new' and read from 'cur'. I + have added a configuration option "maildir-location" which can be + used to tell Alpine where your Maildir inbox is, in case your system + does not use the above directory (e.g. your system may use + "~/.maildir"). In this case define that variable to be the name of + the directory where your e-mail is being delivered (e.g. + ".maildir"). + + * If you want to use the above configuration as your inbox, you must + define your inbox-path as "#md/inbox" (no quotes). You can define + the inbox-path like above even if you have changed the + maildir-location variable. That's the whole point of that variable. + +------------------------------------------- +6. What about Courier/Dovecot file systems? +------------------------------------------- + +In a courier file system all folders are subfolders of a root folder +called INBOX. Normally INBOX is located at ~/Maildir and subfolders are +"dot" directories in ~/Maildir. For example ~/Maildir/.Trash is a +subfolder of INBOX and is accessed with the nickname "INBOX.Trash". + +You can not access folders in this way unless you preceed them with the +string "#mc/". The purpose of the string "#mc/" is to warn Alpine that a +collection in the Courier format is going to be accessed. Therefore, you +can SELECT a folder like "#mc/INBOX.Trash", but not "INBOX.Trash" + +You can access a collection through a server, but if you want to access a +collection of folders created using the Courier server, you MUST edit your +".pinerc" file and enter the definition of the collection as follows: + +folder-collections="Anything you want" #mc/INBOX.[] + +You can replace the string "#mc/INBOX." by something different, for example +"#mc/Courier/." will make Alpine search for your collection in ~/Courier. + +You can not add this setting directly into Alpine because Alpine fails to +accept this value from its input, but it takes it correctly when it is +added through the ".pinerc" file. + +You can access your inbox as "#mc/INBOX" or "#md/INBOX". Both definitions +point to the same place. + +Last Updated May 28, 2011 diff --git a/alpine/Makefile.am b/alpine/Makefile.am index c80a97ff..76bef8e3 100644 --- a/alpine/Makefile.am +++ b/alpine/Makefile.am @@ -46,3 +46,4 @@ CLEANFILES = date.c date.c: echo "char datestamp[]="\"`date`\"";" > date.c echo "char hoststamp[]="\"`hostname`\"";" >> date.c + cat ../patchlevel >> date.c diff --git a/alpine/Makefile.in b/alpine/Makefile.in index 4bb886ec..09719265 100644 --- a/alpine/Makefile.in +++ b/alpine/Makefile.in @@ -817,6 +817,7 @@ uninstall-am: uninstall-binPROGRAMS date.c: echo "char datestamp[]="\"`date`\"";" > date.c echo "char hoststamp[]="\"`hostname`\"";" >> date.c + cat ../patchlevel >> date.c # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. diff --git a/alpine/adrbkcmd.c b/alpine/adrbkcmd.c index 9320160d..ab3c981b 100644 --- a/alpine/adrbkcmd.c +++ b/alpine/adrbkcmd.c @@ -4128,6 +4128,8 @@ ab_compose_internal(BuildTo bldto, int allow_role) * 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 (ps_global->role) + fs_give((void **)&ps_global->role); if(role) role = copy_action(role); else{ @@ -4135,6 +4137,7 @@ ab_compose_internal(BuildTo bldto, int allow_role) memset((void *)role, 0, sizeof(*role)); role->nick = cpystr("Default Role"); } + ps_global->role = cpystr(role->nick); } compose_mail(addr, fcc, role, NULL, NULL); diff --git a/alpine/alpine.c b/alpine/alpine.c index 9e7298f2..838473ec 100644 --- a/alpine/alpine.c +++ b/alpine/alpine.c @@ -308,6 +308,7 @@ main(int argc, char **argv) mail_parameters(NULL, SET_SENDCOMMAND, (void *) pine_imap_cmd_happened); mail_parameters(NULL, SET_FREESTREAMSPAREP, (void *) sp_free_callback); mail_parameters(NULL, SET_FREEELTSPAREP, (void *) free_pine_elt); + mail_parameters(NULL, SET_ERASEPASSWORD, (void *) pine_delete_pwd); #ifdef SMIME mail_parameters(NULL, SET_FREEBODYSPAREP, (void *) free_smime_body_sparep); #endif @@ -459,6 +460,11 @@ main(int argc, char **argv) convert_args_to_utf8(pine_state, &args); + if (args.action == aaFolder && !args.data.folder && + ps_global->send_immediately){ + printf(_("No value for To: field specified\n")); + exit(-1); + } if(args.action == aaFolder){ pine_state->beginning_of_month = first_run_of_month(); pine_state->beginning_of_year = first_run_of_year(); @@ -467,6 +473,7 @@ main(int argc, char **argv) /* Set up optional for user-defined display filtering */ pine_state->tools.display_filter = dfilter; pine_state->tools.display_filter_trigger = dfilter_trigger; + pine_state->tools.exec_rule = exec_function_rule; #ifdef _WINDOWS if(ps_global->install_flag){ @@ -558,6 +565,11 @@ main(int argc, char **argv) if(F_ON(F_MAILDROPS_PRESERVE_STATE, ps_global)) mail_parameters(NULL, SET_SNARFPRESERVE, (void *) TRUE); +#ifndef _WINDOWS + mail_parameters(NULL,SET_COURIERSTYLE, + (void *)(F_ON(F_COURIER_FOLDER_LIST, ps_global) ? 1 : 0)); +#endif + rvl = 0L; if(pine_state->VAR_NNTPRANGE){ if(!SVAR_NNTPRANGE(pine_state, rvl, tmp_20k_buf, SIZEOF_20KBUF)) @@ -677,6 +689,7 @@ main(int argc, char **argv) /*--- output side ---*/ + if (!ps_global->send_immediately){ rv = config_screen(&(pine_state->ttyo)); #ifndef _WINDOWS /* always succeeds under _WINDOWS */ if(rv){ @@ -717,12 +730,6 @@ main(int argc, char **argv) /* initialize titlebar in case we use it */ set_titlebar("", NULL, NULL, NULL, NULL, 0, FolderName, 0, 0, NULL); - /* - * Prep storage object driver for PicoText - */ - so_register_external_driver(pine_pico_get, pine_pico_give, pine_pico_writec, pine_pico_readc, - pine_pico_puts, pine_pico_seek, NULL, NULL); - #ifdef DEBUG if(ps_global->debug_imap > 4 || debug > 9){ q_status_message(SM_ORDER | SM_DING, 5, 9, @@ -730,6 +737,19 @@ main(int argc, char **argv) flush_status_messages(0); } #endif + } + else{ + fake_config_screen(&(pine_state->ttyo)); + init_folders(pine_state); /* digest folder spec's */ + } + + /* + * Prep storage object driver for PicoText + */ + so_register_external_driver(pine_pico_get, pine_pico_give, + (args.noutf8 == 0 ? pine_pico_writec : pine_pico_writec_noucs), + (args.noutf8 == 0 ? pine_pico_readc : pine_pico_readc_noucs), + (args.noutf8 == 0 ? pine_pico_puts : pine_pico_puts_noucs), pine_pico_seek, NULL, NULL); if(args.action == aaPrcCopy || args.action == aaAbookCopy){ int exit_val = -1; @@ -905,6 +925,12 @@ main(int argc, char **argv) int len, good_addr = 1; int exit_val = 0; BUILDER_ARG fcc; + ACTION_S *role = NULL; + + if (pine_state->in_init_seq && pine_state->send_immediately + && (char) *pine_state->initial_cmds++ == '#' + && ++pine_state->initial_cmds_offset) + role_select_screen(pine_state, &role, 1); if(pine_state->in_init_seq){ pine_state->in_init_seq = pine_state->save_in_init_seq = 0; @@ -943,7 +969,7 @@ main(int argc, char **argv) memset(&fcc, 0, sizeof(fcc)); if(good_addr){ - compose_mail(addr, fcc.tptr, NULL, + compose_mail(addr, fcc.tptr, role, args.data.mail.attachlist, stdin_getc); } else{ @@ -977,6 +1003,7 @@ main(int argc, char **argv) pine_state->mail_stream = NULL; pine_state->mangled_screen = 1; + pine_state->subject = NULL; if(args.action == aaURL){ url_tool_t f; @@ -1092,6 +1119,7 @@ main(int argc, char **argv) } } + if (!pine_state->send_immediately) fflush(stdout); #if !defined(_WINDOWS) && !defined(LEAVEOUTFIFO) @@ -2980,10 +3008,15 @@ process_init_cmds(struct pine *ps, char **list) if(i > 0){ ps->initial_cmds = (int *)fs_get((i+1) * sizeof(int)); ps->free_initial_cmds = ps->initial_cmds; + ps->initial_cmds_backup = (int *)fs_get((i+1) * sizeof(int)); + ps->free_initial_cmds_backup = ps->initial_cmds_backup; for(j = 0; j < i; j++) - ps->initial_cmds[j] = i_cmds[j]; - - ps->initial_cmds[i] = 0; + ps->initial_cmds[j] = ps->initial_cmds_backup[j] = i_cmds[j]; +#define ctrl_x 24 + if (i > 1) + ps->send_immediately = i_cmds[i - 2] == ctrl_x + && ((i_cmds[i - 1] == 'y') || (i_cmds[i-1] == 'Y')); + ps->initial_cmds[i] = ps->initial_cmds_backup[i] = 0; ps->in_init_seq = ps->save_in_init_seq = 1; } } @@ -3139,6 +3172,9 @@ goodnight_gracey(struct pine *pine_state, int exit_val) extern KBESC_T *kbesc; dprint((2, "goodnight_gracey:\n")); + strncpy(pine_state->cur_folder, pine_state->inbox_name, + sizeof(pine_state->cur_folder)); + pine_state->cur_folder[sizeof(pine_state->cur_folder) - 1] = '\0'; /* We want to do this here before we close up the streams */ trim_remote_adrbks(); diff --git a/alpine/arg.c b/alpine/arg.c index e23853c4..315649ba 100644 --- a/alpine/arg.c +++ b/alpine/arg.c @@ -60,6 +60,7 @@ static char args_err_missing_passfile[] = N_("missing argument for option \"-pas static char args_err_non_abs_passfile[] = N_("argument to \"-passfile\" should be fully-qualified"); #endif static char args_err_missing_sort[] = N_("missing argument for option \"-sort\""); +static char args_err_missing_thread_sort[] = N_("missing argument for option \"-threadsort\""); static char args_err_missing_flag_arg[] = N_("missing argument for flag \"%c\""); static char args_err_missing_flag_num[] = N_("Non numeric argument for flag \"%c\""); static char args_err_missing_debug_num[] = N_("Non numeric argument for \"%s\""); @@ -103,11 +104,13 @@ N_(" -k \t\tKeys - Force use of function keys"), N_(" -z \t\tSuspend - allow use of ^Z suspension"), N_(" -r \t\tRestricted - can only send mail to oneself"), N_(" -sort <sort>\tSort - Specify sort order of folder:"), +N_(" -threadsort <sort>\tSort - Specify sort order of thread index screen:"), N_("\t\t\tarrival, subject, threaded, orderedsubject, date,"), N_("\t\t\tfrom, size, score, to, cc, /reverse"), N_(" -i\t\tIndex - Go directly to index, bypassing main menu"), N_(" -I <keystroke_list> Initial keystrokes to be executed"), N_(" -n <number>\tEntry in index to begin on"), +N_(" -noutf8\t Warns Alpine that piped input is not encoded in utf-8"), N_(" -o \t\tReadOnly - Open first folder read-only"), N_(" -conf\t\tConfiguration - Print out fresh global configuration. The"), N_("\t\tvalues of your global configuration affect all Alpine users"), @@ -192,6 +195,7 @@ pine_args(struct pine *pine_state, int argc, char **argv, ARGDATA_S *args) char *cmd_list = NULL; char *debug_str = NULL; char *sort = NULL; + char *threadsort = NULL; char *pinerc_file = NULL; char *lc = NULL; int do_help = 0; @@ -363,6 +367,17 @@ Loop: while(--ac > 0) goto Loop; } + else if(strcmp(*av, "threadsort") == 0){ + if(--ac){ + threadsort = *++av; + COM_THREAD_SORT_KEY = cpystr(threadsort); + } + else{ + display_args_err(_(args_err_missing_thread_sort), NULL, 1); + ++usage; + } + goto Loop; + } else if(strcmp(*av, "url") == 0){ if(args->action == aaFolder && !args->data.folder){ args->action = aaURL; @@ -469,6 +484,10 @@ Loop: while(--ac > 0) goto Loop; } + else if(strcmp(*av, "noutf8") == 0){ + args->noutf8++; + goto Loop; + } else if(strcmp(*av, "bail") == 0){ pine_state->exit_if_no_pinerc = 1; goto Loop; @@ -477,6 +496,12 @@ Loop: while(--ac > 0) do_version = 1; goto Loop; } + else if(strcmp(*av, "subject") == 0){ + if(--ac){ + pine_state->subject = cpystr(*++av); + } + goto Loop; + } #ifdef _WINDOWS else if(strcmp(*av, "install") == 0){ pine_state->install_flag = 1; @@ -827,14 +852,14 @@ Loop: while(--ac > 0) exit(-1); if(do_version){ - extern char datestamp[], hoststamp[]; + extern char datestamp[], hoststamp[], plevstamp[]; char rev[128]; - snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Alpine %s (%s %s) built %s on %s", + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Alpine %s (%s %s) built %s on %s, using patchlevel %s.", ALPINE_VERSION, SYSTYPE ? SYSTYPE : "?", get_alpine_revision_string(rev, sizeof(rev)), - datestamp, hoststamp); + datestamp, hoststamp, plevstamp); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; display_args_err(tmp_20k_buf, NULL, 0); exit(0); diff --git a/alpine/arg.h b/alpine/arg.h index 339e793b..4faddfe9 100644 --- a/alpine/arg.h +++ b/alpine/arg.h @@ -27,6 +27,7 @@ typedef struct argdata { enum {aaFolder = 0, aaMore, aaURL, aaMail, aaPrcCopy, aaAbookCopy} action; + int noutf8; union { char *folder; char *file; diff --git a/alpine/busy.c b/alpine/busy.c index a0c71ab0..b2fd1869 100644 --- a/alpine/busy.c +++ b/alpine/busy.c @@ -226,7 +226,7 @@ busy_cue(char *msg, percent_done_t pc_f, int delay) add_review_message(buf, -1); } - else{ + else if (!ps_global->send_immediately){ q_status_message(SM_ORDER, 0, 1, progress); /* @@ -238,8 +238,8 @@ busy_cue(char *msg, percent_done_t pc_f, int delay) */ display_message('x'); } - - fflush(stdout); + if (!ps_global->send_immediately) + fflush(stdout); } /* @@ -287,7 +287,8 @@ busy_cue(char *msg, percent_done_t pc_f, int delay) (*ap)->cf = done_busy_cue; ap = &(*ap)->next; - start_after(a); /* launch cue handler */ + if(!ps_global->send_immediately) + start_after(a); /* launch cue handler */ #ifdef _WINDOWS mswin_setcursor(MSWIN_CURSOR_BUSY); @@ -436,6 +437,11 @@ done_busy_cue(void *data) { int space_left, slots_used; + if (ps_global->send_immediately){ + mark_status_dirty(); + return; + } + if(final_message && final_message_pri >= 0){ char progress[MAX_SCREEN_COLS+1]; diff --git a/alpine/confscroll.c b/alpine/confscroll.c index b4aa3218..11473d27 100644 --- a/alpine/confscroll.c +++ b/alpine/confscroll.c @@ -51,6 +51,7 @@ static char rcsid[] = "$Id: confscroll.c 1169 2008-08-27 06:42:06Z hubert@u.wash #include "../pith/tempfile.h" #include "../pith/pattern.h" #include "../pith/charconv/utf8.h" +#include "../pith/rules.h" #define CONFIG_SCREEN_HELP_TITLE _("HELP FOR SETUP CONFIGURATION") @@ -139,7 +140,7 @@ char *yesno_pretty_value(struct pine *, CONF_S *); char *radio_pretty_value(struct pine *, CONF_S *); char *sigfile_pretty_value(struct pine *, CONF_S *); char *color_pretty_value(struct pine *, CONF_S *); -char *sort_pretty_value(struct pine *, CONF_S *); +char *sort_pretty_value(struct pine *, CONF_S *, int); int longest_feature_name(void); COLOR_PAIR *sample_color(struct pine *, struct variable *); COLOR_PAIR *sampleexc_color(struct pine *, struct variable *); @@ -287,7 +288,8 @@ set_radio_pretty_vals(struct pine *ps, CONF_S **cl) CONF_S *ctmp; if(!(cl && *cl && - ((*cl)->var == &ps->vars[V_SORT_KEY] || + (((*cl)->var == &ps->vars[V_SORT_KEY]) || + ((*cl)->var == &ps->vars[V_THREAD_SORT_KEY]) || standard_radio_var(ps, (*cl)->var) || (*cl)->var == startup_ptr))) return; @@ -463,7 +465,7 @@ conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, ch char tmp[MAXPATH+1]; char *utf8str; UCS ch = 'x'; - int cmd, i, j, done = 0, changes = 0; + int cmd, i, j, k = 1, done = 0, changes = 0; int retval = 0; int km_popped = 0, stay_in_col = 0; struct key_menu *km = NULL; @@ -512,6 +514,7 @@ conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, ch } /*----------- Check for new mail -----------*/ + if (!ps->send_immediately){ if(new_mail(0, NM_TIMING(ch), NM_STATUS_MSG | NM_DEFER_SORT) >= 0) ps->mangled_header = 1; @@ -541,6 +544,7 @@ conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, ch mark_status_unknown(); } + } /* send_immediately */ if(ps->mangled_footer || km != screen->current->keymenu){ bitmap_t bitmap; @@ -612,6 +616,7 @@ conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, ch } } + if(!ps_global->send_immediately){ MoveCursor(cursor_pos.row, cursor_pos.col); #ifdef MOUSE mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); /* prime the handler */ @@ -630,6 +635,14 @@ conf_scroll_screen(struct pine *ps, OPT_SCREEN_S *screen, CONF_S *start_line, ch #ifdef _WINDOWS mswin_setscrollcallback(NULL); #endif + } /* send_immediately */ + + if (ps->send_immediately){ + ps_global->dont_use_init_cmds = 0; + process_config_input(&ch); + if (ch == '\030') /* ^X, bye */ + goto end; + } cmd = menu_command(ch, km); @@ -1383,7 +1396,7 @@ no_down: break; } } - +end: screen->current = first_confline(screen->current); free_conflines(&screen->current); return(retval); @@ -1552,6 +1565,10 @@ text_toolit(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags, int look_ lowrange = 1; hirange = MAX_FILLCOL; } + else if((*cl)->var == &ps->vars[V_SLEEP]){ + lowrange = 0; + hirange = 120; + } else if((*cl)->var == &ps->vars[V_OVERLAP] || (*cl)->var == &ps->vars[V_MARGIN]){ lowrange = 0; @@ -2427,6 +2444,9 @@ delete: * Now go and set the current_val based on user_val changes * above. Turn off command line settings... */ + set_current_val((*cl)->var, + (strcmp((*cl)->var->name,"key-definition-rules") ? TRUE : FALSE), + FALSE); set_current_val((*cl)->var, TRUE, FALSE); fix_side_effects(ps, (*cl)->var, 0); @@ -2923,7 +2943,7 @@ radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags) } set_current_val((*cl)->var, TRUE, TRUE); - if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev) != -1){ + if(decode_sort(ps->VAR_SORT_KEY, &def_sort, &def_sort_rev,0) != -1){ ps->def_sort = def_sort; ps->def_sort_rev = def_sort_rev; } @@ -2932,6 +2952,37 @@ radiobutton_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags) ps->mangled_body = 1; /* BUG: redraw it all for now? */ rv = 1; } + else if((*cl)->var == &ps->vars[V_THREAD_SORT_KEY]){ + SortOrder thread_def_sort; + int thread_def_sort_rev; + + thread_def_sort_rev = (*cl)->varmem >= (short) EndofList; + thread_def_sort = (SortOrder) ((*cl)->varmem - (thread_def_sort_rev + * EndofList)); + sprintf(tmp_20k_buf, "%s%s", sort_name(thread_def_sort), + (thread_def_sort_rev) ? "/Reverse" : ""); + + if((*cl)->var->cmdline_val.p) + fs_give((void **)&(*cl)->var->cmdline_val.p); + + if(apval){ + if(*apval) + fs_give((void **)apval); + + *apval = cpystr(tmp_20k_buf); + } + + set_current_val((*cl)->var, TRUE, TRUE); + if(decode_sort(ps->VAR_THREAD_SORT_KEY, &thread_def_sort, + &thread_def_sort_rev, 1) != -1){ + ps->thread_def_sort = thread_def_sort; + ps->thread_def_sort_rev = thread_def_sort_rev; + } + + set_radio_pretty_vals(ps, cl); + ps->mangled_body = 1; /* BUG: redraw it all for now? */ + rv = 1; + } else q_status_message(SM_ORDER | SM_DING, 3, 6, "Programmer botch! Unknown radiobutton type."); @@ -3794,7 +3845,9 @@ pretty_value(struct pine *ps, CONF_S *cl) else if(standard_radio_var(ps, v) || v == startup_ptr) return(radio_pretty_value(ps, cl)); else if(v == &ps->vars[V_SORT_KEY]) - return(sort_pretty_value(ps, cl)); + return(sort_pretty_value(ps, cl, 0)); + else if(v == &ps->vars[V_THREAD_SORT_KEY]) + return(sort_pretty_value(ps, cl, 1)); else if(v == &ps->vars[V_SIGNATURE_FILE]) return(sigfile_pretty_value(ps, cl)); else if(v == &ps->vars[V_USE_ONLY_DOMAIN_NAME]) @@ -4325,14 +4378,14 @@ color_pretty_value(struct pine *ps, CONF_S *cl) char * -sort_pretty_value(struct pine *ps, CONF_S *cl) +sort_pretty_value(struct pine *ps, CONF_S *cl, int thread) { - return(generalized_sort_pretty_value(ps, cl, 1)); + return(generalized_sort_pretty_value(ps, cl, 1, thread)); } char * -generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) +generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok, int thread) { char tmp[6*MAXPATH]; char *pvalnorm, *pvalexc, *pval; @@ -4382,7 +4435,7 @@ generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) } else if(fixed){ pval = v->fixed_val.p; - decode_sort(pval, &var_sort, &var_sort_rev); + decode_sort(pval, &var_sort, &var_sort_rev, thread); is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", @@ -4393,9 +4446,9 @@ generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) is_the_one ? " (value is fixed)" : ""); } else if(is_set_for_this_level){ - decode_sort(pval, &var_sort, &var_sort_rev); + decode_sort(pval, &var_sort, &var_sort_rev, thread); is_the_one = (var_sort_rev == line_sort_rev && var_sort == line_sort); - decode_sort(pvalexc, &exc_sort, &exc_sort_rev); + decode_sort(pvalexc, &exc_sort, &exc_sort_rev, thread); the_exc_one = (editing_normal_which_isnt_except && pvalexc && exc_sort_rev == line_sort_rev && exc_sort == line_sort); utf8_snprintf(tmp, sizeof(tmp), "(%c) %s%-*w%*s%s", @@ -4413,7 +4466,7 @@ generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) } else{ if(pvalexc){ - decode_sort(pvalexc, &exc_sort, &exc_sort_rev); + decode_sort(pvalexc, &exc_sort, &exc_sort_rev, thread); is_the_one = (exc_sort_rev == line_sort_rev && exc_sort == line_sort); utf8_snprintf(tmp, sizeof(tmp), "( ) %s%-*w%*s%s", @@ -4424,7 +4477,7 @@ generalized_sort_pretty_value(struct pine *ps, CONF_S *cl, int default_ok) } else{ pval = v->current_val.p; - decode_sort(pval, &var_sort, &var_sort_rev); + decode_sort(pval, &var_sort, &var_sort_rev, thread); is_the_one = ((pval || default_ok) && var_sort_rev == line_sort_rev && var_sort == line_sort); @@ -5161,6 +5214,30 @@ fix_side_effects(struct pine *ps, struct variable *var, int revert) var == &ps->vars[V_ABOOK_FORMATS]){ addrbook_reset(); } + else if(var == &ps->vars[V_INDEX_RULES]){ + if(ps_global->rule_list) + free_parsed_rule_list(&ps_global->rule_list); + create_rule_list(ps->vars); + reset_index_format(); + clear_index_cache(ps->mail_stream, 0); + } + else if(var == &ps->vars[V_COMPOSE_RULES] || + var == &ps->vars[V_FORWARD_RULES] || + var == &ps->vars[V_KEY_RULES] || + var == &ps->vars[V_REPLACE_RULES] || + var == &ps->vars[V_REPLY_INDENT_RULES] || + var == &ps->vars[V_REPLY_LEADIN_RULES] || + var == &ps->vars[V_RESUB_RULES] || + var == &ps->vars[V_SAVE_RULES] || + var == &ps->vars[V_SMTP_RULES] || + var == &ps->vars[V_SORT_RULES] || + var == &ps->vars[V_STARTUP_RULES] || + var == &ps->vars[V_THREAD_DISP_STYLE_RULES] || + var == &ps->vars[V_THREAD_INDEX_STYLE_RULES]){ + if(ps_global->rule_list) + free_parsed_rule_list(&ps_global->rule_list); + create_rule_list(ps->vars); + } else if(var == &ps->vars[V_INDEX_FORMAT]){ reset_index_format(); clear_index_cache(ps->mail_stream, 0); @@ -5183,6 +5260,9 @@ fix_side_effects(struct pine *ps, struct variable *var, int revert) clear_index_cache(ps->mail_stream, 0); } + else if(var == &ps->vars[V_SPECIAL_TEXT]){ + regex_pattern(ps->VAR_SPECIAL_TEXT); + } else if(var == &ps->vars[V_INIT_CMD_LIST]){ if(!revert) q_status_message(SM_ASYNC, 0, 3, @@ -5215,6 +5295,16 @@ fix_side_effects(struct pine *ps, struct variable *var, int revert) else ps->viewer_overlap = old_value; } + else if(var == &ps->vars[V_SLEEP]){ + int old_value = ps->sleep; + + if(SVAR_SLEEP(ps, old_value, tmp_20k_buf, SIZEOF_20KBUF)){ + if(!revert) + q_status_message(SM_ORDER, 3, 5, tmp_20k_buf); + } + else + ps->sleep = old_value; + } #ifdef SMIME else if(smime_related_var(ps, var)){ smime_deinit(); @@ -5496,6 +5586,12 @@ fix_side_effects(struct pine *ps, struct variable *var, int revert) (void *)var->current_val.p); } #endif +#ifndef _WINDOWS + else if(var == &ps->vars[V_MAILDIR_LOCATION]){ + if(var->current_val.p && var->current_val.p[0]) + mail_parameters(NULL, SET_MDINBOXPATH, (void *)var->current_val.p); + } +#endif else if(revert && standard_radio_var(ps, var)){ cur_rule_value(var, TRUE, FALSE); @@ -5545,9 +5641,15 @@ fix_side_effects(struct pine *ps, struct variable *var, int revert) else if(revert && var == &ps->vars[V_SORT_KEY]){ int def_sort_rev; - decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev); + decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev, 0); ps->def_sort_rev = def_sort_rev; } + else if(revert && var == &ps->vars[V_THREAD_SORT_KEY]){ + int thread_def_sort_rev; + + decode_sort(VAR_THREAD_SORT_KEY, &ps->thread_def_sort, &thread_def_sort_rev, 1); + ps->thread_def_sort_rev = thread_def_sort_rev; + } else if(var == &ps->vars[V_THREAD_MORE_CHAR] || var == &ps->vars[V_THREAD_EXP_CHAR] || var == &ps->vars[V_THREAD_LASTREPLY_CHAR]){ diff --git a/alpine/confscroll.h b/alpine/confscroll.h index 4315c8e1..95925cc2 100644 --- a/alpine/confscroll.h +++ b/alpine/confscroll.h @@ -95,7 +95,7 @@ int checkbox_tool(struct pine *, int, CONF_S **, unsigned); int radiobutton_tool(struct pine *, int, CONF_S **, unsigned); int yesno_tool(struct pine *, int, CONF_S **, unsigned); int text_toolit(struct pine *, int, CONF_S **, unsigned, int); -char *generalized_sort_pretty_value(struct pine *, CONF_S *, int); +char *generalized_sort_pretty_value(struct pine *, CONF_S *, int, int); int exclude_config_var(struct pine *, struct variable *, int); int config_exit_cmd(unsigned); int simple_exit_cmd(unsigned); diff --git a/alpine/dispfilt.c b/alpine/dispfilt.c index 7c93938b..7a386a1c 100644 --- a/alpine/dispfilt.c +++ b/alpine/dispfilt.c @@ -243,6 +243,19 @@ expand_filter_tokens(char *filter, ENVELOPE *env, char **tmpf, char **resultf, fs_give((void **)q); *q = rl ? rl : cpystr(""); } + else if(!strcmp(*q, "_ADDRESS_")){ + char *r = NULL; + + if(env && env->from && env->from->mailbox && env->from->host){ + size_t l; + l = strlen(env->from->mailbox) + strlen(env->from->host) + 1; + r = (char *) fs_get((l+1) * sizeof(char)); + snprintf(r, l+1, "%s@%s", env->from->mailbox, env->from->host); + } + + fs_give((void **)q); + *q = r ? r : cpystr(""); + } else if(!strcmp(*q, "_TMPFILE_")){ if(!tfn){ tfn = temp_nam(NULL, "sf"); /* send filter file */ @@ -461,3 +474,63 @@ df_valid_test(struct mail_bodystruct *body, char *test) return(passed); } + +char * +exec_function_rule(char *rawcmd, gf_io_t input_gc, gf_io_t output_pc) +{ + char *status = NULL, *cmd, *tmpfile = NULL; + + if((cmd = expand_filter_tokens(rawcmd,NULL,&tmpfile,NULL,NULL,NULL,NULL, NULL)) != NULL){ + suspend_busy_cue(); + ps_global->mangled_screen = 1; + if(tmpfile){ + PIPE_S *filter_pipe; + FILE *fp; + gf_io_t gc, pc; + STORE_S *tmpf_so; + + /* write the tmp file */ + if((tmpf_so = so_get(FileStar, tmpfile, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){ + /* copy input to tmp file */ + gf_set_so_writec(&pc, tmpf_so); + gf_filter_init(); + status = gf_pipe(input_gc, pc); + gf_clear_so_writec(tmpf_so); + if(so_give(&tmpf_so) != 0 && status == NULL) + status = error_description(errno); + + /* prepare the terminal in case the filter uses it */ + if(status == NULL){ + if((filter_pipe = open_system_pipe(cmd, NULL, NULL, + PIPE_USER|PIPE_PROT|PIPE_NOSHELL|PIPE_SILENT, + 0, pipe_callback, NULL)) != NULL){ + if(close_system_pipe(&filter_pipe, NULL, pipe_callback) == 0){ + /* pull result out of tmp file */ + if((fp = our_fopen(tmpfile, "rb")) != NULL){ + gf_set_readc(&gc, fp, 0L, FileStar, READ_FROM_LOCALE); + gf_filter_init(); + status = gf_pipe(gc, output_pc); + fclose(fp); + } + else + status = "Can't read result of EXEC command"; + } + else + status = "EXEC command command returned error."; + } + else + status = "Can't open pipe for EXEC command"; + } + + our_unlink(tmpfile); + } + else + status = "Can't open EXEC command tmp file"; + } + + resume_busy_cue(0); + fs_give((void **)&cmd); + } + + return(status); +} diff --git a/alpine/dispfilt.h b/alpine/dispfilt.h index 0b42010d..a890b19c 100644 --- a/alpine/dispfilt.h +++ b/alpine/dispfilt.h @@ -24,7 +24,7 @@ char *dfilter_trigger(BODY *, char *, size_t); char *expand_filter_tokens(char *, ENVELOPE *, char **, char **, char **, int *, int *, int *); char *filter_session_key(void); char *filter_data_file(int); - +char *exec_function_rule(char *, gf_io_t, gf_io_t); #endif /* PINE_DISPFILT_INCLUDED */ diff --git a/alpine/folder.c b/alpine/folder.c index 31d2031e..59589011 100644 --- a/alpine/folder.c +++ b/alpine/folder.c @@ -247,7 +247,7 @@ folder_screen(struct pine *ps) dprint((1, "=== folder_screen called ====\n")); mailcap_free(); /* free resources we won't be using for a while */ ps->next_screen = SCREEN_FUN_NULL; - + strcpy(ps->screen_name, "folder"); /* Initialize folder state and dispatches */ memset(&fs, 0, sizeof(FSTATE_S)); fs.context = cntxt; @@ -344,6 +344,7 @@ folder_screen(struct pine *ps) pine_mail_close(*fs.cache_streamp); ps->prev_screen = folder_screen; + strcpy(ps->screen_name, "unknown"); } @@ -6342,11 +6343,17 @@ folder_delimiter(char *folder) char * next_folder(MAILSTREAM **streamp, char *next, size_t nextlen, char *current, CONTEXT_S *cntxt, long int *find_recent, int *did_cancel) { - int index, recent = 0, failed_status = 0, try_fast; + int index, recent = 0, failed_status = 0, try_fast, done = 0; char prompt[128]; FOLDER_S *f = NULL; char tmp[MAILTMPLEN]; - + char *test_current = cpystr(current); + int cur_indx = folder_index(ps_global->cur_folder, cntxt, FI_FOLDER); + int loop = !strcmp(next, ps_global->cur_folder) ? 0 : + (folder_index(test_current, cntxt, FI_FOLDER) <= cur_indx + ? 1 : 0); + int last = !strcmp(ps_global->cur_folder, ps_global->inbox_name) + ? 1 : cur_indx; /* note: find_folders may assign "stream" */ build_folder_list(streamp, cntxt, NULL, NULL, @@ -6357,7 +6364,9 @@ next_folder(MAILSTREAM **streamp, char *next, size_t nextlen, char *current, CON if(find_recent) *find_recent = 0L; - for(index = folder_index(current, cntxt, FI_FOLDER) + 1; + +find_new_message: + for(index = folder_index(test_current, cntxt, FI_FOLDER) + 1; index > 0 && index < folder_total(FOLDERS(cntxt)) && (f = folder_entry(index, FOLDERS(cntxt))) @@ -6368,6 +6377,11 @@ next_folder(MAILSTREAM **streamp, char *next, size_t nextlen, char *current, CON int rv, we_cancel = 0, match; char msg_buf[MAX_BM+1]; + if (loop && (index == last)){ + done++; + break; + } + /* must be a folder and it can't be the current one */ if(ps_global->context_current == ps_global->context_list && !strcmp(ps_global->cur_folder, FLDR_NAME(f))) @@ -6535,12 +6549,24 @@ next_folder(MAILSTREAM **streamp, char *next, size_t nextlen, char *current, CON if(f && (!find_recent || recent)){ strncpy(next, FLDR_NAME(f), nextlen); next[nextlen-1] = '\0'; + done++; } else if(nextlen > 0) *next = '\0'; + if (!done && F_ON(F_AUTO_CIRCULAR_TAB,ps_global) + && strcmp(test_current,ps_global->inbox_name)){ + done++; loop++; + if (test_current) + fs_give((void **)&test_current); + test_current = cpystr(ps_global->inbox_name); + goto find_new_message; + } + /* BUG: how can this be made smarter so we cache the list? */ free_folder_list(cntxt); + if (test_current) + fs_give((void **)&test_current); return((*next) ? next : NULL); } diff --git a/alpine/imap.c b/alpine/imap.c index 074b9f6d..e43d3e2e 100644 --- a/alpine/imap.c +++ b/alpine/imap.c @@ -2040,6 +2040,83 @@ passfile_name(char *pinerc, char *path, size_t len) #endif /* PASSFILE */ +void +pine_delete_pwd(NETMBX *mb, char *user) +{ + char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN]; + char port[20], non_def_port[20], insecure[20]; + STRLIST_S hostlist[2]; + MMLOGIN_S *l; + struct servent *sv; + + + dprint((9, "pine_delete_pwd\n")); + + /* do not invalidate password on cancel */ + if(ps_global->user_says_cancel != 0) + return; + + /* setup hostlist */ + non_def_port[0] = '\0'; + if(mb->port && mb->service && + (sv = getservbyname(mb->service, "tcp")) && + (mb->port != ntohs(sv->s_port))){ + snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port); + non_def_port[sizeof(non_def_port)-1] = '\0'; + dprint((9, "mm_login: using non-default port=%s\n", + non_def_port ? non_def_port : "?")); + } + + if(*non_def_port){ + strncpy(hostlist0, mb->host, sizeof(hostlist0)-1); + hostlist0[sizeof(hostlist0)-1] = '\0'; + strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1); + hostlist0[sizeof(hostlist0)-1] = '\0'; + hostlist[0].name = hostlist0; + if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){ + strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1); + hostlist1[sizeof(hostlist1)-1] = '\0'; + strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1); + hostlist1[sizeof(hostlist1)-1] = '\0'; + hostlist[0].next = &hostlist[1]; + hostlist[1].name = hostlist1; + hostlist[1].next = NULL; + } + else + hostlist[0].next = NULL; + } + else{ + hostlist[0].name = mb->host; + if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){ + hostlist[0].next = &hostlist[1]; + hostlist[1].name = mb->orighost; + hostlist[1].next = NULL; + } + else + hostlist[0].next = NULL; + } + + /* delete it from all lists */ + + for(l = mm_login_list; l; l = l->next) + if(imap_same_host(l->hosts, hostlist) + && !strcmp(l->user, user ? user : "") + && l->passwd){ + l->invalidpwd = 1; + break; + } + +#ifdef LOCAL_PASSWD_CACHE + for(l = passfile_cache; l; l = l->next) + if(imap_same_host(l->hosts, hostlist) + && !strcmp(l->user, user ? user : "") + && l->passwd){ + l->invalidpwd = 1; + break; + } + write_passfile(ps_global->pinerc, passfile_cache); /* blast it from passfile */ +#endif +} #ifdef LOCAL_PASSWD_CACHE @@ -2602,6 +2679,8 @@ write_passfile(pinerc, l) #endif /* SMIME */ for(n = 0; l; l = l->next, n++){ + if(l->invalidpwd == 1) /* if password is invalid, do not save it */ + continue; /*** do any necessary ENcryption here ***/ snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%d%s%s\n", l->passwd, l->user, l->hosts->name, l->altflag, diff --git a/alpine/imap.h b/alpine/imap.h index b254fb53..378e687a 100644 --- a/alpine/imap.h +++ b/alpine/imap.h @@ -31,7 +31,8 @@ char *pine_newsrcquery(MAILSTREAM *, char *, char *); int url_local_certdetails(char *); void pine_sslfailure(char *, char *, unsigned long); void mm_expunged_current(long unsigned int); - +void pine_delete_pwd(NETMBX *mb, char *user); + #ifdef LOCAL_PASSWD_CACHE int get_passfile_passwd(char *, char *, char *, STRLIST_S *, int); int is_using_passfile(); diff --git a/alpine/keymenu.c b/alpine/keymenu.c index 195ae73c..a37304f9 100644 --- a/alpine/keymenu.c +++ b/alpine/keymenu.c @@ -650,10 +650,25 @@ struct key index_keys[] = RCOMPOSE_MENU, HOMEKEY_MENU, ENDKEY_MENU, - NULL_MENU, + {"K","Sort Thread",{MC_SORTHREAD,1,{'k'}},KS_NONE}, /* TRANSLATORS: toggles a collapsed view or an expanded view of a message thread on and off */ {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, + /* TRANSLATORS: Collapse all threads */ + {"{",N_("Collapse All"),{MC_KOLAPSE,1,{'{'}},KS_NONE}, + /* TRANSLATORS: Expand all threads */ + {"}",N_("Expand All"), {MC_EXPTHREAD,1,{'}'}},KS_NONE}, + + HELP_MENU, + OTHER_MENU, + {")","Next Threa",{MC_NEXTHREAD,1,{')'}},KS_NONE}, + {"(","Prev Threa",{MC_PRETHREAD,1,{'('}},KS_NONE}, + {"^R","Remove Thr",{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, + {"^U","Undel Thre",{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, + {"^T","Select Thr",{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}, + NULL_MENU, + {"[","Close Thre",{MC_CTHREAD,1,{'['}},KS_NONE}, + {"]","Open Threa",{MC_OTHREAD,1,{']'}},KS_NONE}, {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, NULL_MENU}; INST_KEY_MENU(index_keymenu, index_keys); @@ -728,9 +743,22 @@ struct key thread_keys[] = RCOMPOSE_MENU, HOMEKEY_MENU, ENDKEY_MENU, - NULL_MENU, + {"]",N_("Open Thread"),{MC_OTHREAD,1,{']'}},KS_NONE}, {"/",N_("Collapse/Expand"),{MC_COLLAPSE,1,{'/'}},KS_NONE}, + {")",N_("Next Thread"),{MC_NEXTHREAD,1,{')'}},KS_NONE}, + {"(",N_("Prev Thread"),{MC_PRETHREAD,1,{'('}},KS_NONE}, + + HELP_MENU, + OTHER_MENU, {"@", N_("Quota"), {MC_QUOTA,1,{'@'}}, KS_NONE}, + NULL_MENU, + {"^R",N_("Remove Thread"),{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, + {"^U",N_("Undelete Thread"),{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, + {"^T",N_("SelecT Thread"),{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}, + NULL_MENU, + NULL_MENU, + NULL_MENU, + {"K","Sort Thread",{MC_SORTHREAD,1,{'k'}},KS_NONE}, NULL_MENU}; INST_KEY_MENU(thread_keymenu, thread_keys); @@ -880,7 +908,20 @@ struct key view_keys[] = NULL_MENU, NULL_MENU, NULL_MENU, - NULL_MENU}; + NULL_MENU, + + HELP_MENU, + OTHER_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + NULL_MENU, + {"(",N_("Prev Thread"),{MC_PRETHREAD,1,{'('}},KS_NONE}, + {")",N_("Next Thread"),{MC_NEXTHREAD,1,{')'}},KS_NONE}, + {"^R",N_("Remove Thread"),{MC_DELTHREAD,1,{ctrl('r')}},KS_NONE}, + {"^U",N_("Undelete Thread"),{MC_UNDTHREAD,1,{ctrl('u')}},KS_NONE}, + {"^T",N_("selecT Thread"),{MC_SELTHREAD,1,{ctrl('t')}},KS_NONE}}; INST_KEY_MENU(view_keymenu, view_keys); diff --git a/alpine/keymenu.h b/alpine/keymenu.h index 0e1f7618..84fc0422 100644 --- a/alpine/keymenu.h +++ b/alpine/keymenu.h @@ -215,6 +215,19 @@ struct key_menu { #define MC_DECRYPT 802 #define MC_QUOTA 803 #define MC_ADDHEADER 804 +#define MC_DELTHREAD 805 +#define MC_UNDTHREAD 806 +#define MC_SELTHREAD 807 +#define MC_SSUTHREAD 808 +#define MC_DSUTHREAD 809 +#define MC_USUTHREAD 810 +#define MC_SORTHREAD 811 +#define MC_NEXTHREAD 812 +#define MC_KOLAPSE 813 +#define MC_EXPTHREAD 814 +#define MC_PRETHREAD 815 +#define MC_CTHREAD 816 +#define MC_OTHREAD 817 /* * Some standard Key/Command Bindings diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c index 7388005c..fc25795d 100644 --- a/alpine/mailcmd.c +++ b/alpine/mailcmd.c @@ -73,6 +73,7 @@ static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washing #include "../pith/tempfile.h" #include "../pith/search.h" #include "../pith/margin.h" +#include "../pith/rules.h" #ifdef _WINDOWS #include "../pico/osdep/mswin.h" #endif @@ -113,7 +114,7 @@ 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 select_sort(struct pine *, int, SortOrder *, int *, int); int print_index(struct pine *, MSGNO_S *, int); @@ -974,7 +975,7 @@ nfolder: state->context_current, &recent_cnt, F_ON(F_TAB_NO_CONFIRM,state) ? NULL : &did_cancel))){ - if(!in_inbox){ + if(!in_inbox && F_OFF(F_AUTO_CIRCULAR_TAB,state)){ static ESCKEY_S inbox_opt[] = { {'y', 'y', "Y", N_("Yes")}, {'n', 'n', "N", N_("No")}, @@ -1335,7 +1336,7 @@ get_out: if(any_messages(msgmap, NULL, NULL)){ if(any_lflagged(msgmap, MN_SLCT) > 0L){ if(apply_command(state, stream, msgmap, 0, - AC_NONE, question_line)){ + AC_NONE, question_line, 1)){ if(F_ON(F_AUTO_UNSELECT, state)){ agg_select_all(stream, msgmap, NULL, 0); unzoom_index(state, stream, msgmap); @@ -1353,23 +1354,35 @@ get_out: /*-------- Sort command -------*/ case MC_SORT : + case MC_SORTHREAD: { int were_threading = THREADING(); SortOrder sort = mn_get_sort(msgmap); int rev = mn_get_revsort(msgmap); + int thread = (command == MC_SORT) ? 0 : 1; dprint((1,"MAIL_CMD: sort\n")); - if(select_sort(state, question_line, &sort, &rev)){ + if(sort == SortThread) + sort = ps_global->thread_cur_sort; + if(select_sort(state, question_line, &sort, &rev, thread)){ /* $ command reinitializes threading collapsed/expanded info */ if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX()) erase_threading_info(stream, msgmap); + if(command == MC_SORTHREAD){ + ps_global->thread_cur_sort = sort; + sort = SortThread; + } + else if(sort == SortThread) /* command = MC_SORT */ + ps_global->thread_cur_sort = F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global) + ? SortArrival : ps_global->thread_def_sort; + 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); + sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN, 1); } state->mangled_footer = 1; @@ -2594,6 +2607,9 @@ role_compose(struct pine *state) 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': @@ -2644,12 +2660,12 @@ 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, flags, last_rc = 0, saveable_count = 0, done = 0; + int rc, ku = -1, n = 0, flags, last_rc = 0, saveable_count = 0, done = 0; int delindex, preindex, r; char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN]; char *buf = tmp_20k_buf; char shortbuf[200]; - char *folder; + char *folder, folder2[MAXPATH]; HelpType help; SaveDel del = DontAsk; SavePreserveOrder pre = DontAskPreserve; @@ -2657,6 +2673,7 @@ save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr static HISTORY_S *history = NULL; CONTEXT_S *tc; ESCKEY_S ekey[10]; + RULE_RESULT *rule; if(!cntxt) panic("no context ptr in save_prompt"); @@ -2666,6 +2683,15 @@ save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr 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)) @@ -3174,6 +3200,10 @@ cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap) if(SORT_IS_THREADED(msgmap)) refresh_sort(stream, msgmap, SRT_NON); + if (msgmap->nmsgs + && F_ON(F_ENHANCED_THREAD, state) && COLL_THRDS()) + kolapse_thread(state, stream, msgmap, '[', 0); + state->mangled_body = 1; state->mangled_header = 1; q_status_message2(SM_ORDER, 0, 4, @@ -3268,6 +3298,9 @@ cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap) */ if(SORT_IS_THREADED(msgmap)) refresh_sort(stream, msgmap, SRT_NON); + if (msgmap->nmsgs + && F_ON(F_ENHANCED_THREAD, state) && COLL_THRDS()) + kolapse_thread(state, stream, msgmap, '[', 0); } else{ if(del_count) @@ -3349,6 +3382,9 @@ save_size_changed_prompt(long msgno, int flags) {-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"); @@ -6945,7 +6981,7 @@ select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index) * 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)); + thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state), 1); } else{ if((all_selected = @@ -7001,7 +7037,7 @@ select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index) ----*/ int apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, - UCS preloadkeystroke, int flags, int q_line) + UCS preloadkeystroke, int flags, int q_line, int display) { int i = 8, /* number of static entries in sel_opts3 */ rv = 0, @@ -7153,9 +7189,19 @@ apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, collapse_or_expand(state, stream, msgmap, F_ON(F_SLASH_COLL_ENTIRE, ps_global) ? 0L - : mn_get_cur(msgmap)); + : mn_get_cur(msgmap), + display); break; + case '[' : + collapse_this_thread(state, stream, msgmap, display, 0); + break; + + case ']' : + expand_this_thread(state, stream, msgmap, display, 0); + break; + + case ':' : select_thread_stmp(state, stream, msgmap); break; @@ -7189,7 +7235,7 @@ apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) { - int r, end; + int r, end, cur; long n1, n2, raw; char number1[16], number2[16], numbers[80], *p, *t; HelpType help; @@ -7236,7 +7282,7 @@ select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) *t = '\0'; - end = 0; + end = cur = 0; if(number1[0] == '\0'){ if(*p == '-'){ q_status_message1(SM_ORDER | SM_DING, 0, 2, @@ -7248,6 +7294,14 @@ select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) end = 1; p += strlen("end"); } + else if(!strucmp(p, "$")){ + end = 1; + p++; + } + else if(!struncmp(p, ".",1)){ + cur = 1; + p++; + } else{ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid message number: %s"), numbers); @@ -7257,6 +7311,8 @@ select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) 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"), @@ -7271,12 +7327,20 @@ select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) *t = '\0'; - end = 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(!struncmp(p, ".",1)){ + cur = 1; + p++; + } else{ q_status_message1(SM_ORDER | SM_DING, 0, 2, _("Invalid number range, missing number after \"-\": %s"), @@ -7287,6 +7351,8 @@ select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) 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"), @@ -9014,10 +9080,10 @@ Args: state -- pine state pointer Returns 0 if it was cancelled, 1 otherwise. ----*/ int -select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) +select_sort(struct pine *state, int ql, SortOrder *sort, int *rev, int thread) { char prompt[200], tmp[3], *p; - int s, i; + int s, i, j; int deefault = 'a', retval = 1; HelpType help; ESCKEY_S sorts[14]; @@ -9050,17 +9116,26 @@ select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) 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; + for(i = 0, j = 0; state->sort_types[i] != EndofList; i++) { + sorts[i].rval = i; + sorts[i].name = cpystr(""); + sorts[i].label = ""; + sorts[i].ch = -2; + if (!thread || allowed_thread_key(state->sort_types[i])){ + p = sorts[j].label = sort_name(state->sort_types[i]); + while(*(p+1) && islower((unsigned char)*p)) + p++; + sorts[j].ch = tolower((unsigned char)(tmp[0] = *p)); + sorts[j++].name = cpystr(tmp); + } + + if (thread){ + if (state->thread_def_sort == state->sort_types[i]) + deefault = sorts[j-1].rval; + } + else + if(mn_get_sort(state->msgmap) == state->sort_types[i]) + deefault = sorts[i].rval; } sorts[i].ch = 'r'; @@ -9084,8 +9159,17 @@ select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) state->mangled_body = 1; /* signal screen's changed */ if(s == 'r') *rev = !mn_get_revsort(state->msgmap); - else + else{ + if(thread){ + for(i = 0; state->sort_types[i] != EndofList; i++){ + if(struncmp(sort_name(state->sort_types[i]), + sorts[s].label, strlen(sorts[s].label)) == 0) + break; + } + s = i; + } *sort = state->sort_types[s]; + } if(F_ON(F_SHOW_SORT, ps_global)) ps_global->mangled_header = 1; @@ -9470,3 +9554,378 @@ flag_submenu(mc) } #endif /* _WINDOWS */ + +void +cmd_delete_this_thread(state, stream, msgmap) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; +{ + unsigned long rawno, top, save_kolapsed; + PINETHRD_S *thrd = NULL, *nxthrd; + + if(!stream) + return; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_this_thread(stream, msgmap, rawno); + top = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return; + + save_kolapsed = this_thread_is_kolapsed(state, stream, msgmap, top); + collapse_this_thread(state, stream, msgmap, 0, 0); + thread_command(state, stream, msgmap, 'd', -FOOTER_ROWS(state), 1); + if (!save_kolapsed) + expand_this_thread(state, stream, msgmap, 0, 0); +} + +void +cmd_delete_thread(state, stream, msgmap) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; +{ + unsigned long rawno, top, orig_top, topnxt, save_kolapsed; + PINETHRD_S *thrd = NULL, *nxthrd; + int done = 0, count; + + if(!stream) + return; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_thread(stream, msgmap, rawno); + top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return; + + while (!done){ + cmd_delete_this_thread(state, stream, msgmap); + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) + || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_top != top_thread(stream, top))) + done++; + } + mn_set_cur(msgmap,mn_raw2m(msgmap, rawno)); + cmd_delete(state, msgmap, MCMD_NONE, cmd_delete_index); + count = count_thread(state, stream, msgmap, rawno); + q_status_message2(SM_ORDER, 0, 1, "%s message%s marked deleted", + int2string(count), plural(count)); +} + +int +collapse_this_thread(state, stream, msgmap, display, special) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; + int display; + int special; +{ + int collapsed, rv = 1, done = 0; + PINETHRD_S *thrd = NULL, *nthrd; + unsigned long rawno, orig, msgno; + + if(!stream) + return 0; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return rv; + + collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); + + if (special && collapsed){ + expand_this_thread(state, stream, msgmap, 0, 0); + collapsed = 0; + } + + clear_index_cache_ent(stream, rawno, 0); + + if (!collapsed && thrd->next){ + if (thrd->rawno == top_thread(stream, thrd->rawno)) + collapse_or_expand(state, stream, msgmap, mn_get_cur(msgmap), display); + else{ + set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno), MN_COLL, 1); + set_thread_subtree(stream, thrd, msgmap, 1, MN_CHID); + } + } + else{ + if (!collapsed && special + && ((F_OFF(F_ENHANCED_THREAD, state) && !thrd->next) + || F_ON(F_ENHANCED_THREAD, state))){ + if (thrd->toploose){ + if (thrd->rawno != thrd->toploose) + set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_CHID, + 1); + else + set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_COLL, + 1); + } + } + else{ + rv = 0; + if (display) + q_status_message(SM_ORDER, 0, 1, "Thread already collapsed"); + } + } + return rv; +} + +void +collapse_thread(state, stream, msgmap, display) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; + int display; +{ + int collapsed, rv = 1, done = 0; + PINETHRD_S *thrd = NULL; + unsigned long orig, orig_top, top; + + if(!stream) + return; + + expand_this_thread(state, stream, msgmap, display, 1); + orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_thread(stream, msgmap,orig); + top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return; + + while (!done){ + collapse_this_thread(state, stream, msgmap, display, 1); + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) + || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_top != top_thread(stream, top))) + done++; + } + mn_set_cur(msgmap,mn_raw2m(msgmap, orig_top)); +} + +int +expand_this_thread(state, stream, msgmap, display, special) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; + int display; + int special; +{ + int collapsed, rv = 1, done = 0; + PINETHRD_S *thrd = NULL, *nthrd; + unsigned long rawno, orig, msgno; + + if(!stream) + return 0; + + orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_this_thread(stream, msgmap,orig); + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return rv; + + collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); + + if (special && !collapsed){ + collapse_this_thread(state, stream, msgmap, 0, 0); + collapsed = 1; + } + + clear_index_cache_ent(stream, rawno, 0); + + if (collapsed && thrd->next){ + if (thrd->rawno == top_thread(stream, thrd->rawno)) + collapse_or_expand(state, stream, msgmap, mn_get_cur(msgmap), display); + else{ + set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno), MN_COLL, 0); + set_thread_subtree(stream, thrd, msgmap, 0, MN_CHID); + } + } + else{ + if (collapsed && special + && ((F_OFF(F_ENHANCED_THREAD, state) && !thrd->next) + || F_ON(F_ENHANCED_THREAD, state))){ + if (thrd->toploose) + if (thrd->rawno != thrd->toploose) + set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_CHID, 0); + else + set_lflag(stream, msgmap, mn_raw2m(msgmap,thrd->rawno),MN_COLL, 0); + } + else{ + rv = 0; + if (display) + q_status_message(SM_ORDER, 0, 1, "Thread already expanded"); + } + } + return rv; +} + +void +expand_thread(state, stream, msgmap, display) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; + int display; +{ + int collapsed, rv = 1, done = 0; + PINETHRD_S *thrd = NULL; + unsigned long orig, orig_top, top; + + if(!stream) + return; + + orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); + top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return; + + while (!done){ + expand_this_thread(state, stream, msgmap, display, 1); + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) + || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_top != top_thread(stream, top))) + done++; + } + mn_set_cur(msgmap,mn_raw2m(msgmap, orig_top)); +} + + +void +cmd_undelete_this_thread(state, stream, msgmap) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; +{ + unsigned long rawno; + int save_kolapsed; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + save_kolapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno); + collapse_this_thread(state, stream, msgmap, 0, 0); + thread_command(state, stream, msgmap, 'u', -FOOTER_ROWS(state), 1); + if (!save_kolapsed) + expand_this_thread(state, stream, msgmap, 0, 0); +} + +void +cmd_undelete_thread(state, stream, msgmap) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; +{ + PINETHRD_S *thrd = NULL; + unsigned long rawno, top, orig_top; + int done = 0, count; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_thread(stream, msgmap, rawno); + top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return; + + while (!done){ + cmd_undelete_this_thread(state, stream, msgmap); + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) + || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_top != top_thread(stream, top))) + done++; + } + mn_set_cur(msgmap,mn_raw2m(msgmap, rawno)); + count = count_thread(state, stream, msgmap, rawno); + q_status_message2(SM_ORDER, 0, 1, "Deletion mark removed from %s message%s", + int2string(count), plural(count)); +} + +void +kolapse_thread(state, stream, msgmap, ch, display) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; + char ch; + int display; +{ + PINETHRD_S *thrd = NULL; + unsigned long rawno; + int rv = 1, done = 0; + + if(!stream) + return; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return; + + clear_index_cache(stream, 0); + mn_set_cur(msgmap,1); /* go to the first message */ + while (!done){ + if (ch == '[') + collapse_thread(state, stream, msgmap, display); + else + expand_thread(state, stream, msgmap, display); + if ((rv = move_next_thread(state, stream, msgmap, 0)) <= 0) + done++; + } + + if (rv < 0){ + if (display) + q_status_message(SM_ORDER, 0, 1, (ch == '[') + ? "Error while collapsing thread" + : "Error while expanding thread"); + } + else + if(display) + q_status_message(SM_ORDER, 0, 1, (ch == '[') + ? "All threads collapsed. Use \"}\" to expand them" + : "All threads expanded. Use \"{\" to collapse them"); + + mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream,rawno))); +} + +void +cmd_select_thread(state, stream, msgmap) + struct pine *state; + MAILSTREAM *stream; + MSGNO_S *msgmap; +{ + unsigned long rawno; + int save_kolapsed; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + save_kolapsed = thread_is_kolapsed(state, stream, msgmap, rawno); + collapse_thread(state, stream, msgmap, 0); + thread_command(state, stream, msgmap, ':', -FOOTER_ROWS(state), 1); + if (!save_kolapsed) + expand_thread(state, stream, msgmap, 0); +} + diff --git a/alpine/mailcmd.h b/alpine/mailcmd.h index c86ef7cd..2be89e40 100644 --- a/alpine/mailcmd.h +++ b/alpine/mailcmd.h @@ -84,7 +84,7 @@ char *broach_folder(int, int, int *, CONTEXT_S **); int ask_mailbox_reopen(struct pine *, int *); void visit_folder(struct pine *, char *, CONTEXT_S *, MAILSTREAM *, unsigned long); int select_by_current(struct pine *, MSGNO_S *, CmdWhere); -int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int); +int apply_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int, int); char **choose_list_of_keywords(void); char *choose_a_charset(int); char **choose_list_of_charsets(void); @@ -102,6 +102,15 @@ int any_selected_callback(int, long); int flag_callback(int, long); MPopup *flag_submenu(MESSAGECACHE *); #endif - +void cmd_delete_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +void cmd_delete_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +void cmd_undelete_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +void cmd_undelete_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +void cmd_select_thread(struct pine *, MAILSTREAM *, MSGNO_S *); +void kolapse_thread(struct pine *, MAILSTREAM *, MSGNO_S *, char, int); +void collapse_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +void expand_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +int collapse_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int, int); +int expand_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int, int); #endif /* PINE_MAILCMD_INCLUDED */ diff --git a/alpine/mailindx.c b/alpine/mailindx.c index 01e01558..263825d2 100644 --- a/alpine/mailindx.c +++ b/alpine/mailindx.c @@ -229,6 +229,8 @@ mail_index_screen(struct pine *state) state->prev_screen = mail_index_screen; state->next_screen = SCREEN_FUN_NULL; + setup_threading_display_style(); + if(THRD_AUTO_VIEW() && sp_viewing_a_thread(state->mail_stream) && state->view_skipped_index @@ -240,10 +242,14 @@ mail_index_screen(struct pine *state) adjust_cur_to_visible(state->mail_stream, state->msgmap); + strcpy(state->screen_name,"index"); + if(THRD_INDX()) thread_index_screen(state); else index_index_screen(state); + + strcpy(state->screen_name,"unknown"); } @@ -561,6 +567,7 @@ index_lister(struct pine *state, CONTEXT_S *cntxt, char *folder, MAILSTREAM *str /*---------- Scroll line up ----------*/ case MC_CHARUP : +previtem: (void) process_cmd(state, stream, msgmap, MC_PREVITEM, (style == MsgIndex || style == MultiMsgIndex @@ -578,6 +585,7 @@ index_lister(struct pine *state, CONTEXT_S *cntxt, char *folder, MAILSTREAM *str /*---------- Scroll line down ----------*/ case MC_CHARDOWN : +nextitem: /* * Special Page framing handling here. If we * did something that should scroll-by-a-line, frame @@ -795,6 +803,7 @@ view_a_thread: case MC_THRDINDX : +mc_thrdindx: msgmap->top = msgmap->top_after_thrd; if(unview_thread(state, stream, msgmap)){ state->next_screen = mail_index_screen; @@ -845,7 +854,7 @@ view_a_thread: && mp.col == id.plus_col && style != ThreadIndex){ collapse_or_expand(state, stream, msgmap, - mn_get_cur(msgmap)); + mn_get_cur(msgmap), 1); } else if (mp.doubleclick){ if(mp.button == M_BUTTON_LEFT){ @@ -954,9 +963,105 @@ view_a_thread: case MC_COLLAPSE : - thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state)); + thread_command(state, stream, msgmap, ch, -FOOTER_ROWS(state), 1); break; + case MC_CTHREAD : + if (SEP_THRDINDX()) + goto mc_thrdindx; + else + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, + "to collapse a thread")) + collapse_thread(state, stream,msgmap, 1); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_OTHREAD : + if (SEP_THRDINDX()) + goto view_a_thread; + else + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to expand a thread")) + expand_thread(state, stream,msgmap, 1); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_NEXTHREAD: + case MC_PRETHREAD: + if (THRD_INDX()){ + if (cmd == MC_NEXTHREAD) + goto nextitem; + else + goto previtem; + } + else + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, + "to move to other thread")) + move_thread(state, stream, msgmap, + cmd == MC_NEXTHREAD ? 1 : -1); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_KOLAPSE: + case MC_EXPTHREAD: + if (SEP_THRDINDX()){ + q_status_message(SM_ORDER, 0, 1, + "Command not available in this screen"); + } + else{ + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, + cmd == MC_KOLAPSE ? "to collapse" : "to expand")) + kolapse_thread(state, stream, msgmap, + (cmd == MC_KOLAPSE) ? '[' : ']', 1); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + } + break; + + case MC_DELTHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to delete")) + cmd_delete_thread(state, stream, msgmap); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_UNDTHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to undelete")) + cmd_undelete_thread(state, stream, msgmap); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_SELTHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to undelete")) + cmd_select_thread(state, stream, msgmap); + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + case MC_DELETE : case MC_UNDELETE : case MC_REPLY : @@ -977,13 +1082,12 @@ view_a_thread: if(rawno) thrd = fetch_thread(stream, rawno); - collapsed = thrd && thrd->next - && get_lflag(stream, NULL, rawno, MN_COLL); + collapsed = thread_is_kolapsed(ps_global, stream, msgmap, rawno); } if(collapsed){ thread_command(state, stream, msgmap, - ch, -FOOTER_ROWS(state)); + ch, -FOOTER_ROWS(state),1); /* increment current */ if(cmd == MC_DELETE){ advance_cur_after_delete(state, stream, msgmap, @@ -2675,6 +2779,7 @@ top_ent_calc(MAILSTREAM *stream, MSGNO_S *msgs, long int at_top, long int lines_ n = mn_raw2m(msgs, thrd->rawno); while(thrd){ + unsigned long branch; if(!msgline_hidden(stream, msgs, n, 0) && (++m % lines_per_page) == 1L) t = n; @@ -2743,11 +2848,12 @@ top_ent_calc(MAILSTREAM *stream, MSGNO_S *msgs, long int at_top, long int lines_ /* n is the end of this thread */ while(thrd){ + unsigned long next = 0L, branch = 0L; n = mn_raw2m(msgs, thrd->rawno); - if(thrd->branch) - thrd = fetch_thread(stream, thrd->branch); - else if(thrd->next) - thrd = fetch_thread(stream, thrd->next); + if(branch = get_branch(stream,thrd)) + thrd = fetch_thread(stream, branch); + else if(next = get_next(stream,thrd)) + thrd = fetch_thread(stream, next); else thrd = NULL; } @@ -2855,7 +2961,7 @@ warn_other_cmds(void) void thread_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, - UCS preloadkeystroke, int q_line) + UCS preloadkeystroke, int q_line, int display) { PINETHRD_S *thrd = NULL; unsigned long rawno, save_branch; @@ -2904,7 +3010,7 @@ thread_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, cancel_busy_cue(0); (void ) apply_command(state, stream, msgmap, preloadkeystroke, flags, - q_line); + q_line, display); /* restore the original flags */ copy_lflags(stream, msgmap, MN_STMP, MN_SLCT); @@ -3398,7 +3504,7 @@ index_sort_callback(set, order) if(set){ sort_folder(ps_global->mail_stream, ps_global->msgmap, order & 0x000000ff, - (order & 0x00000100) != 0, SRT_VRB); + (order & 0x00000100) != 0, SRT_VRB, 1); mswin_beginupdate(); update_titlebar_message(); update_titlebar_status(); diff --git a/alpine/mailindx.h b/alpine/mailindx.h index 3b4291c2..a1627c2f 100644 --- a/alpine/mailindx.h +++ b/alpine/mailindx.h @@ -103,7 +103,7 @@ int truncate_subj_and_from_strings(void); void paint_index_hline(MAILSTREAM *, long, ICE_S *); void setup_index_state(int); void warn_other_cmds(void); -void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int); +void thread_command(struct pine *, MAILSTREAM *, MSGNO_S *, UCS, int, int); COLOR_PAIR *apply_rev_color(COLOR_PAIR *, int); #ifdef _WINDOWS int index_sort_callback(int, long); diff --git a/alpine/mailpart.c b/alpine/mailpart.c index 8286d79d..63427744 100644 --- a/alpine/mailpart.c +++ b/alpine/mailpart.c @@ -2146,6 +2146,11 @@ display_attachment(long int msgno, ATTACH_S *a, int flags) return(1); } + /* ok, we have a filename. Now check if there is a template, and if + * so, rename the file accordingly + */ + filename = mc_template(filename, a->body, a->can_display & MCD_EXT_PROMPT); + if((store = so_get(FileStar, filename, WRITE_ACCESS|OWNER_ONLY)) == NULL){ q_status_message2(SM_ORDER | SM_DING, 3, 5, _("Error \"%s\", Can't write file %s"), @@ -3308,7 +3313,7 @@ reply_msg_att(MAILSTREAM *stream, long int msgno, ATTACH_S *a) /* * For consistency, the first question is always "include text?" */ - if((include_text = reply_text_query(ps_global, 1, &prefix)) >= 0 + if((include_text = reply_text_query(ps_global, 1, NULL, &prefix)) >= 0 && reply_news_test(a->body->nested.msg->env, outgoing) > 0 && reply_harvest(ps_global, msgno, a->number, a->body->nested.msg->env, &saved_from, diff --git a/alpine/mailview.c b/alpine/mailview.c index 5193118f..41aeea5a 100644 --- a/alpine/mailview.c +++ b/alpine/mailview.c @@ -45,6 +45,7 @@ static char rcsid[] = "$Id: mailview.c 1266 2009-07-14 18:39:12Z hubert@u.washin #include "dispfilt.h" #include "busy.h" #include "smime.h" +#include "roleconf.h" #include "../pith/conf.h" #include "../pith/filter.h" #include "../pith/msgno.h" @@ -130,6 +131,7 @@ struct view_write_s { #define SS_CUR 2 #define SS_FREE 3 +static ACTION_S *role_chosen = NULL; /* * Handle hints. @@ -204,7 +206,15 @@ char *pcpine_help_scroll(char *); int pcpine_view_cursor(int, long); #endif +static char *prefix; +#define NO_FLOWED 0x0000 +#define IS_FLOWED 0x0001 +#define DELETEQUO 0x0010 +#define COLORAQUO 0x0100 +#define RAWSTRING 0x1000 +int is_word (char *, int, int); +int is_mailbox(char *, int, int); /*---------------------------------------------------------------------- Format a buffer with the text of the current message for browser @@ -243,6 +253,8 @@ mail_view_screen(struct pine *ps) ps->prev_screen = mail_view_screen; ps->force_prefer_plain = ps->force_no_prefer_plain = 0; + strcpy(ps->screen_name, "text"); + if(ps->ttyo->screen_rows - HEADER_ROWS(ps) - FOOTER_ROWS(ps) < 1){ q_status_message(SM_ORDER | SM_DING, 0, 3, _("Screen too small to view message")); @@ -295,6 +307,17 @@ mail_view_screen(struct pine *ps) else ps->unseen_in_view = !mc->seen; + prefix = reply_quote_str(env); + /* Make sure the prefix is not only made of spaces, so that we do not + * paint the screen incorrectly + */ + if (prefix && *prefix){ + int i; + for (i = 0; isspace((unsigned char) prefix[i]); i++); + if (i == strlen(prefix)) + fs_give((void **)&prefix); + } + init_handles(&handles); store = so_get(src, NULL, EDIT_ACCESS); @@ -479,6 +502,10 @@ mail_view_screen(struct pine *ps) } while(ps->next_screen == SCREEN_FUN_NULL); + strcpy(ps->screen_name, "unknown"); + + if (prefix && *prefix) + fs_give((void **)&prefix); if(we_cancel) cancel_busy_cue(-1); @@ -733,6 +760,8 @@ scroll_handle_prompt(HANDLE_S *handle, int force) {0, 'a', "A", N_("editApp")}, {-1, 0, NULL, NULL}}; + if (role_chosen) + free_action(&role_chosen); if(handle->type == URL){ launch_opts[4].ch = 'u'; @@ -847,12 +876,42 @@ scroll_handle_prompt(HANDLE_S *handle, int force) * sense if you just say View selected URL ... */ if(handle->type == URL && - !struncmp(handle->h.url.path, "mailto:", 7)) - snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\" ? ", - MIN(MAX(0,sc - 25), sizeof(prompt)-50), handle->h.url.path+7, - (strlen(handle->h.url.path+7) > MAX(0,sc-25)) ? "..." : ""); - else - snprintf(prompt, sizeof(prompt), "View selected %s %s%.*s%s ? ", + !struncmp(handle->h.url.path, "mailto:", 7)){ + int rolenick = role_chosen ? strlen(role_chosen->nick) : 0; + int offset = 25 + (role_chosen ? 20 : 0); + int offset2 = max(0, sc - offset) - strlen(handle->h.url.path+7); + int offset3 = sc - strlen(handle->h.url.path+7) - rolenick - offset; + int laddress = min(max(0,sc - offset), sizeof(prompt)-50); + int lrole = rolenick; + + if (offset3 < 0){ + lrole = rolenick; + laddress = sc - offset - lrole; + offset3 = laddress - 20; /* redefine offset3 */ + if (offset3 < 0){ + laddress = 20; + lrole = sc - offset - laddress; + } + } + launch_opts[2].ch = 'r'; + launch_opts[2].rval = 'r'; + launch_opts[2].name = "R"; + launch_opts[2].label = N_("Set Role"); + snprintf(prompt, sizeof(prompt), "Compose mail to \"%.*s%s\" %s%.*s%s%s? ", + laddress, handle->h.url.path+7, + (offset2 < 0 ? "..." : ""), + (role_chosen ? "using role \"" : ""), + (role_chosen ? lrole : 0), + (role_chosen ? role_chosen->nick : ""), + (role_chosen ? (rolenick > lrole ? "..." : "") : ""), + (role_chosen ? "\" " : "")); + } + else{ + launch_opts[2].ch = -2; + launch_opts[2].rval = 0; + launch_opts[2].name = NULL; + launch_opts[2].label = NULL; + snprintf(prompt, sizeof(prompt), "View selected %s %s%.*s%s ? ", (handle->type == URL) ? "URL" : "Attachment", (handle->type == URL) ? "\"" : "", MIN(MAX(0,sc-27), sizeof(prompt)-50), @@ -860,6 +919,7 @@ scroll_handle_prompt(HANDLE_S *handle, int force) (handle->type == URL) ? ((strlen(handle->h.url.path) > MAX(0,sc-27)) ? "...\"" : "\"") : ""); + } prompt[sizeof(prompt)-1] = '\0'; @@ -868,6 +928,29 @@ scroll_handle_prompt(HANDLE_S *handle, int force) case 'y' : return(1); + case 'r': + { + void (*prev_screen)(struct pine *) = ps_global->prev_screen, + (*redraw)(void) = ps_global->redrawer; + ps_global->redrawer = NULL; + ps_global->next_screen = SCREEN_FUN_NULL; + if(role_select_screen(ps_global, &role_chosen, 1) < 0){ + cmd_cancelled("Compose"); + ps_global->next_screen = prev_screen; + ps_global->redrawer = redraw; + if(ps_global->redrawer) + (*ps_global->redrawer)(); + return 0; + } + ps_global->next_screen = prev_screen; + ps_global->redrawer = redraw; + if(role_chosen) + role_chosen = combine_inherited_role(role_chosen); + if(ps_global->redrawer) + (*ps_global->redrawer)(); + break; + } + case 'u' : strncpy(tmp, handle->h.url.path, sizeof(tmp)-1); tmp[sizeof(tmp)-1] = '\0'; @@ -1432,7 +1515,7 @@ url_launch(HANDLE_S *handle) else #endif /* quote shell specials */ - if(strpbrk(handle->h.url.path, "&*!;<>?[]|~$(){}'\"") != NULL){ + if(strpbrk(handle->h.url.path, "&*;<>?[]|~$(){}'\"") != NULL){ escape_single_quotes++; if((p = strstr(toolp, "_URL_")) != NULL){ /* explicit arg? */ int in_quote = 0; @@ -1510,8 +1593,7 @@ url_launch(HANDLE_S *handle) *cmdp++ = '\''; /* closing quote */ *cmdp++ = '\\'; *cmdp++ = '\''; /* opening quote comes from p below */ - } else if (strchr("&*!;<>?[]|~$(){}\"", *p) != NULL) - *cmdp++ = '\\'; + } *cmdp++ = *p; } @@ -1539,7 +1621,7 @@ url_launch(HANDLE_S *handle) if(cmdp-cmd >= URL_MAX_LAUNCH) return(url_launch_too_long(rv)); - mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW ; + mode = PIPE_RESET | PIPE_USER | PIPE_RUNNOW | PIPE_NOSHELL; if((syspipe = open_system_pipe(cmd, NULL, NULL, mode, 0, pipe_callback, pipe_report_error)) != NULL){ close_system_pipe(&syspipe, NULL, pipe_callback); q_status_message(SM_ORDER, 0, 4, _("VIEWER command completed")); @@ -1817,7 +1899,7 @@ url_local_mailto_and_atts(char *url, PATMT *attachlist) fs_give((void **) &urlp); rflags = ROLE_COMPOSE; - if(nonempty_patterns(rflags, &dummy)){ + if(!(role = copy_action(role_chosen)) && nonempty_patterns(rflags, &dummy)){ role = set_role_from_msg(ps_global, rflags, -1L, NULL); if(confirm_role(rflags, &role)) role = combine_inherited_role(role); @@ -1893,6 +1975,7 @@ outta_here: free_redraft_pos(&redraft_pos); free_action(&role); + free_action(&role_chosen); return(rv); } @@ -3463,6 +3546,52 @@ scrolltool(SCROLL_S *sparms) print_to_printer(sparms); break; + case MC_NEXTHREAD: + case MC_PRETHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, + "to move to other thread")) + move_thread(ps_global, ps_global->mail_stream, ps_global->msgmap, + cmd == MC_NEXTHREAD ? 1 : -1); + done = 1; + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_DELTHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to delete")) + cmd_delete_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); + done = 1; + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_UNDTHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to undelete")) + cmd_undelete_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); + done = 1; + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; + + case MC_SELTHREAD: + if (THREADING()){ + if (any_messages(ps_global->msgmap, NULL, "to undelete")) + cmd_select_thread(ps_global, ps_global->mail_stream, ps_global->msgmap); + done = 1; + } + else + q_status_message(SM_ORDER, 0, 1, + "Command available in threaded mode only"); + break; /* ------- First handle on Line ------ */ case MC_GOTOBOL : diff --git a/alpine/osdep/debuging.c b/alpine/osdep/debuging.c index 0310f5b5..4767ca57 100644 --- a/alpine/osdep/debuging.c +++ b/alpine/osdep/debuging.c @@ -105,6 +105,7 @@ init_debug(void) if(debugfile != NULL){ char rev[128]; + extern char plevstamp[]; time_t now = time((time_t *)0); if(ps_global->debug_flush) setvbuf(debugfile, (char *)NULL, _IOLBF, BUFSIZ); @@ -127,6 +128,8 @@ init_debug(void) get_alpine_revision_string(rev, sizeof(rev)), ctime(&now))); + dprint((0, "This version uses all.patch:\n%s\n\n", plevstamp)); + dprint((0, "Starting after the reading_pinerc calls, the data in this file should\nbe encoded as UTF-8. Before that it will be in the user's native charset.\n")); if(dfile && (debug > DEFAULT_DEBUG || ps_global->debug_imap > 0 || diff --git a/alpine/osdep/execview.c b/alpine/osdep/execview.c index da7310df..d7087caa 100644 --- a/alpine/osdep/execview.c +++ b/alpine/osdep/execview.c @@ -261,7 +261,7 @@ exec_mailcap_cmd(MCAP_CMD_S *mc_cmd, char *image_file, int needsterminal) p = command = (char *)fs_get((l+1) * sizeof(char)); if(!needsterminal) /* put in background if it doesn't need terminal */ *p++ = '('; - snprintf(p, l+1-(p-command), "%s ; rm -f %s", cmd, image_file); + snprintf(p, l+1-(p-command), "%s ; sleep %d ; rm -f %s", cmd, ps_global->sleep, image_file); command[l] = '\0'; p += strlen(p); if(!needsterminal && (p-command)+5 < l){ diff --git a/alpine/osdep/termin.gen.c b/alpine/osdep/termin.gen.c index fb106be1..74d2a37f 100644 --- a/alpine/osdep/termin.gen.c +++ b/alpine/osdep/termin.gen.c @@ -33,6 +33,8 @@ static char rcsid[] = "$Id: termin.gen.c 1025 2008-04-08 22:59:38Z hubert@u.wash #include "../../pith/newmail.h" #include "../../pith/conf.h" #include "../../pith/busy.h" +#include "../../pith/list.h" +#include "../../pith/rules.h" #include "../../pico/estruct.h" #include "../../pico/pico.h" @@ -67,12 +69,30 @@ static int g_mc_row, g_mc_col; int pcpine_oe_cursor(int, long); #endif +void +fake_config_screen(tt) + struct ttyo **tt; +{ + struct ttyo *ttyo; + + ttyo = (struct ttyo *)fs_get(sizeof (struct ttyo)); + + ttyo->header_rows = 2; + ttyo->footer_rows = 3; + ttyo->screen_rows = 24; + ttyo->screen_cols = 80; + + *tt = ttyo; + +} + /* * Generic tty input routines */ - +void process_init_cmds(struct pine *, char **); +void queue_init_errors(struct pine *); /*---------------------------------------------------------------------- Read a character from keyboard with timeout Input: none @@ -114,6 +134,41 @@ read_command(char **utf8str) *utf8str = NULL; ucs = read_char(tm); + if(!ps_global->initial_cmds){ + RULE_RESULT *rule; + char **list = NULL, *error = NULL; + int commas = 0, k; /* From args.c */ + + ps_global->pressed_key = cpystr(pretty_command(ucs)); + rule = (RULE_RESULT *)get_result_rule(V_KEY_RULES, FOR_KEY, NULL); + if(ps_global->pressed_key) + fs_give((void **)&ps_global->pressed_key); + if (rule){ + for(k = 0; rule->result[k]; k++) + if(rule->result[k] == ',') commas++; + list = parse_list(rule->result, commas+1, 0, &error); + if(error) + sprintf(tmp_20k_buf, "Error in parsing command list: %s, %s", + rule->result, error); + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + if(error){ + q_status_message(SM_ORDER | SM_DING, 0, 2, tmp_20k_buf); + return (NO_OP_COMMAND); + } + process_init_cmds(ps_global, list); + if(ps_global->init_errs){ + queue_init_errors(ps_global); + return (NO_OP_COMMAND); + } + ucs = read_char(tm); + ps_global->in_init_seq = 1; /* no output please */ + for(k = 0; k < commas; k++) + if(list[k]) fs_give((void **)&list[k]); + if (list) fs_give((void **)list); + } + } if(ucs != NO_OP_COMMAND && ucs != NO_OP_IDLE && ucs != KEY_RESIZE) zero_new_mail_count(); @@ -307,7 +362,7 @@ optionally_enter(char *utf8string, int y_base, int x_base, int utf8string_size, (escape_list && escape_list[0].ch != -1 && escape_list[0].label) ? escape_list[0].label: "")); - if(!ps_global->ttyo) + if(!ps_global->ttyo || ps_global->send_immediately) return(pre_screen_config_opt_enter(utf8string, utf8string_size, utf8prompt, escape_list, help, flags)); @@ -1154,10 +1209,11 @@ process_config_input(UCS *ch) } } } - + ps_global->initial_cmds_offset++; if(!*ps_global->initial_cmds && ps_global->free_initial_cmds){ fs_give((void **) &ps_global->free_initial_cmds); ps_global->initial_cmds = NULL; + firsttime = (char) 1; } return(ret); diff --git a/alpine/osdep/termin.gen.h b/alpine/osdep/termin.gen.h index 18c7c0de..5309eba2 100644 --- a/alpine/osdep/termin.gen.h +++ b/alpine/osdep/termin.gen.h @@ -33,6 +33,7 @@ int process_config_input(UCS *); int key_recorder(int); int key_playback(int); int recent_keystroke(int *, char *, size_t); +void fake_config_screen(struct ttyo **); int init_tty_driver(struct pine *); void end_tty_driver(struct pine *); int PineRaw(int); diff --git a/alpine/osdep/termin.unx.c b/alpine/osdep/termin.unx.c index 451d18c2..b23c6f22 100644 --- a/alpine/osdep/termin.unx.c +++ b/alpine/osdep/termin.unx.c @@ -111,6 +111,8 @@ open_mailer for details. int init_tty_driver(struct pine *ps) { + if(ps->send_immediately) + return 0; #ifdef MOUSE if(F_ON(F_ENABLE_MOUSE, ps_global)) init_mouse(); @@ -677,6 +679,9 @@ a lot of at UW void init_keyboard(int use_fkeys) { + if (ps_global->send_immediately) + return; + if(use_fkeys && (!strucmp(term_name,"vt102") || !strucmp(term_name,"vt100"))) printf("\033\133\071\071\150"); @@ -694,6 +699,9 @@ init_keyboard(int use_fkeys) void end_keyboard(int use_fkeys) { + if(ps_global->send_immediately) + return; + if(use_fkeys && (!strcmp(term_name, "vt102") || !strcmp(term_name, "vt100"))){ printf("\033\133\071\071\154"); diff --git a/alpine/osdep/termout.unx.c b/alpine/osdep/termout.unx.c index faf8c7f9..88df57ea 100644 --- a/alpine/osdep/termout.unx.c +++ b/alpine/osdep/termout.unx.c @@ -206,6 +206,9 @@ config_screen(struct ttyo **tt) void init_screen(void) { + if(ps_global->send_immediately) + return; + if(_termcap_init) /* init using termcap's rule */ tputs(_termcap_init, 1, outchar); @@ -313,6 +316,9 @@ end_screen(char *message, int exit_val) { int footer_rows_was_one = 0; + if(ps_global->send_immediately) + return; + if(!panicking()){ dprint((9, "end_screen called\n")); @@ -367,7 +373,7 @@ ClearScreen(void) _line = 0; /* clear leaves us at top... */ _col = 0; - if(ps_global->in_init_seq) + if(ps_global->in_init_seq || ps_global->send_immediately) return; mark_status_unknown(); diff --git a/alpine/radio.c b/alpine/radio.c index fc5c9ab9..436be052 100644 --- a/alpine/radio.c +++ b/alpine/radio.c @@ -122,7 +122,7 @@ want_to(char *question, int dflt, int on_ctrl_C, HelpType help, int flags) int rv, width; size_t len; - if(!ps_global->ttyo) + if(!ps_global->ttyo || ps_global->send_immediately) return(pre_screen_config_want_to(question, dflt, on_ctrl_C)); #ifdef _WINDOWS if (mswin_usedialog ()) { diff --git a/alpine/reply.c b/alpine/reply.c index f0a40651..e72268c0 100644 --- a/alpine/reply.c +++ b/alpine/reply.c @@ -62,7 +62,8 @@ The evolution continues... #include "../pith/tempfile.h" #include "../pith/busy.h" #include "../pith/ablookup.h" - +#include "../pith/copyaddr.h" +#include "../pith/rules.h" /* * Internal Prototypes @@ -109,11 +110,12 @@ reply(struct pine *pine_state, ACTION_S *role_arg) long msgno, j, totalm, rflags, *seq = NULL; int i, include_text = 0, times = -1, warned = 0, rv = 0, flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0; - int rolemsg = 0, copytomsg = 0; + int rolemsg = 0, copytomsg = 0, do_role_early = 0; gf_io_t pc; PAT_STATE dummy; REDRAFT_POS_S *redraft_pos = NULL; ACTION_S *role = NULL, *nrole; + RULE_RESULT *rule; #if defined(DOS) && !defined(_WINDOWS) char *reserve; #endif @@ -139,6 +141,69 @@ reply(struct pine *pine_state, ACTION_S *role_arg) && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global)) reply_raw_body = 1; + /* Setup possible role */ + if(role_arg) + role = copy_action(role_arg); + + if(!role && F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state)){ + for(msgno = mn_first_cur(pine_state->msgmap); + msgno > 0L; msgno = mn_next_cur(pine_state->msgmap)){ + + env = pine_mail_fetchstructure(pine_state->mail_stream, + mn_m2raw(pine_state->msgmap, msgno), + NULL); + if(!env) { + q_status_message1(SM_ORDER,3,4, + _("Error fetching message %s. Can't reply to it."), + long2string(msgno)); + goto done_early; + } + + if(rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE , env)){ + RULELIST *list = get_rulelist_from_code(V_REPLY_INDENT_RULES, + ps_global->rule_list); + RULE_S *prule = get_rule(list, rule->number); + if(condition_contains_token(prule->condition, ROLE_TOKEN)) + do_role_early++; + if(rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + } + } + + if(do_role_early){ + rflags = ROLE_REPLY; + if(nonempty_patterns(rflags, &dummy)){ + /* setup default role */ + nrole = NULL; + j = mn_first_cur(pine_state->msgmap); + do { + role = nrole; + nrole = set_role_from_msg(pine_state, rflags, + mn_m2raw(pine_state->msgmap, j), + NULL); + } while(nrole && (!role || nrole == role) + && (j=mn_next_cur(pine_state->msgmap)) > 0L); + + if(!role || nrole == role) + role = nrole; + else + role = NULL; + + if(confirm_role(rflags, &role)) + role = combine_inherited_role(role); + else{ /* cancel reply */ + role = NULL; + cmd_cancelled("Reply"); + goto done_early; + } + } + } + + if (role) + ps_global->role = cpystr(role->nick); /* remember the role */ + /* * We may have to loop through first to figure out what default * reply-indent-string to offer... @@ -230,7 +295,7 @@ reply(struct pine *pine_state, ACTION_S *role_arg) if(!times){ /* only first time */ char *p = cpystr(prefix); - if((include_text=reply_text_query(pine_state,totalm,&prefix)) < 0) + if((include_text=reply_text_query(pine_state,totalm,env,&prefix)) < 0) goto done_early; /* edited prefix? */ @@ -286,8 +351,18 @@ reply(struct pine *pine_state, ACTION_S *role_arg) outgoing->subject = cpystr("Re: several messages"); } } - else - outgoing->subject = reply_subject(env->subject, NULL, 0); + else{ + RULE_RESULT *rule; + rule = get_result_rule(V_RESUB_RULES,FOR_RESUB|FOR_TRIM , env); + if (rule){ + outgoing->subject = reply_subject(rule->result, NULL, 0); + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + else + outgoing->subject = reply_subject(env->subject, NULL, 0); + } } /* fill reply header */ @@ -306,13 +381,15 @@ reply(struct pine *pine_state, ACTION_S *role_arg) if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */ goto done_early; - /* Setup possible role */ - if(role_arg) - role = copy_action(role_arg); + if (ps_global->reply.role_chosen){ + if(role_arg) + free_action(&role); + role = ps_global->reply.role_chosen; + } - if(!role){ + if(!do_role_early){ rflags = ROLE_REPLY; - if(nonempty_patterns(rflags, &dummy)){ + if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){ /* setup default role */ nrole = NULL; j = mn_first_cur(pine_state->msgmap); @@ -548,7 +625,7 @@ reply(struct pine *pine_state, ACTION_S *role_arg) if(orig_body == NULL || orig_body->type == TYPETEXT || reply_raw_body) { reply_delimiter(env, role, pc); - if(F_ON(F_INCLUDE_HEADER, pine_state)) + if(ps_global->reply.inchdr) reply_forward_header(pine_state->mail_stream, mn_m2raw(pine_state->msgmap,msgno), NULL, env, pc, prefix); @@ -567,7 +644,7 @@ reply(struct pine *pine_state, ACTION_S *role_arg) && orig_body->nested.part->body.type == TYPETEXT) { /*---- First part of the message is text -----*/ reply_delimiter(env, role, pc); - if(F_ON(F_INCLUDE_HEADER, pine_state)) + if(ps_global->reply.inchdr) reply_forward_header(pine_state->mail_stream, mn_m2raw(pine_state->msgmap, msgno), @@ -721,6 +798,9 @@ reply(struct pine *pine_state, ACTION_S *role_arg) if(prefix) fs_give((void **)&prefix); + if (ps_global->role) + fs_give((void **)&ps_global->role); + if(fcc) fs_give((void **) &fcc); @@ -876,7 +956,8 @@ confirm_role(long int rflags, ACTION_S **role) prompt[sizeof(prompt)-1] = '\0'; - cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey, + cmd = ps_global->send_immediately ? 'n' : + radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey, 'y', 'x', help, RB_NORM); switch(cmd){ @@ -1003,48 +1084,113 @@ reply_using_replyto_query(void) * 0 if we're NOT to include the text * -1 on cancel or error */ +#define MAX_REPLY_OPTIONS 8 int -reply_text_query(struct pine *ps, long int many, char **prefix) +reply_text_query(struct pine *ps, long int many, ENVELOPE *env, char **prefix) { int ret, edited = 0; - static ESCKEY_S rtq_opts[] = { - {'y', 'y', "Y", N_("Yes")}, - {'n', 'n', "N", N_("No")}, - {-1, 0, NULL, NULL}, /* may be overridden below */ - {-1, 0, NULL, NULL} - }; + static ESCKEY_S compose_style[MAX_REPLY_OPTIONS]; + int ekey_num; + int orig_sf; + + orig_sf = *prefix && **prefix ? (F_OFF(F_QUELL_FLOWED_TEXT, ps) + && F_OFF(F_STRIP_WS_BEFORE_SEND, ps) + && (strcmp(*prefix, "> ") == 0 + || strcmp(*prefix, ">") == 0)) : 0; + + ps_global->reply.no_send_flowed = !orig_sf; + ps_global->reply.role_chosen = NULL; + ps_global->reply.strip = ps->full_header == 0 + && (F_ON(F_ENABLE_STRIP_SIGDASHES, ps) + || F_ON(F_ENABLE_SIGDASHES, ps)); + ps_global->reply.attach = F_ON(F_ATTACHMENTS_IN_REPLY, ps); + ps_global->reply.inchdr = F_ON(F_INCLUDE_HEADER, ps); if(F_ON(F_AUTO_INCLUDE_IN_REPLY, ps) - && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps)) + && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps) && F_OFF(F_ALT_REPLY_MENU,ps)) return(1); - while(1){ - if(many > 1L) - /* TRANSLATORS: The final three %s's can probably be safely ignored */ - snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s original messages in Reply%s%s%s? "), - comatose(many), - F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "", - F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "", - F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : ""); - else - snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include original message in Reply%s%s%s? "), + while(1){ + /* TRANSLATORS: The final five %s's can probably be safely ignored */ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply%s%s%s%s%s%s? "), + (many > 1L) ? comatose(many) : "", + (many > 1L) ? " " : "", + (many > 1L) ? "s" : "", + (many > 1L) ? "s" : "", F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "", F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "", + ps_global->reply.role_chosen ? "\" and role \"" : "", + ps_global->reply.role_chosen ? ps_global->reply.role_chosen->nick : "", F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : ""); - if(F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){ - rtq_opts[2].ch = ctrl('R'); - rtq_opts[2].rval = 'r'; - rtq_opts[2].name = "^R"; - rtq_opts[2].label = N_("Edit Indent String"); + ekey_num = 0; + compose_style[ekey_num].ch = 'y'; + compose_style[ekey_num].rval = 'y'; + compose_style[ekey_num].name = "Y"; + compose_style[ekey_num++].label = N_("Yes"); + + compose_style[ekey_num].ch = 'n'; + compose_style[ekey_num].rval = 'n'; + compose_style[ekey_num].name = "N"; + compose_style[ekey_num++].label = N_("No"); + + if (F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){ + compose_style[ekey_num].ch = ctrl('R'); + compose_style[ekey_num].rval = 'r'; + compose_style[ekey_num].name = "^R"; + compose_style[ekey_num++].label = N_("Edit Indent String"); } - else - rtq_opts[2].ch = -1; + + /***** Alternate Reply Menu ********/ + + if (F_ON(F_ALT_REPLY_MENU, ps)){ + unsigned which_help; + + if (F_ON(F_ENABLE_STRIP_SIGDASHES, ps) || + F_ON(F_ENABLE_SIGDASHES, ps)){ + compose_style[ekey_num].ch = 's'; + compose_style[ekey_num].rval = 'S'; + compose_style[ekey_num].name = "S"; + compose_style[ekey_num++].label = ps_global->reply.strip + ? N_("No Strip"): N_("Strip Sig"); + } + + compose_style[ekey_num].ch = 'r'; + compose_style[ekey_num].rval = 'R'; + compose_style[ekey_num].name = "R"; + compose_style[ekey_num++].label = N_("Set Role"); + + if(orig_sf){ + compose_style[ekey_num].ch = 'f'; + compose_style[ekey_num].rval = 'F'; + compose_style[ekey_num].name = "F"; + compose_style[ekey_num++].label = ps_global->reply.no_send_flowed + ? N_("Quell Flow") : N_("Send Flowd"); + } + + compose_style[ekey_num].ch = 'a'; + compose_style[ekey_num].rval = 'A'; + compose_style[ekey_num].name = "A"; + compose_style[ekey_num++].label = ps_global->reply.attach + ? N_("No Attach"): N_("Inc Attac"); + + compose_style[ekey_num].ch = 'h'; + compose_style[ekey_num].rval = 'H'; + compose_style[ekey_num].name = "H"; + compose_style[ekey_num++].label = ps_global->reply.inchdr + ? N_("No Header") : N_("Inc Head"); + + } + compose_style[ekey_num].ch = -1; + compose_style[ekey_num].name = NULL; + compose_style[ekey_num].label = NULL; + + /***** End Alt Reply Menu *********/ switch(ret = radio_buttons(tmp_20k_buf, ps->ttyo->screen_rows > 4 ? -FOOTER_ROWS(ps_global) : -1, - rtq_opts, + compose_style, (edited || F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)) ? 'y' : 'n', 'x', NO_HELP, RB_SEQ_SENSITIVE)){ @@ -1052,6 +1198,46 @@ reply_text_query(struct pine *ps, long int many, char **prefix) cmd_cancelled("Reply"); return(-1); + case 'F': + ps_global->reply.no_send_flowed = (ps_global->reply.no_send_flowed + 1) % 2; + break; + + case 'S': + ps_global->reply.strip = (ps_global->reply.strip + 1) % 2; + break; + + case 'A': + ps_global->reply.attach = (ps_global->reply.attach + 1) % 2; + break; + + case 'H': + ps_global->reply.inchdr = (ps_global->reply.inchdr + 1) % 2; + break; + + + case 'R': + { + void (*prev_screen)(struct pine *) = ps->prev_screen, + (*redraw)(void) = ps->redrawer; + ps->redrawer = NULL; + ps->next_screen = SCREEN_FUN_NULL; + if(role_select_screen(ps, &ps_global->reply.role_chosen, 1) < 0){ + cmd_cancelled("Reply"); + ps->next_screen = prev_screen; + ps->redrawer = redraw; + if (ps->redrawer) + (*ps->redrawer)(); + continue; + } + ps->next_screen = prev_screen; + ps->redrawer = redraw; + if(ps_global->reply.role_chosen) + ps_global->reply.role_chosen = combine_inherited_role(ps_global->reply.role_chosen); + } + if (ps->redrawer) + (*ps->redrawer)(); + break; + case 'r': if(prefix && *prefix){ int done = 0; @@ -1075,6 +1261,12 @@ reply_text_query(struct pine *ps, long int many, char **prefix) if(flags & OE_USER_MODIFIED){ fs_give((void **)prefix); *prefix = removing_quotes(cpystr(buf)); + orig_sf = *prefix && **prefix ? + (F_OFF(F_QUELL_FLOWED_TEXT, ps) + && F_OFF(F_STRIP_WS_BEFORE_SEND, ps) + && (strcmp(*prefix, "> ") == 0 + || strcmp(*prefix, ">") == 0)) : 0; + ps_global->reply.no_send_flowed = !orig_sf; edited = 1; } @@ -1471,9 +1663,14 @@ forward(struct pine *ps, ACTION_S *role_arg) } } - if(role) + if (ps_global->role) + fs_give((void **)&ps_global->role); + + if(role){ q_status_message1(SM_ORDER, 3, 4, _("Forwarding using role \"%s\""), role->nick); + ps_global->role = cpystr(role->nick); + } if(role && role->template){ char *filtered; @@ -1705,6 +1902,7 @@ forward(struct pine *ps, ACTION_S *role_arg) #if defined(DOS) && !defined(_WINDOWS) free((void *)reserve); #endif + outgoing->sparep = env && env->from ? copyaddr(env->from) : NULL; pine_send(outgoing, &body, "FORWARD MESSAGE", role, NULL, &reply, redraft_pos, NULL, NULL, 0); @@ -2457,6 +2655,8 @@ display_message_for_pico(int x) { int rv; + if(ps_global->send_immediately) + return 0; clear_cursor_pos(); /* can't know where cursor is */ mark_status_dirty(); /* don't count on cached text */ fix_windsize(ps_global); diff --git a/alpine/reply.h b/alpine/reply.h index 2c239071..134f8fc2 100644 --- a/alpine/reply.h +++ b/alpine/reply.h @@ -28,7 +28,7 @@ int reply(struct pine *, ACTION_S *); int confirm_role(long, ACTION_S **); int reply_to_all_query(int *); int reply_using_replyto_query(void); -int reply_text_query(struct pine *, long, char **); +int reply_text_query(struct pine *, long, ENVELOPE *, char **); int reply_news_test(ENVELOPE *, ENVELOPE *); char *get_signature_file(char *, int, int, int); int forward(struct pine *, ACTION_S *); diff --git a/alpine/roleconf.c b/alpine/roleconf.c index fad1e661..be046acf 100644 --- a/alpine/roleconf.c +++ b/alpine/roleconf.c @@ -140,8 +140,13 @@ role_select_screen(struct pine *ps, ACTION_S **role, int alt_compose) if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){ + if(!ps->send_immediately) q_status_message(SM_ORDER, 3, 3, _("No roles available. Use Setup/Rules to add roles.")); + else{ + printf( _("No roles available. Use Setup/Rules to add roles.")); + exit(-1); + } return(ret); } @@ -4478,11 +4483,11 @@ role_config_edit_screen(struct pine *ps, PAT_S *def, char *title, long int rflag ctmp->tool = role_sort_tool; ctmp->valoffset = rindent; ctmp->flags |= CF_NOSELECT; - ctmp->value = cpystr(set_choose); \ + ctmp->value = cpystr(set_choose); pval = PVAL(&sort_act_var, ew); if(pval) - decode_sort(pval, &def_sort, &def_sort_rev); + decode_sort(pval, &def_sort, &def_sort_rev, 0); /* allow user to set their default sort order */ new_confline(&ctmp)->var = &sort_act_var; @@ -4492,7 +4497,7 @@ role_config_edit_screen(struct pine *ps, PAT_S *def, char *title, long int rflag ctmp->tool = role_sort_tool; ctmp->valoffset = rindent; ctmp->varmem = -1; - ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0); + ctmp->value = generalized_sort_pretty_value(ps, ctmp, 0, 0); for(j = 0; j < 2; j++){ for(i = 0; ps->sort_types[i] != EndofList; i++){ @@ -4504,7 +4509,7 @@ role_config_edit_screen(struct pine *ps, PAT_S *def, char *title, long int rflag ctmp->valoffset = rindent; ctmp->varmem = i + (j * EndofList); ctmp->value = generalized_sort_pretty_value(ps, ctmp, - 0); + 0, 0); } } @@ -5437,7 +5442,7 @@ role_config_edit_screen(struct pine *ps, PAT_S *def, char *title, long int rflag (*result)->patgrp->stat_boy = PAT_STAT_EITHER; if(sort_act){ - decode_sort(sort_act, &def_sort, &def_sort_rev); + decode_sort(sort_act, &def_sort, &def_sort_rev, 0); (*result)->action->sort_is_set = 1; (*result)->action->sortorder = def_sort; (*result)->action->revsort = (def_sort_rev ? 1 : 0); @@ -7706,6 +7711,11 @@ role_text_tool_inick(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags) if(apval) *apval = (role && role->nick) ? cpystr(role->nick) : NULL; + if (ps_global->role) + fs_give((void **)&ps_global->role); + if (role && role->nick) + ps_global->role = cpystr(role->nick); + if((*cl)->value) fs_give((void **)&((*cl)->value)); diff --git a/alpine/send.c b/alpine/send.c index 852b611c..06c43b65 100644 --- a/alpine/send.c +++ b/alpine/send.c @@ -63,7 +63,7 @@ static char rcsid[] = "$Id: send.c 1142 2008-08-13 17:22:21Z hubert@u.washington #include "../pith/mimetype.h" #include "../pith/send.h" #include "../pith/smime.h" - +#include "../pith/rules.h" typedef struct body_particulars { unsigned short type, encoding, had_csp; @@ -239,6 +239,11 @@ alt_compose_screen(struct pine *pine_state) role->nick = cpystr("Default Role"); } + if (ps_global->role) + fs_give((void **)&ps_global->role); + + ps_global->role = cpystr(role->nick); + pine_state->redrawer = NULL; compose_mail(NULL, NULL, role, NULL, NULL); free_action(&role); @@ -448,8 +453,12 @@ compose_mail(char *given_to, char *fcc_arg, ACTION_S *role_arg, ps_global->next_screen = prev_screen; ps_global->redrawer = redraw; - if(role) + if (ps_global->role) + fs_give((void **)&ps_global->role); + if(role){ role = combine_inherited_role(role); + ps_global->role = cpystr(role->nick); + } } break; @@ -614,6 +623,7 @@ compose_mail(char *given_to, char *fcc_arg, ACTION_S *role_arg, if(given_to) rfc822_parse_adrlist(&outgoing->to, given_to, ps_global->maildomain); + outgoing->subject = cpystr(ps_global->subject); outgoing->message_id = generate_message_id(); /* @@ -644,9 +654,14 @@ compose_mail(char *given_to, char *fcc_arg, ACTION_S *role_arg, } } - if(role) + if (ps_global->role) + fs_give((void **)&ps_global->role); + + if(role){ q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""), role->nick); + ps_global->role = cpystr(role->nick); + } /* * The type of storage object allocated below is vitally @@ -912,7 +927,7 @@ static struct headerentry he_template[]={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, {"From : ", "From", h_composer_from, 10, 0, NULL, build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, - 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, {"Reply-To: ", "Reply To", h_composer_reply_to, 10, 0, NULL, build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, @@ -1782,6 +1797,9 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, pbf = &pbuf1; standard_picobuf_setup(pbf); + pbf->auto_cmds = ps_global->initial_cmds_backup + + ps_global->initial_cmds_offset; + /* * Cancel any pending initial commands since pico uses a different * input routine. If we didn't cancel them, they would happen after @@ -2305,6 +2323,11 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, he->rich_header = 0; } } + if (F_ON(F_ALLOW_CHANGING_FROM, ps_global) && + !ps_global->never_allow_changing_from){ + he->display_it = 1; /* show it */ + he->rich_header = 0; + } he_from = he; break; @@ -2414,6 +2437,26 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, removing_trailing_white_space(pf->textbuf); (void)removing_double_quotes(pf->textbuf); build_address(pf->textbuf, &addr, NULL, NULL, NULL); + if (!strncmp(pf->name,"Lcc",3) && addr && *addr){ + RULE_RESULT *rule; + + outgoing->date = (unsigned char *) cpystr(addr); + ps_global->procid = cpystr("fwd-lcc"); + rule = get_result_rule(V_FORWARD_RULES, + FOR_COMPOSE|FOR_TRIM, outgoing); + if (rule){ + addr = cpystr(rule->result); + removing_trailing_white_space(addr); + (void)removing_extra_stuff(addr); + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + fs_give((void **)&ps_global->procid); + if (outgoing->date) + fs_give((void **)&outgoing->date); + } + rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain); fs_give((void **)&addr); @@ -2983,7 +3026,12 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, #ifdef _WINDOWS mswin_setwindowmenu (MENU_DEFAULT); #endif - fix_windsize(ps_global); + if (ps_global->send_immediately){ + if(ps_global->free_initial_cmds_backup) + fs_give((void **)&ps_global->free_initial_cmds_backup); + } + else + fix_windsize(ps_global); /* * Only reinitialize signals if we didn't receive an interesting @@ -3042,7 +3090,9 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, if(outgoing->return_path) mail_free_address(&outgoing->return_path); - outgoing->return_path = rfc822_cpy_adr(outgoing->from); + outgoing->return_path = F_ON(F_USE_DOMAIN_NAME,ps_global) + ? rfc822_cpy_adr(generate_from()) + : rfc822_cpy_adr(outgoing->from); /* * Don't ever believe the sender that is there. @@ -3719,10 +3769,16 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, if(sending_filter_requested && !filter_message_text(sending_filter_requested, outgoing, *body, &orig_so, &header)){ - q_status_message1(SM_ORDER, 3, 3, + if (!ps_global->send_immediately){ + q_status_message1(SM_ORDER, 3, 3, _("Problem filtering! Nothing sent%s."), fcc ? " or saved to fcc" : ""); - continue; + continue; + } + else{ + fprintf(stderr, _("Problem filtering! Nothing sent or saved to Fcc\n")); + exit(-1); + } } /*------ Actually post -------*/ @@ -3966,6 +4022,8 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, /*----- Mail Post FAILED, back to composer -----*/ if(result & (P_MAIL_LOSE | P_FCC_LOSE)){ dprint((1, "Send failed, continuing\n")); + if (ps_global->send_immediately) + exit(1); if(result & P_FCC_LOSE){ /* @@ -4000,6 +4058,7 @@ pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, update_answered_flags(reply); /*----- Signed, sealed, delivered! ------*/ + if (!ps_global->send_immediately) q_status_message(SM_ORDER, 0, 3, pine_send_status(result, fcc, tmp_20k_buf, SIZEOF_20KBUF, NULL)); @@ -4466,7 +4525,7 @@ send_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_ return(1); } - if(F_ON(F_SEND_WO_CONFIRM, ps_global)){ + if(!ps_global->send_immediately && F_ON(F_SEND_WO_CONFIRM, ps_global)){ if(result) *result = NULL; @@ -4646,7 +4705,8 @@ send_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_ opts[i].ch = -1; - fix_windsize(ps_global); + if (!ps_global->send_immediately) + fix_windsize(ps_global); while(1){ if(filters && filters->filter && (p = strindex(filters->filter, ' '))) @@ -4828,7 +4888,8 @@ send_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_ if(double_rad + ((call_mailer_flags & CM_DSN_SHOW) ? 4 : F_ON(F_DSN, ps_global) ? 1 : 0) > 11) - rv = double_radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts, + rv = ps_global->send_immediately ? 'y' : + double_radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts, 'y', 'z', (F_ON(F_DSN, ps_global) && allow_flowed) ? h_send_prompt_dsn_flowed : @@ -4837,7 +4898,8 @@ send_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_ h_send_prompt, RB_NORM); else - rv = radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts, + rv = ps_global->send_immediately ? 'y' : + radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts, 'y', 'z', (double_rad + ((call_mailer_flags & CM_DSN_SHOW) @@ -5174,11 +5236,13 @@ cancel_for_pico(void (*redraw_pico)(void)) {'c', 'c', "C", N_("Confirm")}, {'n', 'n', "N", N_("No")}, {'y', 'y', "", ""}, + {'t', 't', "T", N_("CounT")}, {-1, 0, NULL, NULL} }; ps_global->redrawer = redraw_pico; fix_windsize(ps_global); + pbf->curpos[0] = '\0'; while(1){ rv = radio_buttons(prompt, -FOOTER_ROWS(ps_global), opts, @@ -5191,12 +5255,16 @@ cancel_for_pico(void (*redraw_pico)(void)) q_status_message(SM_INFO, 1, 3, _(" Type \"C\" to cancel message ")); display_message('x'); } + else if(rv == 't'){ + showcpos(1,0); + break; + } else break; } ps_global->redrawer = redraw; - return(rstr); + return(pbf->curpos[0] ? pbf->curpos : rstr); } @@ -5300,9 +5368,11 @@ filter_message_text(char *fcmd, ENVELOPE *outgoing, struct mail_bodystruct *body if((tmp_so = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){ gf_set_so_writec(&pc, tmp_so); ps_global->mangled_screen = 1; - suspend_busy_cue(); - ClearScreen(); - fflush(stdout); + if (!ps_global->send_immediately){ + suspend_busy_cue(); + ClearScreen(); + fflush(stdout); + } if(tmpf){ PIPE_S *fpipe; @@ -5414,8 +5484,10 @@ filter_message_text(char *fcmd, ENVELOPE *outgoing, struct mail_bodystruct *body set_mime_type_by_grope(b); } - ClearScreen(); - resume_busy_cue(0); + if (!ps_global->send_immediately){ + ClearScreen(); + resume_busy_cue(0); + } } else errstr = "Can't create space for filtered text."; @@ -5446,10 +5518,16 @@ filter_message_text(char *fcmd, ENVELOPE *outgoing, struct mail_bodystruct *body if(tmp_so) so_give(&tmp_so); - q_status_message1(SM_ORDER | SM_DING, 3, 6, _("Problem filtering: %s"), + if (!ps_global->send_immediately){ + q_status_message1(SM_ORDER | SM_DING, 3, 6, _("Problem filtering: %s"), errstr); - dprint((1, "Filter FAILED: %s\n", + dprint((1, "Filter FAILED: %s\n", errstr ? errstr : "?")); + } + else{ + fprintf(stderr, _("Filter FAILED: %s\n"), errstr ? errstr : "?"); + exit(-1); + } } return(errstr == NULL); diff --git a/alpine/setup.c b/alpine/setup.c index 3ac90f21..4a30bec9 100644 --- a/alpine/setup.c +++ b/alpine/setup.c @@ -258,7 +258,7 @@ option_screen(struct pine *ps, int edit_exceptions) ctmpa->flags |= CF_NOSELECT; ctmpa->value = cpystr("--- ----------------------"); - decode_sort(pval, &def_sort, &def_sort_rev); + decode_sort(pval, &def_sort, &def_sort_rev, 0); for(j = 0; j < 2; j++){ for(i = 0; ps->sort_types[i] != EndofList; i++){ @@ -273,6 +273,55 @@ option_screen(struct pine *ps, int edit_exceptions) } } } + else if(vtmp == &ps->vars[V_THREAD_SORT_KEY]){ /* radio case */ + SortOrder thread_def_sort; + int thread_def_sort_rev, lv; + + ctmpa->flags |= CF_NOSELECT; + ctmpa->keymenu = &config_radiobutton_keymenu; + ctmpa->tool = NULL; + + /* put a nice delimiter before list */ + new_confline(&ctmpa)->var = NULL; + ctmpa->varnamep = ctmpb; + ctmpa->keymenu = &config_radiobutton_keymenu; + ctmpa->help = NO_HELP; + ctmpa->tool = radiobutton_tool; + ctmpa->valoffset = 12; + ctmpa->flags |= CF_NOSELECT; + ctmpa->value = cpystr("Set Thread Sort Options"); + + new_confline(&ctmpa)->var = NULL; + ctmpa->varnamep = ctmpb; + ctmpa->keymenu = &config_radiobutton_keymenu; + ctmpa->help = NO_HELP; + ctmpa->tool = radiobutton_tool; + ctmpa->valoffset = 12; + ctmpa->flags |= CF_NOSELECT; + ctmpa->value = cpystr("--- ----------------------"); + + /* find longest value's name */ + for(lv = 0, i = 0; ps->sort_types[i] != EndofList; i++) + if(lv < (j = strlen(sort_name(ps->sort_types[i])))) + lv = j; + + decode_sort(pval, &thread_def_sort, &thread_def_sort_rev, 1); + + for(j = 0; j < 2; j++){ + for(i = 0; ps->sort_types[i] != EndofList; i++){ + if (allowed_thread_key(ps->sort_types[i])){ + new_confline(&ctmpa)->var = vtmp; + ctmpa->varnamep = ctmpb; + ctmpa->keymenu = &config_radiobutton_keymenu; + ctmpa->help = config_help(vtmp - ps->vars, 0); + ctmpa->tool = radiobutton_tool; + ctmpa->valoffset = 12; + ctmpa->varmem = i + (j * EndofList); + ctmpa->value = pretty_value(ps, ctmpa); + } + } + } + } else if(vtmp == &ps->vars[V_USE_ONLY_DOMAIN_NAME]){ /* yesno case */ ctmpa->keymenu = &config_yesno_keymenu; ctmpa->tool = yesno_tool; @@ -335,6 +384,7 @@ option_screen(struct pine *ps, int edit_exceptions) } else{ if(vtmp == &ps->vars[V_FILLCOL] + || vtmp == &ps->vars[V_SLEEP] || vtmp == &ps->vars[V_QUOTE_SUPPRESSION] || vtmp == &ps->vars[V_OVERLAP] || vtmp == &ps->vars[V_MAXREMSTREAM] @@ -464,6 +514,15 @@ option_screen(struct pine *ps, int edit_exceptions) } } + pval = PVAL(&ps->vars[V_THREAD_SORT_KEY], ew); + if(vsave[V_THREAD_SORT_KEY].saved_user_val.p && pval + && strcmp(vsave[V_THREAD_SORT_KEY].saved_user_val.p, pval)){ + if(!mn_get_mansort(ps_global->msgmap)){ + clear_index_cache(ps_global->mail_stream, 0); + reset_sort_order(SRT_VRB); + } + } + treat_color_vars_as_text = 0; free_saved_config(ps, &vsave, expose_hidden_config); #ifdef _WINDOWS diff --git a/alpine/status.c b/alpine/status.c index b51c640f..4dd44817 100644 --- a/alpine/status.c +++ b/alpine/status.c @@ -111,6 +111,9 @@ q_status_message(int flags, int min_time, int max_time, char *message) char *clean_msg; size_t mlen; + if (ps_global->send_immediately) + return; + status_message_lock(); /* @@ -605,6 +608,9 @@ flush_status_messages(int skip_last_pause) SMQ_T *q, *copy_of_q; int ding; + if(ps_global->send_immediately) + return; + start_over: status_message_lock(); diff --git a/autom4te.cache/output.0 b/autom4te.cache/output.0 index 22b69dee..09a3082f 100644 --- a/autom4te.cache/output.0 +++ b/autom4te.cache/output.0 @@ -1,7 +1,7 @@ @%:@! /bin/sh @%:@ From configure.ac Rev:1 by chappa@washington.edu. @%:@ Guess values for system-dependent variables and create Makefiles. -@%:@ Generated by GNU Autoconf 2.69 for alpine 2.10.9. +@%:@ Generated by GNU Autoconf 2.69 for alpine 2.11. @%:@ @%:@ Report bugs to <chappa@washington.edu>. @%:@ @@ -730,8 +730,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='alpine' PACKAGE_TARNAME='alpine' -PACKAGE_VERSION='2.10.9' -PACKAGE_STRING='alpine 2.10.9' +PACKAGE_VERSION='2.11' +PACKAGE_STRING='alpine 2.11' PACKAGE_BUGREPORT='chappa@washington.edu' PACKAGE_URL='' @@ -1593,7 +1593,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures alpine 2.10.9 to adapt to many kinds of systems. +\`configure' configures alpine 2.11 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1663,7 +1663,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of alpine 2.10.9:";; + short | recursive ) echo "Configuration of alpine 2.11:";; esac cat <<\_ACEOF @@ -1947,7 +1947,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -alpine configure 2.10.9 +alpine configure 2.11 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2553,7 +2553,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by alpine $as_me 2.10.9, which was +It was created by alpine $as_me 2.11, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3374,7 +3374,7 @@ fi # Define the identity of the package. PACKAGE='alpine' - VERSION='2.10.9' + VERSION='2.11' cat >>confdefs.h <<_ACEOF @@ -19929,7 +19929,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by alpine $as_me 2.10.9, which was +This file was extended by alpine $as_me 2.11, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -19995,7 +19995,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -alpine config.status 2.10.9 +alpine config.status 2.11 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/autom4te.cache/output.1 b/autom4te.cache/output.1 index 22b69dee..09a3082f 100644 --- a/autom4te.cache/output.1 +++ b/autom4te.cache/output.1 @@ -1,7 +1,7 @@ @%:@! /bin/sh @%:@ From configure.ac Rev:1 by chappa@washington.edu. @%:@ Guess values for system-dependent variables and create Makefiles. -@%:@ Generated by GNU Autoconf 2.69 for alpine 2.10.9. +@%:@ Generated by GNU Autoconf 2.69 for alpine 2.11. @%:@ @%:@ Report bugs to <chappa@washington.edu>. @%:@ @@ -730,8 +730,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='alpine' PACKAGE_TARNAME='alpine' -PACKAGE_VERSION='2.10.9' -PACKAGE_STRING='alpine 2.10.9' +PACKAGE_VERSION='2.11' +PACKAGE_STRING='alpine 2.11' PACKAGE_BUGREPORT='chappa@washington.edu' PACKAGE_URL='' @@ -1593,7 +1593,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures alpine 2.10.9 to adapt to many kinds of systems. +\`configure' configures alpine 2.11 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1663,7 +1663,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of alpine 2.10.9:";; + short | recursive ) echo "Configuration of alpine 2.11:";; esac cat <<\_ACEOF @@ -1947,7 +1947,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -alpine configure 2.10.9 +alpine configure 2.11 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2553,7 +2553,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by alpine $as_me 2.10.9, which was +It was created by alpine $as_me 2.11, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3374,7 +3374,7 @@ fi # Define the identity of the package. PACKAGE='alpine' - VERSION='2.10.9' + VERSION='2.11' cat >>confdefs.h <<_ACEOF @@ -19929,7 +19929,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by alpine $as_me 2.10.9, which was +This file was extended by alpine $as_me 2.11, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -19995,7 +19995,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -alpine config.status 2.10.9 +alpine config.status 2.11 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/autom4te.cache/requests b/autom4te.cache/requests index e819e61d..3e12765c 100644 --- a/autom4te.cache/requests +++ b/autom4te.cache/requests @@ -86,13 +86,13 @@ 'gt_PRINTF_POSIX' => 1, 'AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH' => 1, '_LT_AC_LANG_C_CONFIG' => 1, - 'AM_PROG_INSTALL_STRIP' => 1, 'gl_LOCK_EARLY' => 1, + 'AM_PROG_INSTALL_STRIP' => 1, '_m4_warn' => 1, 'AC_LIBTOOL_OBJDIR' => 1, 'gl_FUNC_ARGZ' => 1, - 'AM_SANITY_CHECK' => 1, 'LTOBSOLETE_VERSION' => 1, + 'AM_SANITY_CHECK' => 1, 'AC_LIBTOOL_LANG_GCJ_CONFIG' => 1, 'AC_LIBTOOL_PROG_COMPILER_PIC' => 1, 'LT_LIB_M' => 1, @@ -104,21 +104,21 @@ '_AM_PROG_TAR' => 1, 'LT_SYS_SYMBOL_USCORE' => 1, 'AC_LIBTOOL_GCJ' => 1, - 'LT_FUNC_DLSYM_USCORE' => 1, 'LT_SYS_DLOPEN_DEPLIBS' => 1, - '_LT_AC_LANG_F77' => 1, - 'AC_LIBTOOL_CONFIG' => 1, + 'LT_FUNC_DLSYM_USCORE' => 1, 'AC_LIB_ARG_WITH' => 1, - '_AM_SUBST_NOTMAKE' => 1, + 'AC_LIBTOOL_CONFIG' => 1, + '_LT_AC_LANG_F77' => 1, 'AC_LTDL_DLLIB' => 1, + '_AM_SUBST_NOTMAKE' => 1, '_AM_AUTOCONF_VERSION' => 1, 'AM_DISABLE_SHARED' => 1, '_LTDL_SETUP' => 1, - '_LT_AC_LANG_CXX' => 1, 'AM_PROG_LIBTOOL' => 1, - 'AC_LIB_LTDL' => 1, - '_LT_AC_FILE_LTDLL_C' => 1, + '_LT_AC_LANG_CXX' => 1, 'AM_PROG_LD' => 1, + '_LT_AC_FILE_LTDLL_C' => 1, + 'AC_LIB_LTDL' => 1, 'gt_INTL_MACOSX' => 1, 'AM_ICONV_LINK' => 1, 'AC_LIB_PREPARE_MULTILIB' => 1, @@ -135,60 +135,60 @@ 'LTDL_CONVENIENCE' => 1, '_AM_SET_OPTION' => 1, 'AC_LTDL_PREOPEN' => 1, - '_LT_LINKER_BOILERPLATE' => 1, 'gl_LOCK_EARLY_BODY' => 1, - 'AC_LIBTOOL_PROG_CC_C_O' => 1, + '_LT_LINKER_BOILERPLATE' => 1, 'AC_LIBTOOL_LANG_CXX_CONFIG' => 1, + 'AC_LIBTOOL_PROG_CC_C_O' => 1, 'gl_PREREQ_ARGZ' => 1, 'AC_LIB_PREFIX' => 1, 'gt_TYPE_LONGDOUBLE' => 1, - 'AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, 'LT_SUPPORTED_TAG' => 1, - 'LT_SYS_MODULE_EXT' => 1, + 'AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, 'LT_PROG_RC' => 1, + 'LT_SYS_MODULE_EXT' => 1, 'AC_DEFUN_ONCE' => 1, '_LT_AC_LANG_GCJ' => 1, 'AC_' => 1, 'AC_LTDL_OBJDIR' => 1, - '_LT_PATH_TOOL_PREFIX' => 1, 'gt_INTDIV0' => 1, - 'AC_LIBTOOL_RC' => 1, + '_LT_PATH_TOOL_PREFIX' => 1, 'AM_ICONV' => 1, - 'AM_SILENT_RULES' => 1, - 'AC_DISABLE_FAST_INSTALL' => 1, + 'AC_LIBTOOL_RC' => 1, '_LT_AC_PROG_ECHO_BACKSLASH' => 1, - '_LT_AC_SYS_LIBPATH_AIX' => 1, - '_LT_AC_TRY_DLOPEN_SELF' => 1, + 'AC_DISABLE_FAST_INSTALL' => 1, + 'AM_SILENT_RULES' => 1, 'include' => 1, + '_LT_AC_TRY_DLOPEN_SELF' => 1, + '_LT_AC_SYS_LIBPATH_AIX' => 1, 'LT_AC_PROG_SED' => 1, 'AM_ENABLE_SHARED' => 1, - 'gl_AC_TYPE_UNSIGNED_LONG_LONG' => 1, 'AC_LIB_APPENDTOVAR' => 1, - 'LTDL_INSTALLABLE' => 1, + 'gl_AC_TYPE_UNSIGNED_LONG_LONG' => 1, 'AM_GNU_GETTEXT' => 1, + 'LTDL_INSTALLABLE' => 1, '_LT_AC_LANG_GCJ_CONFIG' => 1, - 'AC_ENABLE_SHARED' => 1, 'AM_POSTPROCESS_PO_MAKEFILE' => 1, + 'AC_ENABLE_SHARED' => 1, 'AC_LIB_WITH_FINAL_PREFIX' => 1, 'gt_TYPE_WINT_T' => 1, - 'AC_ENABLE_STATIC' => 1, 'AC_LIBTOOL_SYS_HARD_LINK_LOCKS' => 1, + 'AC_ENABLE_STATIC' => 1, '_LT_AC_TAGVAR' => 1, 'AC_LIBTOOL_LANG_F77_CONFIG' => 1, 'AM_CONDITIONAL' => 1, 'LT_LIB_DLLOAD' => 1, 'AM_LANGINFO_CODESET' => 1, 'gl_AC_HEADER_INTTYPES_H' => 1, - 'LTDL_INIT' => 1, 'LTVERSION_VERSION' => 1, - 'AM_PROG_INSTALL_SH' => 1, + 'LTDL_INIT' => 1, 'm4_include' => 1, + 'AM_PROG_INSTALL_SH' => 1, 'AC_PROG_EGREP' => 1, - '_AC_AM_CONFIG_HEADER_HOOK' => 1, - 'AC_PATH_MAGIC' => 1, 'PINEVAR' => 1, - 'AC_LTDL_SYSSEARCHPATH' => 1, + 'AC_PATH_MAGIC' => 1, + '_AC_AM_CONFIG_HEADER_HOOK' => 1, 'gl_PREREQ_LOCK' => 1, + 'AC_LTDL_SYSSEARCHPATH' => 1, 'AM_MAKE_INCLUDE' => 1, 'LT_CMD_MAX_LEN' => 1, '_LT_AC_TAGCONFIG' => 1, @@ -200,96 +200,96 @@ 'AC_LIBTOOL_COMPILER_OPTION' => 1, 'AC_DISABLE_SHARED' => 1, '_LT_COMPILER_BOILERPLATE' => 1, - 'AC_LIBTOOL_SETUP' => 1, 'AC_LIBTOOL_WIN32_DLL' => 1, + 'AC_LIBTOOL_SETUP' => 1, 'AC_PROG_LD_RELOAD_FLAG' => 1, 'AC_LTDL_DLSYM_USCORE' => 1, - 'LT_LANG' => 1, 'AM_MISSING_HAS_RUN' => 1, - 'LT_SYS_DLSEARCH_PATH' => 1, + 'LT_LANG' => 1, 'AC_TYPE_LONG_LONG_INT' => 1, + 'LT_SYS_DLSEARCH_PATH' => 1, 'LT_CONFIG_LTDL_DIR' => 1, - 'LT_OUTPUT' => 1, - 'AC_LIBTOOL_DLOPEN_SELF' => 1, 'AM_GNU_GETTEXT_VERSION' => 1, + 'AC_LIBTOOL_DLOPEN_SELF' => 1, + 'LT_OUTPUT' => 1, 'AC_LIB_PROG_LD_GNU' => 1, - 'AC_LIBTOOL_PROG_LD_SHLIBS' => 1, 'AM_NLS' => 1, - 'AC_LIBTOOL_LINKER_OPTION' => 1, + 'AC_LIBTOOL_PROG_LD_SHLIBS' => 1, 'AC_WITH_LTDL' => 1, + 'AC_LIBTOOL_LINKER_OPTION' => 1, 'gt_INTL_SUBDIR_CORE' => 1, - 'AC_LIBTOOL_CXX' => 1, 'LT_AC_PROG_RC' => 1, + 'AC_LIBTOOL_CXX' => 1, 'LT_INIT' => 1, - 'LT_SYS_DLOPEN_SELF' => 1, 'LT_AC_PROG_GCJ' => 1, - 'AM_DISABLE_STATIC' => 1, + 'LT_SYS_DLOPEN_SELF' => 1, 'AM_DEP_TRACK' => 1, + 'AM_DISABLE_STATIC' => 1, '_AC_PROG_LIBTOOL' => 1, '_AM_IF_OPTION' => 1, 'AC_PATH_TOOL_PREFIX' => 1, - 'AC_LIBTOOL_F77' => 1, 'm4_pattern_allow' => 1, + 'AC_LIBTOOL_F77' => 1, 'AM_PATH_PROG_WITH_TEST' => 1, 'AM_SET_LEADING_DOT' => 1, 'LT_AC_PROG_EGREP' => 1, '_AM_DEPENDENCIES' => 1, - 'AC_LIBTOOL_LANG_C_CONFIG' => 1, 'gt_CHECK_DECL' => 1, + 'AC_LIBTOOL_LANG_C_CONFIG' => 1, 'LTOPTIONS_VERSION' => 1, - '_LT_AC_SYS_COMPILER' => 1, 'AC_LIB_LINKFLAGS' => 1, + '_LT_AC_SYS_COMPILER' => 1, 'AM_PROG_NM' => 1, 'AC_LIBLTDL_CONVENIENCE' => 1, - 'AC_DEPLIBS_CHECK_METHOD' => 1, 'AM_GNU_GETTEXT_NEED' => 1, - 'AM_SET_CURRENT_AUTOMAKE_VERSION' => 1, - 'jm_MAINTAINER_MODE' => 1, + 'AC_DEPLIBS_CHECK_METHOD' => 1, 'AC_LIBLTDL_INSTALLABLE' => 1, - 'AC_LTDL_ENABLE_INSTALL' => 1, + 'jm_MAINTAINER_MODE' => 1, + 'AM_SET_CURRENT_AUTOMAKE_VERSION' => 1, 'gl_XSIZE' => 1, - 'AC_LIBTOOL_SYS_DYNAMIC_LINKER' => 1, + 'AC_LTDL_ENABLE_INSTALL' => 1, 'LT_PROG_GCJ' => 1, + 'AC_LIBTOOL_SYS_DYNAMIC_LINKER' => 1, 'gt_GLIBC2' => 1, 'AM_INIT_AUTOMAKE' => 1, 'gl_AC_TYPE_UINTMAX_T' => 1, 'gl_LOCK' => 1, 'AM_INTL_SUBDIR' => 1, - 'AC_DISABLE_STATIC' => 1, 'gl_VISIBILITY' => 1, - 'gt_TYPE_WCHAR_T' => 1, + 'AC_DISABLE_STATIC' => 1, 'PINEVAR_UNQUOTED' => 1, + 'gt_TYPE_WCHAR_T' => 1, 'LT_PATH_NM' => 1, 'AM_MAINTAINER_MODE' => 1, 'AC_LTDL_SHLIBEXT' => 1, '_LT_AC_LOCK' => 1, '_LT_AC_LANG_RC_CONFIG' => 1, 'LT_SYS_MODULE_PATH' => 1, - 'AC_LIBTOOL_POSTDEP_PREDEP' => 1, 'LT_WITH_LTDL' => 1, + 'AC_LIBTOOL_POSTDEP_PREDEP' => 1, 'AC_LTDL_SHLIBPATH' => 1, 'AM_AUX_DIR_EXPAND' => 1, - 'gl_GLIBC21' => 1, 'AC_LIB_LINKFLAGS_FROM_LIBS' => 1, - '_LT_AC_LANG_F77_CONFIG' => 1, + 'gl_GLIBC21' => 1, 'AC_LIBTOOL_PROG_COMPILER_NO_RTTI' => 1, - '_AM_SET_OPTIONS' => 1, + '_LT_AC_LANG_F77_CONFIG' => 1, '_LT_COMPILER_OPTION' => 1, - '_AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, + '_AM_SET_OPTIONS' => 1, 'AM_RUN_LOG' => 1, - 'AC_LIBTOOL_SYS_OLD_ARCHIVE' => 1, - 'AC_LTDL_SYS_DLOPEN_DEPLIBS' => 1, + '_AM_OUTPUT_DEPENDENCY_COMMANDS' => 1, 'AC_LIBTOOL_PICMODE' => 1, + 'AC_LTDL_SYS_DLOPEN_DEPLIBS' => 1, + 'AC_LIBTOOL_SYS_OLD_ARCHIVE' => 1, 'AC_LIB_LINKFLAGS_BODY' => 1, - 'LT_PATH_LD' => 1, - 'AC_CHECK_LIBM' => 1, 'ACX_PTHREAD' => 1, + 'AC_CHECK_LIBM' => 1, + 'LT_PATH_LD' => 1, 'AC_LIBTOOL_SYS_LIB_STRIP' => 1, - '_AM_MANGLE_OPTION' => 1, - 'gt_LC_MESSAGES' => 1, 'AC_TYPE_UNSIGNED_LONG_LONG_INT' => 1, - 'AC_LTDL_SYMBOL_USCORE' => 1, + 'gt_LC_MESSAGES' => 1, + '_AM_MANGLE_OPTION' => 1, 'AC_LIBTOOL_SYS_MAX_CMD_LEN' => 1, + 'AC_LTDL_SYMBOL_USCORE' => 1, 'AM_SET_DEPDIR' => 1, '_LT_CC_BASENAME' => 1, 'gl_LOCK_BODY' => 1, @@ -309,57 +309,57 @@ 'configure.ac' ], { - '_LT_AC_TAGCONFIG' => 1, 'AM_PROG_F77_C_O' => 1, - 'm4_pattern_forbid' => 1, + '_LT_AC_TAGCONFIG' => 1, 'AC_INIT' => 1, - '_AM_COND_IF' => 1, + 'm4_pattern_forbid' => 1, 'AC_CANONICAL_TARGET' => 1, - 'AC_SUBST' => 1, + '_AM_COND_IF' => 1, 'AC_CONFIG_LIBOBJ_DIR' => 1, - 'AC_FC_SRCEXT' => 1, + 'AC_SUBST' => 1, 'AC_CANONICAL_HOST' => 1, + 'AC_FC_SRCEXT' => 1, 'AC_PROG_LIBTOOL' => 1, 'AM_INIT_AUTOMAKE' => 1, - 'AM_PATH_GUILE' => 1, 'AC_CONFIG_SUBDIRS' => 1, + 'AM_PATH_GUILE' => 1, 'AM_AUTOMAKE_VERSION' => 1, 'LT_CONFIG_LTDL_DIR' => 1, - 'AC_REQUIRE_AUX_FILE' => 1, 'AC_CONFIG_LINKS' => 1, - 'm4_sinclude' => 1, + 'AC_REQUIRE_AUX_FILE' => 1, 'LT_SUPPORTED_TAG' => 1, + 'm4_sinclude' => 1, 'AM_MAINTAINER_MODE' => 1, 'AM_NLS' => 1, 'AC_FC_PP_DEFINE' => 1, 'AM_GNU_GETTEXT_INTL_SUBDIR' => 1, - 'AM_MAKEFILE_INCLUDE' => 1, '_m4_warn' => 1, + 'AM_MAKEFILE_INCLUDE' => 1, 'AM_PROG_CXX_C_O' => 1, - '_AM_COND_ENDIF' => 1, '_AM_MAKEFILE_INCLUDE' => 1, + '_AM_COND_ENDIF' => 1, 'AM_ENABLE_MULTILIB' => 1, 'AM_SILENT_RULES' => 1, 'AM_PROG_MOC' => 1, 'AC_CONFIG_FILES' => 1, - 'LT_INIT' => 1, 'include' => 1, - 'AM_PROG_AR' => 1, + 'LT_INIT' => 1, 'AM_GNU_GETTEXT' => 1, + 'AM_PROG_AR' => 1, 'AC_LIBSOURCE' => 1, - 'AM_PROG_FC_C_O' => 1, 'AC_CANONICAL_BUILD' => 1, + 'AM_PROG_FC_C_O' => 1, 'AC_FC_FREEFORM' => 1, - 'AH_OUTPUT' => 1, 'AC_FC_PP_SRCEXT' => 1, - '_AM_SUBST_NOTMAKE' => 1, + 'AH_OUTPUT' => 1, 'AC_CONFIG_AUX_DIR' => 1, - 'sinclude' => 1, - 'AM_PROG_CC_C_O' => 1, + '_AM_SUBST_NOTMAKE' => 1, 'm4_pattern_allow' => 1, - 'AM_XGETTEXT_OPTION' => 1, - 'AC_CANONICAL_SYSTEM' => 1, + 'AM_PROG_CC_C_O' => 1, + 'sinclude' => 1, 'AM_CONDITIONAL' => 1, + 'AC_CANONICAL_SYSTEM' => 1, + 'AM_XGETTEXT_OPTION' => 1, 'AC_CONFIG_HEADERS' => 1, 'AC_DEFINE_TRACE_LITERAL' => 1, 'AM_POT_TOOLS' => 1, diff --git a/autom4te.cache/traces.1 b/autom4te.cache/traces.1 index a6a4ae01..ff7f24a9 100644 --- a/autom4te.cache/traces.1 +++ b/autom4te.cache/traces.1 @@ -13,7 +13,7 @@ m4trace:aclocal.m4:1003: -1- m4_include([m4/nls.m4]) m4trace:aclocal.m4:1004: -1- m4_include([m4/po.m4]) m4trace:aclocal.m4:1005: -1- m4_include([m4/progtest.m4]) m4trace:configure.ac:21: -3- m4_include([VERSION]) -m4trace:configure.ac:21: -1- AC_INIT([alpine], [2.10.9], [chappa@washington.edu]) +m4trace:configure.ac:21: -1- AC_INIT([alpine], [2.11], [chappa@washington.edu]) m4trace:configure.ac:21: -1- m4_pattern_forbid([^_?A[CHUM]_]) m4trace:configure.ac:21: -1- m4_pattern_forbid([_AC_]) m4trace:configure.ac:21: -1- m4_pattern_forbid([^LIBOBJS$], [do not use LIBOBJS directly, use AC_LIBOBJ (see section `AC_LIBOBJ vs LIBOBJS']) @@ -1,7 +1,7 @@ #! /bin/sh # From configure.ac Rev:1 by chappa@washington.edu. # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for alpine 2.10.9. +# Generated by GNU Autoconf 2.69 for alpine 2.11. # # Report bugs to <chappa@washington.edu>. # @@ -730,8 +730,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='alpine' PACKAGE_TARNAME='alpine' -PACKAGE_VERSION='2.10.9' -PACKAGE_STRING='alpine 2.10.9' +PACKAGE_VERSION='2.11' +PACKAGE_STRING='alpine 2.11' PACKAGE_BUGREPORT='chappa@washington.edu' PACKAGE_URL='' @@ -1593,7 +1593,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures alpine 2.10.9 to adapt to many kinds of systems. +\`configure' configures alpine 2.11 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1663,7 +1663,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of alpine 2.10.9:";; + short | recursive ) echo "Configuration of alpine 2.11:";; esac cat <<\_ACEOF @@ -1947,7 +1947,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -alpine configure 2.10.9 +alpine configure 2.11 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2553,7 +2553,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by alpine $as_me 2.10.9, which was +It was created by alpine $as_me 2.11, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3374,7 +3374,7 @@ fi # Define the identity of the package. PACKAGE='alpine' - VERSION='2.10.9' + VERSION='2.11' cat >>confdefs.h <<_ACEOF @@ -19929,7 +19929,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by alpine $as_me 2.10.9, which was +This file was extended by alpine $as_me 2.11, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -19995,7 +19995,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -alpine config.status 2.10.9 +alpine config.status 2.11 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/doc/alpine.1 b/doc/alpine.1 index 6ae54416..96bc462d 100644 --- a/doc/alpine.1 +++ b/doc/alpine.1 @@ -164,6 +164,8 @@ Use function keys for commands. This is the same as running the command .IP \fB-n\ \fInumber\fR 20 Start up with current message-number set to .I number. +.IP \fB-noutf8\fR 20 +Warns Alpine that piped input is not encoded in UTF-8. .IP \fB-o\fR 20 Open first folder read-only. .IP \fB-p\ \fIconfig-file\fR 20 diff --git a/imap/src/c-client/imap4r1.c b/imap/src/c-client/imap4r1.c index 1409b37d..96d1fa19 100644 --- a/imap/src/c-client/imap4r1.c +++ b/imap/src/c-client/imap4r1.c @@ -1156,6 +1156,7 @@ long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) } /* no error if protocol-initiated cancel */ lsterr = cpystr (reply->text); + delete_password(mb, usr); } } while (LOCAL->netstream && !LOCAL->byeseen && trial && @@ -1207,6 +1208,7 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) if (imap_OK (stream,reply = imap_send (stream,"LOGIN",args))) ret = LONGT; /* success */ else { + delete_password(mb, usr); mm_log (reply->text,WARN); if (!LOCAL->referral && (trial == imap_maxlogintrials)) mm_log ("Too many login failures",ERROR); diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c index d80a01f6..8fd55265 100644 --- a/imap/src/c-client/mail.c +++ b/imap/src/c-client/mail.c @@ -52,6 +52,8 @@ static mailcache_t mailcache = mm_cache; static rfc822out_t mail822out = NIL; /* RFC-822 output generator (new style) */ static rfc822outfull_t mail822outfull = NIL; + /* Erase password (client side) */ +static deletepwd_t erase_password = NIL; /* SMTP verbose callback */ static smtpverbose_t mailsmtpverbose = mm_dlog; /* proxy copy routine */ @@ -544,6 +546,11 @@ void *mail_parameters (MAILSTREAM *stream,long function,void *value) case GET_SENDCOMMAND: ret = (void *) mailsendcommand; break; + case SET_ERASEPASSWORD: + erase_password = (deletepwd_t) value; + case GET_ERASEPASSWORD: + ret = (void *) erase_password; + break; case SET_SERVICENAME: servicename = (char *) value; @@ -991,7 +998,7 @@ long mail_create (MAILSTREAM *stream,char *mailbox) MAILSTREAM *ts; char *s,*t,tmp[MAILTMPLEN]; size_t i; - DRIVER *d; + DRIVER *d, *md; /* never allow names with newlines */ if (s = strpbrk (mailbox,"\015\012")) { MM_LOG ("Can't create mailbox with such a name",ERROR); @@ -1015,6 +1022,8 @@ long mail_create (MAILSTREAM *stream,char *mailbox) return NIL; } + /* Hack, we should do this better, but it works */ + for (md = maildrivers; md && strcmp (md->name, "md"); md = md->next); /* see if special driver hack */ if ((mailbox[0] == '#') && ((mailbox[1] == 'd') || (mailbox[1] == 'D')) && ((mailbox[2] == 'r') || (mailbox[2] == 'R')) && @@ -1045,6 +1054,13 @@ long mail_create (MAILSTREAM *stream,char *mailbox) (((*mailbox == '{') || (*mailbox == '#')) && (stream = mail_open (NIL,mailbox,OP_PROTOTYPE | OP_SILENT)))) d = stream->dtb; + else if(mailbox[0] == '#' + && (mailbox[1] == 'm' || mailbox[1] == 'M') + && (mailbox[2] == 'd' || mailbox[2] == 'D' + || mailbox[2] == 'c' || mailbox[2] == 'C') + && mailbox[3] == '/' + && mailbox[4] != '\0') + return (*md->create)(stream, mailbox); else if ((*mailbox != '{') && (ts = default_proto (NIL))) d = ts->dtb; else { /* failed utterly */ sprintf (tmp,"Can't create mailbox %.80s: indeterminate format",mailbox); @@ -3352,13 +3368,13 @@ unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines, long flags) { STRINGLIST *hdrs; - int notfound; + int notfound, fix = text[len - 1] == '\0'; unsigned long i; char c,*s,*e,*t,tmp[MAILTMPLEN]; char *src = text; char *dst = src; char *end = text + len; - text[len] = '\012'; /* guard against running off buffer */ + text[fix ? len - 1 : len] = '\012'; /* guard against running off buffer */ while (src < end) { /* process header */ /* slurp header line name */ for (s = src,e = s + MAILTMPLEN - 1,e = (e < end ? e : end),t = tmp; @@ -3397,6 +3413,10 @@ unsigned long mail_filter (char *text,unsigned long len,STRINGLIST *lines, } } *dst = '\0'; /* tie off destination */ + if(fix){ + text[len] = '\012'; + text[len-1] = '\0'; + } return dst - text; } @@ -6121,6 +6141,15 @@ unsigned int mail_lookup_auth_name (char *mechanism,long flags) return i; return 0; } +/* Client side callback warning to delete wrong password + * + */ +void delete_password(NETMBX *mb, char *user) +{ + deletepwd_t ep = mail_parameters(NULL, GET_ERASEPASSWORD, NULL); + if (ep) (ep)(mb, user); +} + /* Standard TCP/IP network driver */ diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h index 174993e1..0af362e6 100644 --- a/imap/src/c-client/mail.h +++ b/imap/src/c-client/mail.h @@ -177,6 +177,8 @@ #define SET_EXTERNALAUTHID (long) 230 #define GET_SSLCAPATH (long) 231 #define SET_SSLCAPATH (long) 232 +#define GET_ERASEPASSWORD (long) 233 +#define SET_ERASEPASSWORD (long) 234 /* 3xx: TCP/IP */ #define GET_OPENTIMEOUT (long) 300 @@ -353,6 +355,10 @@ #define SET_SCANCONTENTS (long) 573 #define GET_MHALLOWINBOX (long) 574 #define SET_MHALLOWINBOX (long) 575 +#define GET_COURIERSTYLE (long) 576 +#define SET_COURIERSTYLE (long) 577 +#define SET_MDINBOXPATH (long) 578 +#define GET_MDINBOXPATH (long) 579 /* Driver flags */ @@ -1326,6 +1332,7 @@ typedef ADDRESS *(*parsephrase_t) (char *phrase,char *end,char *host); typedef void *(*blocknotify_t) (int reason,void *data); typedef long (*kinit_t) (char *host,char *reason); typedef void (*sendcommand_t) (MAILSTREAM *stream,char *cmd,long flags); +typedef void (*deletepwd_t) (NETMBX *mb,char *user); typedef char *(*newsrcquery_t) (MAILSTREAM *stream,char *mulname,char *name); typedef void (*getacl_t) (MAILSTREAM *stream,char *mailbox,ACLLIST *acl); typedef void (*listrights_t) (MAILSTREAM *stream,char *mailbox,char *id, @@ -1605,6 +1612,8 @@ long mm_diskerror (MAILSTREAM *stream,long errcode,long serious); void mm_fatal (char *string); void *mm_cache (MAILSTREAM *stream,unsigned long msgno,long op); +void delete_password (NETMBX *mb, char *user); + extern STRINGDRIVER mail_string; void mail_versioncheck (char *version); void mail_link (DRIVER *driver); diff --git a/imap/src/c-client/nntp.c b/imap/src/c-client/nntp.c index fe90edba..8fbcb9b7 100644 --- a/imap/src/c-client/nntp.c +++ b/imap/src/c-client/nntp.c @@ -2031,6 +2031,7 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) sprintf (tmp,"Retrying using %s authentication after %.80s", at->name,lsterr); mm_log (tmp,NIL); + delete_password(mb, mb ? mb->user : NULL); fs_give ((void **) &lsterr); } trial = 0; /* initial trial count */ @@ -2039,6 +2040,7 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) if (lsterr) { sprintf (tmp,"Retrying %s authentication after %.80s",at->name,lsterr); mm_log (tmp,WARN); + delete_password(mb, mb ? mb->user : NULL); fs_give ((void **) &lsterr); } stream->saslcancel = NIL; @@ -2064,6 +2066,7 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) sprintf (tmp,"Can not authenticate to NNTP server: %.80s",lsterr); mm_log (tmp,ERROR); } + delete_password(mb, mb ? mb->user : NULL); fs_give ((void **) &lsterr); } else if (mb->secflag) /* no SASL, can't do /secure */ @@ -2092,6 +2095,8 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) stream->sensitive = T; /* hide this command */ if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED) ret = LONGT; /* password OK */ + else + delete_password(mb, mb ? mb->user : NULL); stream->sensitive = NIL; /* unhide */ if (ret) break; /* OK if successful */ default: /* authentication failed */ diff --git a/imap/src/c-client/pop3.c b/imap/src/c-client/pop3.c index 58a9ceb6..1b411546 100644 --- a/imap/src/c-client/pop3.c +++ b/imap/src/c-client/pop3.c @@ -615,6 +615,7 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) sprintf (pwd,"Retrying using %.80s authentication after %.80s", at->name,t); mm_log (pwd,NIL); + delete_password(mb, usr); fs_give ((void **) &t); } trial = 0; /* initial trial count */ @@ -622,6 +623,7 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) if (t) { sprintf (pwd,"Retrying %s authentication after %.80s",at->name,t); mm_log (pwd,WARN); + delete_password(mb, usr); fs_give ((void **) &t); } LOCAL->saslcancel = NIL; @@ -667,6 +669,7 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) LOCAL->sensitive=NIL; /* unhide */ } if (!ret) { /* failure */ + delete_password(mb, usr); mm_log (LOCAL->reply,WARN); if (trial == pop3_maxlogintrials) mm_log ("Too many login failures",ERROR); diff --git a/imap/src/osdep/unix/Makefile b/imap/src/osdep/unix/Makefile index 78913acc..ae7b5b9e 100644 --- a/imap/src/osdep/unix/Makefile +++ b/imap/src/osdep/unix/Makefile @@ -144,7 +144,7 @@ DEFAULTAUTHENTICATORS=ext md5 pla log # However, mh needs to be before any sysinbox formats (such as mmdf or unix) # since otherwise INBOX won't work correctly when mh_allow_inbox is set. # -DEFAULTDRIVERS=imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile +DEFAULTDRIVERS=maildir courier imap nntp pop3 mix mx mbx tenex mtx mh mmdf unix news phile CHUNKSIZE=65536 # Normally no need to change any of these @@ -153,7 +153,7 @@ ARCHIVE=c-client.a BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \ dummy.o pseudo.o netmsg.o flstring.o fdstring.o \ rfc822.o nntp.o smtp.o imap4r1.o pop3.o \ - unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o + unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o maildir.o CFLAGS=-g CAT=cat @@ -290,7 +290,7 @@ cvx: # Convex cyg: # Cygwin - note that most local file drivers don't work!! $(BUILD) `$(CAT) SPECIALS` OS=$@ \ - DEFAULTDRIVERS="imap nntp pop3 mbx unix phile" \ + DEFAULTDRIVERS="imap nntp pop3 mbx unix maildir phile" \ SIGTYPE=psx CHECKPW=cyg LOGINPW=cyg CRXTYPE=std \ SPOOLDIR=/var \ ACTIVEFILE=/usr/local/news/lib/active \ @@ -900,7 +900,7 @@ tenex.o: mail.h misc.h osdep.h dummy.h unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c utf8aux.o: mail.h misc.h osdep.h utf8.h - +maildir.o: mail.h misc.h osdep.h maildir.h dummy.h # OS-dependent diff --git a/imap/src/osdep/unix/dummy.c b/imap/src/osdep/unix/dummy.c index b003a0ba..92a2c463 100644 --- a/imap/src/osdep/unix/dummy.c +++ b/imap/src/osdep/unix/dummy.c @@ -106,13 +106,19 @@ MAILSTREAM dummyproto = {&dummydriver}; * Accepts: mailbox name * Returns: our driver if name is valid, NIL otherwise */ - +char * maildir_remove_root(char *); DRIVER *dummy_valid (char *name) { - char *s,tmp[MAILTMPLEN]; + char *s,tmp[MAILTMPLEN], *rname; struct stat sbuf; + + if(strlen(name) > MAILTMPLEN) + name[MAILTMPLEN] = '\0'; + + strcpy(tmp, name); + rname = maildir_remove_root(tmp); /* must be valid local mailbox */ - if (name && *name && (*name != '{') && (s = mailboxfile (tmp,name))) { + if (rname && *rname && (*rname != '{') && (s = mailboxfile (tmp,rname))) { /* indeterminate clearbox INBOX */ if (!*s) return &dummydriver; else if (!stat (s,&sbuf)) switch (sbuf.st_mode & S_IFMT) { @@ -121,8 +127,9 @@ DRIVER *dummy_valid (char *name) return &dummydriver; } /* blackbox INBOX does not exist yet */ - else if (!compare_cstring (name,"INBOX")) return &dummydriver; + else if (!compare_cstring (rname,"INBOX")) return &dummydriver; } + if(rname) fs_give((void **)&rname); return NIL; } @@ -454,6 +461,8 @@ long dummy_create (MAILSTREAM *stream,char *mailbox) { char *s,tmp[MAILTMPLEN]; long ret = NIL; + if(!strncmp(mailbox,"#md/",4) || !strncmp(mailbox,"#mc/", 4)) + return maildir_create(stream, mailbox); /* validate name */ if (!(compare_cstring (mailbox,"INBOX") && (s = dummy_file (tmp,mailbox)))) { sprintf (tmp,"Can't create %.80s: invalid name",mailbox); @@ -519,6 +528,14 @@ long dummy_delete (MAILSTREAM *stream,char *mailbox) { struct stat sbuf; char *s,tmp[MAILTMPLEN]; + if (!strncmp(mailbox,"#md/",4) || !strncmp(mailbox,"#mc/", 4) + || is_valid_maildir(&mailbox)){ + char tmp[MAILTMPLEN] = {'\0'}; + strcpy(tmp, mailbox); + if(tmp[strlen(tmp) - 1] != '/') + tmp[strlen(tmp)] = '/'; + return maildir_delete(stream, tmp); + } if (!(s = dummy_file (tmp,mailbox))) { sprintf (tmp,"Can't delete - invalid name: %.80s",s); MM_LOG (tmp,ERROR); @@ -544,12 +561,23 @@ long dummy_delete (MAILSTREAM *stream,char *mailbox) long dummy_rename (MAILSTREAM *stream,char *old,char *newname) { struct stat sbuf; - char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN]; + char c,*s,tmp[MAILTMPLEN],mbx[MAILTMPLEN],oldname[MAILTMPLEN], *rold, *rnewname; + + if(strlen(old) > MAILTMPLEN) + old[MAILTMPLEN] = '\0'; + + if(strlen(newname) > MAILTMPLEN) + newname[MAILTMPLEN] = '\0'; + + strcpy(tmp, old); + rold = maildir_remove_root(tmp); + strcpy(tmp, newname); + rnewname = maildir_remove_root(tmp); /* no trailing / allowed */ - if (!dummy_file (oldname,old) || !(s = dummy_file (mbx,newname)) || + if (!dummy_file (oldname,rold) || !(s = dummy_file (mbx,rnewname)) || stat (oldname,&sbuf) || ((s = strrchr (s,'/')) && !s[1] && ((sbuf.st_mode & S_IFMT) != S_IFDIR))) { - sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",old,newname); + sprintf (mbx,"Can't rename %.80s to %.80s: invalid name",rold,rnewname); MM_LOG (mbx,ERROR); return NIL; } @@ -565,14 +593,16 @@ long dummy_rename (MAILSTREAM *stream,char *old,char *newname) } } /* rename of non-ex INBOX creates dest */ - if (!compare_cstring (old,"INBOX") && stat (oldname,&sbuf)) + if (!compare_cstring (rold,"INBOX") && stat (oldname,&sbuf)) return dummy_create (NIL,mbx); if (rename (oldname,mbx)) { - sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",old,newname, + sprintf (tmp,"Can't rename mailbox %.80s to %.80s: %.80s",rold,rnewname, strerror (errno)); MM_LOG (tmp,ERROR); return NIL; } + if(rold) fs_give((void **)&rold); + if(rnewname) fs_give((void **)&rnewname); return T; /* return success */ } diff --git a/imap/src/osdep/unix/maildir.c b/imap/src/osdep/unix/maildir.c new file mode 100644 index 00000000..b15adc87 --- /dev/null +++ b/imap/src/osdep/unix/maildir.c @@ -0,0 +1,2638 @@ +/* + * Maildir driver for Alpine 2.11 + * + * Written by Eduardo Chappa <chappa@gmx.com> + * Last Update: May 29, 2011. + * + */ + +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +extern int errno; /* just in case */ +#include "mail.h" +#include <pwd.h> +#include <sys/stat.h> +#include <sys/time.h> +#include "osdep.h" +#include "rfc822.h" +#include "fdstring.h" +#include "misc.h" +#include "dummy.h" +#include "maildir.h" + +/* Driver dispatch used by MAIL */ +DRIVER maildirdriver = { + "md", /* driver name, yes it's md, not maildir */ + DR_MAIL|DR_LOCAL|DR_NAMESPACE|DR_DIRFMT, /* driver flags */ + (DRIVER *) NIL, /* next driver */ + maildir_valid, /* mailbox is valid for us */ + maildir_parameters, /* manipulate parameters */ + NIL, /* scan mailboxes */ + maildir_list, /* find mailboxes */ + maildir_lsub, /* find subscribed mailboxes */ + maildir_sub, /* subscribe to mailbox */ + maildir_unsub, /* unsubscribe from mailbox */ + maildir_create, /* create mailbox */ + maildir_delete, /* delete mailbox */ + maildir_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + maildir_open, /* open mailbox */ + maildir_close, /* close mailbox */ + maildir_fast, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message structure */ + maildir_header, /* fetch message header */ + maildir_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + maildir_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + maildir_ping, /* ping mailbox to see if still alive */ + maildir_check, /* check for new messages */ + maildir_expunge, /* expunge deleted messages */ + maildir_copy, /* copy messages to another mailbox */ + maildir_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + + +DRIVER courierdriver = { + "mc", /* Why a separate driver? So that createproto will work */ + DR_MAIL|DR_LOCAL|DR_NAMESPACE|DR_DIRFMT, /* driver flags */ + (DRIVER *) NIL, /* next driver */ + maildir_valid, /* mailbox is valid for us */ + maildir_parameters, /* manipulate parameters */ + NIL, /* scan mailboxes */ + courier_list, /* find mailboxes */ + maildir_lsub, /* find subscribed mailboxes */ + maildir_sub, /* subscribe to mailbox */ + maildir_unsub, /* unsubscribe from mailbox */ + maildir_create, /* create mailbox */ + maildir_delete, /* delete mailbox */ + maildir_rename, /* rename mailbox */ + mail_status_default, /* status of mailbox */ + maildir_open, /* open mailbox */ + maildir_close, /* close mailbox */ + maildir_fast, /* fetch message "fast" attributes */ + NIL, /* fetch message flags */ + NIL, /* fetch overview */ + NIL, /* fetch message structure */ + maildir_header, /* fetch message header */ + maildir_text, /* fetch message body */ + NIL, /* fetch partial message text */ + NIL, /* unique identifier */ + NIL, /* message number */ + NIL, /* modify flags */ + maildir_flagmsg, /* per-message modify flags */ + NIL, /* search for message based on criteria */ + NIL, /* sort messages */ + NIL, /* thread messages */ + maildir_ping, /* ping mailbox to see if still alive */ + maildir_check, /* check for new messages */ + maildir_expunge, /* expunge deleted messages */ + maildir_copy, /* copy messages to another mailbox */ + maildir_append, /* append string message to mailbox */ + NIL /* garbage collect stream */ +}; + +MAILSTREAM maildirproto = {&maildirdriver}; /* prototype stream */ +MAILSTREAM courierproto = {&courierdriver}; /* prototype stream */ + +long maildir_dirfmttest (char *name) +{ + int i; + for (i = 0; mdstruct[i] && strcmp(name, mdstruct[i]); i++); + return (i < EndDir) || !strcmp(name, MDDIR) + || !strncmp(name, MDUIDLAST, strlen(MDUIDLAST)) + || !strncmp(name, MDUIDTEMP, strlen(MDUIDTEMP)) ? LONGT : NIL; +} + +void +md_domain_name(void) +{ + int i, j; + + strcpy(mdlocaldomain, mylocalhost ()); + for (i = 0; mdlocaldomain[i] != '\0' ;) + if(mdlocaldomain[i] == '/' || mdlocaldomain[i] == ':'){ + for(j = strlen(mdlocaldomain); j >= i; j--) + mdlocaldomain[j+4] = mdlocaldomain[j]; + mdlocaldomain[i++] = '\\'; + mdlocaldomain[i++] = '0'; + if(mdlocaldomain[i] == '/'){ + mdlocaldomain[i++] = '5'; + mdlocaldomain[i++] = '7'; + } else { + mdlocaldomain[i++] = '7'; + mdlocaldomain[i++] = '2'; + } + } + else + i++; +} + +char * +myrootdir(char *name) +{ +return myhomedir(); +} + +char * +mdirpath(void) +{ + char *path = maildir_parameters(GET_MDINBOXPATH, NIL); + return path ? (path[0] ? path : ".") : "Maildir"; +} + +/* remove the "#md/" or "#mc/" part from a folder name + * memory freed by caller + */ +char * +maildir_remove_root (char *name) +{ + int courier = IS_COURIER(name), offset; + char realname[MAILTMPLEN]; + + offset = maildir_valid_name(name) ? (name[3] == '/' ? 4 : 3) : 0; + if(courier) + courier_realname(name+offset, realname); + else + strcpy(realname, name+offset); + return cpystr(realname); +} + + +/* Check validity of the name, we accept: + * a) #md/directory/folder + * b) #md/inbox + * A few considerations: We can only accept as valid + * a) names that start with #md/ and the directory exists or + * b) names that do not start with #md/ but are maildir directories (have + * the /cur, /tmp and /new structure) + */ +int maildir_valid_name (char *name) +{ + char tmpname[MAILTMPLEN] = {'\0'}; + + if (mdfpath) + fs_give((void **)&mdfpath); + if (name && (name[0] != '#')) + snprintf(tmpname, sizeof(tmpname), "%s%s",MDPREFIX(CCLIENT), name); + mdfpath = cpystr(tmpname[0] ? tmpname : name); + + return IS_CCLIENT(name) || IS_COURIER(name); +} + +/* Check if the directory whose path is given by name is a valid maildir + * directory (contains /cur, /tmp and /new) + */ +int maildir_valid_dir (char *name) +{ + int len; + DirNamesType i; + struct stat sbuf; + char tmp[MAILTMPLEN]; + + if(name[strlen(name) - 1] == '/') + name[strlen(name) - 1] = '\0'; + len = strlen(name); + for (i = Cur; i != EndDir; i++){ + MDFLD(tmp, name, i); + if (stat(tmp, &sbuf) < 0 || !S_ISDIR(sbuf.st_mode)) + break; + } + name[len] = '\0'; + return (i == EndDir) ? T : NIL; +} + +void courier_realname(char *name, char *realname) +{ + int i,j; + + if(!name) + return; + + for (i = 0, j = 0; i < MAILTMPLEN && j < strlen(name); j++, i++){ + realname[i] = name[j]; + if(name[j] == '/' && name[j+1] != '.' && name[j+1] != '%' + && name[j+1] != '*') + realname[++i] = '.'; + } + if(realname[i-1] == '.') + i--; + realname[i] = '\0'; +} + + +/* given a maildir folder, return its path. Memory freed by caller. Directory + * does not contain the trailing slash "/". On error NULL is returned. + */ +int maildir_file_path (char *name, char *tmp, size_t sizeoftmp) +{ + char *maildirpath = mdirpath(), *rname; + int courier = IS_COURIER(name); + + /* There are several ways in which the path can come, so we will handle + them here. First we deal with #mc/ or #md/ prefix by removing the + prefix, if any */ + + if(strlen(name) >= MAILTMPLEN) + name[MAILTMPLEN] = '\0'; + strcpy(tmp, name); + rname = maildir_remove_root(tmp); + tmp[0] = '\0'; /* just in case something fails */ + + if (strlen(myrootdir(rname)) + + max(strlen(rname), strlen(maildirpath)) > sizeoftmp){ + errno = ENAMETOOLONG; + snprintf(tmp, sizeoftmp, "Error opening \"%s\": %s", rname, strerror (errno)); + mm_log(tmp,ERROR); + if(rname) fs_give((void **)&rname); + return NIL; + } + + /* There are two ways in which the name can come here, either as a + full path or not. If it is not a full path it can come in two ways, + either as a file system path (Maildir/.Drafts) or as a maildir path + (INBOX.Drafts) + */ + + if(*rname == '/'){ /* full path */ + strncpy(tmp, rname, sizeoftmp); /* do nothing */ + tmp[sizeoftmp-1] = '\0'; + } + else + snprintf (tmp, sizeoftmp, "%s/%s%s%s", myrootdir (rname), + strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) + ? rname : maildirpath, + strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) + ? "" : (courier ? "/" : ""), + strncmp (ucase (strcpy (tmp, rname)), "INBOX", 5) + ? "" : (*(rname+5) == MDSEPARATOR(courier) ? rname+5 : "")); + if(rname) fs_give((void **)&rname); + return tmp[0] ? T : NIL; +} + +/* This function is given a full path for a mailbox and returns + * if it is a valid maildir transformed to canonical notation + */ +int +is_valid_maildir (char **name) +{ + if (!strncmp(*name, myrootdir (*name), strlen(myrootdir(*name)))){ + (*name) += strlen(myrootdir(*name)); + if (**name == '/') (*name)++; + } + return maildir_valid(*name) ? T : NIL; +} + +/* Check validity of mailbox. This routine does not send errors to log, other + * routines calling this one may do so, though + */ + +DRIVER *maildir_valid (char *name) +{ + char tmpname[MAILTMPLEN]; + + maildir_file_path(name, tmpname, sizeof(tmpname)); + + return maildir_valid_dir(tmpname) + ? (IS_COURIER(name) ? &courierdriver : &maildirdriver) : NIL; +} + +void maildir_fast (MAILSTREAM *stream,char *sequence,long flags) +{ + unsigned long i; + MESSAGECACHE *elt; + /* get sequence */ + if (stream && LOCAL && ((flags & FT_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence))) + for (i = 1L; i <= stream->nmsgs; i++) { + if ((elt = mail_elt (stream,i))->sequence && (elt->valid = T) && + !(elt->day && elt->rfc822_size)) { + ENVELOPE **env = NIL; + ENVELOPE *e = NIL; + if (!stream->scache) env = &elt->private.msg.env; + else if (stream->msgno == i) env = &stream->env; + else env = &e; + if (!*env || !elt->rfc822_size) { + STRING bs; + unsigned long hs; + char *ht = (*stream->dtb->header) (stream,i,&hs,NIL); + + if (!*env) rfc822_parse_msg (env,NIL,ht,hs,NIL,BADHOST, + stream->dtb->flags); + if (!elt->rfc822_size) { + (*stream->dtb->text) (stream,i,&bs,FT_PEEK); + elt->rfc822_size = hs + SIZE (&bs) - GETPOS (&bs); + } + } + + if (!elt->day && *env && (*env)->date) + mail_parse_date (elt,(*env)->date); + + if (!elt->day) elt->day = elt->month = 1; + mail_free_envelope (&e); + } + } +} + +int +maildir_eliminate_duplicate (char *name, struct direct ***flist, unsigned long *nfiles) +{ + int i, j, k, error = 0, scanr; + char new[MAILTMPLEN], old[MAILTMPLEN], tmp[MAILTMPLEN], *str; + struct direct **names = NIL; + + if((scanr = maildir_doscandir(name, &names, CCLIENT)) < 0) + return -1; + + if(nfiles) *nfiles = scanr; + for(i = 0, j = 1, k = 0; j < scanr; i++, j++){ + if(k) + names[i] = names[i+k]; + if(same_maildir_file(names[i]->d_name, names[j]->d_name)){ + int d, f, r, s; + maildir_getflag(names[i]->d_name, &d, &f, &r, &s, NIL); + snprintf(old, sizeof(old), "%s/%s", name, names[i]->d_name); + snprintf(new, sizeof(new), "%s/.%s", name, names[i]->d_name); + if(rename(old, new) < 0 && errno != EEXIST) + error++; + if(!error){ + for(; j < scanr + && same_maildir_file(names[i]->d_name, names[j]->d_name) + ; j++, k++){ + maildir_getflag(names[j]->d_name, (d ? NIL : &d), + (f ? NIL : &f), (r ? NIL : &r), (s ? NIL : &s), NIL); + snprintf(tmp, sizeof(tmp), "%s/%s", name, names[j]->d_name); + if(unlink(tmp) < 0){ /* Hmmm... a problem, let's see */ + struct stat sbuf; + if (stat(tmp, &sbuf) == 0 && (sbuf.st_mode & S_IFMT) == S_IFREG) + error++; + } + } + if((str = strrchr(names[i]->d_name,FLAGSEP)) != NULL) *str = '\0'; + snprintf (old, sizeof(old), "%s/%s%s%s%s%s%s", name, names[i]->d_name, MDSEP(2), + MDFLAG(Draft, d), MDFLAG(Flagged, f), MDFLAG(Replied, r), + MDFLAG(Seen, s)); + if(rename(new, old) < 0) + error++; + } + } + + } + if(k > 0) + fs_give((void **)&names); + else + *flist = names; + return error ? -1 : k; +} + +int +maildir_doscandir(char *name, struct direct ***flist, int flag) +{ +return scandir(name, flist, + flag == CCLIENT ? maildir_select : courier_dir_select, + flag == CCLIENT ? maildir_namesort : courier_dir_sort); +} + +/* + * return all files in a given directory. This is a separate call + * so that if there are warnings during compilation this only appears once. + */ +unsigned long +maildir_scandir (char *name, struct direct ***flist, + unsigned long *nfiles, int *scand, int flag) +{ + struct stat sbuf; + int rv = -2; /* impossible value */ + + if (scand) + *scand = -1; /* assume error for safety */ + *nfiles = 0; + if((stat(name,&sbuf) < 0) + || (flag == CCLIENT + && ((rv = maildir_eliminate_duplicate(name, flist, nfiles)) < 0))) + return 0L; + + if (scand && (rv > 0 || rv == -2)) + *nfiles = maildir_doscandir(name, flist, flag); + + if(scand) *scand = *nfiles; + + return (unsigned long) sbuf.st_ctime; +} + +/* Does a message with given name exists (or was it removed)? + * Returns: 1 - yes, such message exist, + * 0 - No, that message does not exist anymore + * + * Parameters: stream, name of mailbox, new name if his message does not + * exist. + */ + +int maildir_message_exists(MAILSTREAM *stream, char *name, char *newfile) +{ + char tmp[MAILTMPLEN]; + int gotit = NIL; + DIR *dir; + struct direct *d; + struct stat sbuf; + + /* First check directly if it exists, if not there, look for it */ + snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->path[Cur], name); + if ((stat(tmp, &sbuf) == 0) && ((sbuf.st_mode & S_IFMT) == S_IFREG)) + return T; + + if (!(dir = opendir (LOCAL->path[Cur]))) + return NIL; + + while ((d = readdir(dir)) && gotit == NIL){ + if (d->d_name[0] == '.') + continue; + if (same_maildir_file(d->d_name, name)){ + gotit = T; + strcpy(newfile, d->d_name); + } + } + closedir(dir); + return gotit; +} + +/* Maildir open */ + +MAILSTREAM *maildir_open (MAILSTREAM *stream) +{ + char tmp[MAILTMPLEN]; + struct stat sbuf; + + if (!stream) return &maildirproto; + if (stream->local) fatal ("maildir recycle stream"); + md_domain_name(); /* get domain name for maildir files in mdlocaldomain */ + if(mypid == (pid_t) 0) + mypid = getpid(); + if (!stream->rdonly){ + stream->perm_seen = stream->perm_deleted = stream->perm_flagged = + stream->perm_answered = stream->perm_draft = T; + } + stream->local = (MAILDIRLOCAL *) fs_get (sizeof (MAILDIRLOCAL)); + memset(LOCAL, 0, sizeof(MAILDIRLOCAL)); + LOCAL->fd = -1; + + LOCAL->courier = IS_COURIER(stream->mailbox); + strcpy(tmp, stream->mailbox); + if (maildir_file_path (stream->mailbox, tmp, sizeof(tmp))) + LOCAL->dir = cpystr (tmp); + LOCAL->candouid = maildir_can_assign_uid(stream); + maildir_read_uid(stream, &stream->uid_last, &stream->uid_validity); + if (LOCAL->dir){ + LOCAL->path = (char **) fs_get(EndDir*sizeof(char *)); + MDFLD(tmp, LOCAL->dir, Cur); LOCAL->path[Cur] = cpystr (tmp); + MDFLD(tmp, LOCAL->dir, New); LOCAL->path[New] = cpystr (tmp); + MDFLD(tmp, LOCAL->dir, Tmp); LOCAL->path[Tmp] = cpystr (tmp); + if (stat (LOCAL->path[Cur],&sbuf) < 0) { + snprintf (tmp, sizeof(tmp), "Can't open folder %s: %s", + stream->mailbox,strerror (errno)); + mm_log (tmp,ERROR); + maildir_close(stream, 0); + return NIL; + } + } + + if(maildir_file_path (stream->mailbox, tmp, sizeof(tmp))){ + fs_give ((void **) &stream->mailbox); + stream->mailbox = cpystr(tmp); + } + + LOCAL->buf = (char *) fs_get (CHUNKSIZE); + LOCAL->buflen = CHUNKSIZE - 1; + stream->sequence++; + stream->nmsgs = stream->recent = 0L; + + maildir_parse_folder(stream, 1); + + return stream; +} + +/* Maildir initial parsing of the folder */ +void +maildir_parse_folder (MAILSTREAM *stream, int full) +{ + char tmp[MAILTMPLEN]; + struct direct **namescur = NIL, **namesnew = NIL; + unsigned long i, nfilescur = 0L, nfilesnew = 0L, oldpos, newpos, total; + int scan_err, rescan, loop = 0; + + if (!stream) /* what??? */ + return; + + MM_CRITICAL(stream); + + maildir_scandir (LOCAL->path[New], &namesnew, &nfilesnew, &scan_err, CCLIENT); + if (scan_err < 0) + maildir_abort(stream); + + /* Scan old messages first, escoba! */ + if(stream->rdonly || + (LOCAL && ((maildir_initial_check(stream, Cur) == 0) + || nfilesnew > 0L))){ + LOCAL->scantime = maildir_scandir (LOCAL->path[Cur], &namescur, &nfilescur, + &scan_err, CCLIENT); + if (scan_err < 0){ + if(namesnew){ + for(i = 0L; i < nfilesnew; i++) + fs_give((void **)&namesnew[i]); + fs_give((void **) &namesnew); + } + maildir_abort(stream); + } + } + if(LOCAL && (maildir_initial_check(stream, New) == 0) + && (nfilescur > 0L)){ + while(LOCAL && loop < 10){ + if(nfilesnew == 0L) + maildir_scandir (LOCAL->path[New], &namesnew, &nfilesnew, &scan_err, CCLIENT); + if (scan_err < 0){ + if(namesnew){ + for(i = 0L; i < nfilesnew; i++) + fs_give((void **)&namesnew[i]); + fs_give((void **) &namesnew); + } + maildir_abort(stream); + break; + } + for(i = 0L, rescan = 0, newpos = oldpos = 0L; + newpos < nfilescur && i < nfilesnew; i++){ + if(maildir_message_in_list(namesnew[i]->d_name, namescur, oldpos, + nfilescur - 1L, &newpos)){ + oldpos = newpos; + snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->path[New], namesnew[i]->d_name); + if(unlink(tmp) < 0) + scan_err = -1; + rescan++; + } + else + newpos = oldpos; + } + if(scan_err < 0) + maildir_abort(stream); + if(rescan == 0) + break; + else{ /* restart */ + if(namesnew){ + for(i = 0L; i < nfilesnew; i++) + fs_give((void **)&namesnew[i]); + fs_give((void **) &namesnew); + } + nfilesnew = 0L; + loop++; + } + } + } + if(loop == 10) + maildir_abort(stream); + if(LOCAL){ + if(stream->rdonly) + stream->recent = 0L; + total = namescur || stream->rdonly + ? maildir_parse_dir(stream, 0L, Cur, namescur, + nfilescur, full) : stream->nmsgs; + stream->nmsgs = maildir_parse_dir(stream, total, New, namesnew, + nfilesnew, full); + } + if(namesnew){ + for(i = 0L; i < nfilesnew; i++) + fs_give((void **)&namesnew[i]); + fs_give((void **) &namesnew); + } + if(namescur){ + for(i = 0L; i < nfilescur; i++) + fs_give((void **)&namescur[i]); + fs_give((void **) &namescur); + } + MM_NOCRITICAL(stream); +} + +int +maildir_initial_check (MAILSTREAM *stream, DirNamesType dirtype) +{ + char *tmp; + struct stat sbuf; + + if (access (LOCAL->path[dirtype], R_OK|W_OK|X_OK) != 0){ + maildir_abort(stream); + return -1; + } + + if (dirtype != New && + (stat(LOCAL->path[Cur], &sbuf) < 0 || sbuf.st_ctime == LOCAL->scantime)) + return -1; + return 0; +} + + +/* Return the number of messages in the directory, while filling the + * elt structure. + */ + +unsigned long +maildir_parse_dir(MAILSTREAM *stream, unsigned long nmsgs, + DirNamesType dirtype, struct direct **names, + unsigned long nfiles, int full) +{ + char tmp[MAILTMPLEN], file[MAILTMPLEN], newfile[MAILTMPLEN], *mdstr; + struct stat sbuf; + unsigned long i, new = 0L, l, uid_last; + unsigned long recent = stream ? stream->recent : 0L; + int d = 0, f = 0, r = 0, s = 0, t = 0; + int we_compute, in_list; + int silent = stream ? stream->silent : NIL; + MESSAGECACHE *elt; + + if (dirtype == Cur && !stream->rdonly) + for (i = 1L; i <= stream->nmsgs;){ + elt = mail_elt(stream, i); + in_list = elt && elt->private.spare.ptr && nfiles > 0L + ? (MDPOS(elt) < nfiles + ? same_maildir_file(MDFILE(elt), names[MDPOS(elt)]->d_name) + : NIL) + || maildir_message_in_list(MDFILE(elt), names, 0L, + nfiles - 1L, &MDPOS(elt)) + : NIL; + if (!in_list){ + if (elt->private.spare.ptr) + maildir_free_file ((void **) &elt->private.spare.ptr); + + if (elt->recent) --recent; + mail_expunged(stream,i); + } + else i++; + } + + stream->silent = T; + uid_last = 0L; + for (we_compute = 0, i = l = 1L; l <= nfiles; l++){ + unsigned long pos, uid; + if (dirtype == New && !stream->rdonly){ /* move new messages to cur */ + pos = l - 1L; + snprintf (file, sizeof(file), "%s/%s", LOCAL->path[New], names[pos]->d_name); + if(lstat(file,&sbuf) == 0) + switch(sbuf.st_mode & S_IFMT){ + case S_IFREG: + strcpy(tmp, names[pos]->d_name); + if((mdstr = strstr(tmp,MDSEP(3))) + || (mdstr = strstr(tmp,MDSEP(2)))) + *(mdstr+1) = '2'; + else + strcat(tmp, MDSEP(2)); + snprintf(newfile, sizeof(newfile), "%s/%s", LOCAL->path[Cur], tmp); + if(rename (file, newfile) != 0){ + mm_log("Unable to read new mail!", WARN); + continue; + } + unlink (file); + new++; + break; + case S_IFLNK: /* clean up, clean up, everybody, everywhere */ + if(unlink(file) < 0){ + if(LOCAL->link == NIL){ + mm_log("Unable to remove symbolic link", WARN); + LOCAL->link = T; + } + } + continue; + break; + default: + if(LOCAL && LOCAL->link == NIL){ + mm_log("Unrecognized file or link in folder", WARN); + LOCAL->link = T; + } + continue; + break; + } + } + mail_exists(stream, i + nmsgs); + elt = mail_elt(stream, i + nmsgs); + pos = (elt && elt->private.spare.ptr) ? MDPOS(elt) : l - 1L; + if (dirtype == New) elt->recent = T; + maildir_getflag(names[pos]->d_name, &d, &f, &r ,&s, &t); + if (elt->private.spare.ptr) + maildir_free_file_only ((void **)&elt->private.spare.ptr); + else{ + maildir_get_file((MAILDIRFILE **)&elt->private.spare.ptr); + we_compute++; + } + MDFILE(elt) = cpystr(names[pos]->d_name); + MDPOS(elt) = pos; + MDLOC(elt) = dirtype; + if (dirtype == Cur){ /* deal with UIDs */ + if(elt->private.uid == 0L) + elt->private.uid = maildir_get_uid(MDFILE(elt)); + if(elt->private.uid <= uid_last){ + uid = (we_compute ? uid_last : stream->uid_last) + 1L; + if(LOCAL->candouid) + maildir_assign_uid(stream, i + nmsgs, uid); + else + elt->private.uid = uid; + } + else + uid = elt->private.uid; + uid_last = uid; + if(uid_last > stream->uid_last) + stream->uid_last = uid_last; + } + if(dirtype == New && !stream->rdonly){ + maildir_free_file_only((void **)&elt->private.spare.ptr); + MDFILE(elt) = cpystr(tmp); + MDSIZE(elt) = sbuf.st_size; + MDMTIME(elt) = sbuf.st_mtime; + MDLOC(elt) = Cur; + } + if (elt->draft != d || elt->flagged != f || + elt->answered != r || elt->seen != s || elt->deleted != t){ + elt->draft = d; elt->flagged = f; elt->answered = r; + elt->seen = s; elt->deleted = t; + if (!we_compute && !stream->rdonly) + MM_FLAGS(stream, i+nmsgs); + } + maildir_get_date(stream, i+nmsgs); + elt->valid = T; + i++; + } + stream->silent = silent; + if(LOCAL->candouid && dirtype == Cur) + maildir_read_uid(stream, NULL, &stream->uid_validity); + if (dirtype == New && stream->rdonly) + new = nfiles; + mail_exists(stream, nmsgs + ((dirtype == New) ? new : nfiles)); + mail_recent(stream, recent + ((dirtype == New) ? new : 0L)); + + return (nmsgs + (dirtype == New ? new : nfiles)); +} + +long maildir_ping (MAILSTREAM *stream) +{ + maildir_parse_folder(stream, 0); + if(stream && LOCAL){ + if(LOCAL->candouid < 0) + LOCAL->candouid++; + else if(LOCAL->candouid) + maildir_uid_renew_tempfile(stream); + else /* try again to get uids */ + LOCAL->candouid = maildir_can_assign_uid(stream); + } + return stream && LOCAL ? LONGT : NIL; +} + +int maildir_select (const struct direct *name) +{ + return (name->d_name[0] != '.'); +} + +/* + * Unfortunately, there is no way to sort by arrival in this driver, this + * means that opening a folder in this driver using the scandir function + * will always make this driver slower than any driver that has a natural + * way of sorting by arrival (like a flat file format, "mbox", "mbx", etc). + */ +int maildir_namesort (const struct direct **d1, const struct direct **d2) +{ + const struct direct *e1 = *(const struct direct **) d1; + const struct direct *e2 = *(const struct direct **) d2; + + return comp_maildir_file((char *) e1->d_name, (char *) e2->d_name); +} + +/* Maildir close */ + +void maildir_close (MAILSTREAM *stream, long options) +{ + MESSAGECACHE *elt; + unsigned long i; + int silent = stream ? stream->silent : 0; + mailcache_t mc = (mailcache_t) mail_parameters (NIL,GET_CACHE,NIL); + + if (!stream) return; + + for (i = 1L; i <= stream->nmsgs; i++) + if((elt = (MESSAGECACHE *) (*mc)(stream,i,CH_ELT)) && elt->private.spare.ptr) + maildir_free_file ((void **) &elt->private.spare.ptr); + stream->silent = T; + if (options & CL_EXPUNGE) maildir_expunge (stream, NIL, NIL); + maildir_abort(stream); + if (mdfpath) fs_give((void **)&mdfpath); + if (mypid) mypid = (pid_t) 0; + stream->silent = silent; +} + +void maildir_check (MAILSTREAM *stream) +{ + if (maildir_ping (stream)) mm_log ("Check completed",(long) NIL); +} + +long maildir_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs, long flags) +{ + char tmp[MAILTMPLEN]; + unsigned long i; + MESSAGECACHE *elt; + char *s; + /* UID call "impossible" */ + if (flags & FT_UID || !LOCAL) return NIL; + elt = mail_elt (stream, msgno); + + if (!(flags & FT_PEEK) && !elt->seen){ + elt->seen = T; + maildir_flagmsg (stream, elt); + MM_FLAGS(stream, elt->msgno); + } + + MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); + if (LOCAL->fd < 0) /* if file closed ? */ + LOCAL->fd = open(tmp,O_RDONLY,NIL); + + if (LOCAL->fd < 0 && (errno == EACCES || errno == ENOENT)){ + INIT (bs, mail_string, "", 0); + elt->rfc822_size = 0L; + return NIL; + } + + s = maildir_text_work(stream, elt, &i, flags); + INIT (bs, mail_string, s, i); + return LONGT; +} + +char *maildir_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, + unsigned long *length,long flags) +{ + FDDATA d; + STRING bs; + char *s,tmp[CHUNK]; + unsigned long msgno = elt->msgno; + static int try = 0; + + if (length) + *length = 0L; + LOCAL->buf[0] = '\0'; + + MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); + if (LOCAL->fd < 0) /* if file closed ? */ + LOCAL->fd = open(tmp,O_RDONLY,NIL); + + if (LOCAL->fd < 0){ /* flag change? */ + if (try < 5){ + try++; + if (maildir_update_elt_maildirp(stream, msgno) > 0) + try = 0; + return maildir_text_work(stream, mail_elt(stream, msgno),length, flags); + } + try = 0; + return NULL; + } + + lseek (LOCAL->fd, elt->private.msg.text.offset,L_SET); + + if (flags & FT_INTERNAL) { /* initial data OK? */ + if (elt->private.msg.text.text.size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = + elt->private.msg.text.text.size) + 1); + } + read (LOCAL->fd,LOCAL->buf,elt->private.msg.text.text.size); + LOCAL->buf[*length = elt->private.msg.text.text.size] = '\0'; + } + else { + if (elt->rfc822_size > LOCAL->buflen) { + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = elt->rfc822_size) + 1); + } + d.fd = LOCAL->fd; /* yes, set up file descriptor */ + d.pos = elt->private.msg.text.offset; + d.chunk = tmp; /* initial buffer chunk */ + d.chunksize = CHUNK; + INIT (&bs,fd_string,&d,elt->private.msg.text.text.size); + for (s = LOCAL->buf; SIZE (&bs);) switch (CHR (&bs)) { + case '\r': /* carriage return seen */ + *s++ = SNX (&bs); /* copy it and any succeeding LF */ + if (SIZE (&bs) && (CHR (&bs) == '\n')) *s++ = SNX (&bs); + break; + case '\n': + *s++ = '\r'; /* insert a CR */ + default: + *s++ = SNX (&bs); /* copy characters */ + } + *s = '\0'; /* tie off buffer */ + *length = s - (char *) LOCAL->buf; /* calculate length */ + } + close(LOCAL->fd); LOCAL->fd = -1; + return LOCAL->buf; +} + +/* maildir parse, fill the elt structure... well not all of it... */ +unsigned long maildir_parse_message(MAILSTREAM *stream, unsigned long msgno, + DirNamesType dirtype) +{ + char *b, *s, *t, c; + char tmp[MAILTMPLEN]; + struct stat sbuf; + unsigned long i, len; + int d, f, r, se, dt; + MESSAGECACHE *elt; + + elt = mail_elt (stream,msgno); + MSGPATH(tmp, LOCAL->dir, MDFILE(elt), dirtype); + if(stat(tmp, &sbuf) == 0) + MDSIZE(elt) = sbuf.st_size; + + maildir_get_date(stream, msgno); + maildir_getflag(MDFILE(elt), &d, &f, &r ,&se, &dt); + elt->draft = d; elt->flagged = f; elt->answered = r; elt->seen = se; + elt->deleted = dt; elt->valid = T; + if (LOCAL->fd < 0) /* if file closed ? */ + LOCAL->fd = open(tmp,O_RDONLY,NIL); + + if (LOCAL->fd >= 0){ + s = (char *) fs_get (MDSIZE(elt) + 1); + read (LOCAL->fd,s,MDSIZE(elt)); + s[MDSIZE(elt)] = '\0'; + t = s + strlen(s); /* make t point to the end of s */ + for (i = 0L, b = s; b < t && !(i && (*b == '\n')); i = (*b++ == '\n')); + len = (*b ? ++b : b) - s; + elt->private.msg.header.text.size = + elt->private.msg.text.offset = len; + elt->private.msg.text.text.size = MDSIZE(elt) - len; + for (i = 0L, b = s, c = *b; b && + ((c < '\016' && ((c == '\012' && ++i) + ||(c == '\015' && *(b+1) == '\012' && ++b && (i +=2)))) + || b < t); i++, c= *++b); + elt->rfc822_size = i; + fs_give ((void **) &s); + close(LOCAL->fd); LOCAL->fd = -1; + } + return elt->rfc822_size; +} + +int +maildir_update_elt_maildirp(MAILSTREAM *stream, unsigned long msgno) +{ + struct direct **names = NIL; + unsigned long i, nfiles, pos; + int d = 0, f = 0 , r = 0, s = 0, t = 0, in_list, scan_err; + MESSAGECACHE *elt; + + maildir_scandir (LOCAL->path[Cur], &names, &nfiles, &scan_err, CCLIENT); + + elt = mail_elt (stream,msgno); + + in_list = nfiles > 0L + ? maildir_message_in_list(MDFILE(elt), names, 0L, nfiles - 1L, &pos) + : NIL; + + if (in_list && pos >= 0L && pos < nfiles + && !strcmp(MDFILE(elt), names[pos]->d_name)){ + in_list = NIL; + maildir_abort(stream); + } + + if (in_list && pos >= 0L && pos < nfiles){ + maildir_free_file_only((void **)&elt->private.spare.ptr); + MDFILE(elt) = cpystr(names[pos]->d_name); + maildir_getflag(MDFILE(elt), &d, &f, &r ,&s, &t); + if (elt->draft != d || elt->flagged != f || + elt->answered != r || elt->seen != s || elt->deleted != t){ + elt->draft = d; elt->flagged = f; elt->answered = r; + elt->seen = s; elt->deleted = t; + MM_FLAGS(stream, msgno); + } + } + for (i = 0L; i < nfiles; i++) + fs_give((void **) &names[i]); + if (names) + fs_give((void **) &names); + return in_list ? 1 : -1; +} + +/* Maildir fetch message header */ + +char *maildir_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length, long flags) +{ + char tmp[MAILTMPLEN], *s; + MESSAGECACHE *elt; + static int try = 0; + + if (length) *length = 0; + if (flags & FT_UID || !LOCAL) return ""; /* UID call "impossible" */ + elt = mail_elt (stream,msgno); + if(elt->private.msg.header.text.size == 0) + maildir_parse_message(stream, msgno, MDLOC(elt)); + + MSGPATH(tmp, LOCAL->dir, MDFILE(elt), MDLOC(elt)); + if (LOCAL->fd < 0) + LOCAL->fd = open (tmp,O_RDONLY,NIL); + + if (LOCAL->fd < 0 && errno == EACCES){ + mm_log ("Message exists but can not be read. Envelope and body lost!",ERROR); + return NULL; + } + + if (LOCAL->fd < 0){ /* flag change? */ + if (try < 5){ + try++; + if (maildir_update_elt_maildirp(stream, msgno) > 0) + try = 0; + return maildir_header(stream, msgno, length, flags); + } + try = 0; + return NULL; + } + + if (flags & FT_INTERNAL){ + if(elt->private.msg.header.text.size > LOCAL->buflen){ + fs_give ((void **) &LOCAL->buf); + LOCAL->buf = (char *) fs_get ((LOCAL->buflen = + elt->private.msg.header.text.size) + 1); + } + read (LOCAL->fd, (void *)LOCAL->buf, elt->private.msg.header.text.size); + LOCAL->buf[*length = elt->private.msg.header.text.size] = '\0'; + } + else{ + s = (char *) fs_get(elt->private.msg.header.text.size+1); + read (LOCAL->fd, (void *)s, elt->private.msg.header.text.size); + s[elt->private.msg.header.text.size] = '\0'; + *length = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen,s, + elt->private.msg.header.text.size); + fs_give ((void **) &s); + } + elt->private.msg.text.offset = elt->private.msg.header.text.size; + elt->private.msg.text.text.size = MDSIZE(elt) - elt->private.msg.text.offset; + close(LOCAL->fd); LOCAL->fd = -1; + return LOCAL->buf; +} + +/* Maildir find list of subscribed mailboxes + * Accepts: mail stream + * pattern to search + */ + +void maildir_list (MAILSTREAM *stream,char *ref, char *pat) +{ + char *s,test[MAILTMPLEN],file[MAILTMPLEN]; + long i = 0L; + + if((!pat || !*pat) && maildir_canonicalize (test,ref,"*") + && maildir_valid_name(test)){ /* there is a #md/ leading here */ + for (i = 3L; test[i] && test[i] != '/'; i++); + if ((s = strchr (test+i+1,'/')) != NULL) *++s = '\0'; + else test[0] = '\0'; + mm_list (stream,'/',test, LATT_NOSELECT); + } + else if (maildir_canonicalize (test,ref,pat)) { + if (test[3] == '/') { /* looking down levels? */ + /* yes, found any wildcards? */ + if ((s = strpbrk (test,"%*")) != NULL){ + /* yes, copy name up to that point */ + strncpy (file,test+4,i = s - (test+4)); + file[i] = '\0'; /* tie off */ + } + else strcpy (file,test+4);/* use just that name then */ + /* find directory name */ + if ((s = strrchr (file, '/')) != NULL){ + *s = '\0'; /* found, tie off at that point */ + s = file; + } + /* do the work */ + if(IS_COURIER(test)) + courier_list_work (stream,s,test,0); + else + maildir_list_work (stream,s,test,0); + } + /* always an INBOX */ + if (!compare_cstring (test,"#MD/INBOX")) + mm_list (stream,NIL,"#MD/INBOX",LATT_NOINFERIORS); + if (!compare_cstring (test,"#MC/INBOX")) + mm_list (stream,NIL,"#MC/INBOX",LATT_NOINFERIORS); + } +} + +void courier_list (MAILSTREAM *stream,char *ref, char *pat) +{ +/* I am too lazy to do anything. Do you care to ask maildir list, please? + The real reason why this is a dummy function is because we do not want to + see the same folder listed twice. +*/ +} + +/* For those that want to hide things, we give them a chance to do so */ +void *maildir_parameters (long function, void *value) +{ + void *ret = NIL; + switch ((int) function) { + case SET_MDINBOXPATH: + if(strlen((char *) value ) > 49) + strcpy(myMdInboxDir, "Maildir"); + else + strcpy(myMdInboxDir, (char *) value); + case GET_MDINBOXPATH: + if (myMdInboxDir[0] == '\0') strcpy(myMdInboxDir,"Maildir"); + ret = (void *) myMdInboxDir; + break; + case SET_COURIERSTYLE: + CourierStyle = (long) value; + case GET_COURIERSTYLE: + ret = (void *) CourierStyle; + break; + case GET_DIRFMTTEST: + ret = (void *) maildir_dirfmttest; + break; + default: + break; + } + return ret; +} + +int maildir_create_folder(char *mailbox) +{ + char tmp[MAILTMPLEN], err[MAILTMPLEN]; + DirNamesType i; + + for (i = Cur; i != EndDir; i++){ + MDFLD(tmp, mailbox, i); + if (mkdir(tmp, 0700) && errno != EEXIST){ /* try to make new dir */ + snprintf (err, sizeof(err), "Can't create %s: %s", tmp, strerror(errno)); + mm_log (err,ERROR); + return NIL; + } + } + return T; +} + +int maildir_create_work(char *mailbox, int loop) +{ + char *s, c, err[MAILTMPLEN], tmp[MAILTMPLEN], tmp2[MAILTMPLEN], mbx[MAILTMPLEN]; + int fnlen, create_dir = 0, courier, mv; + struct stat sbuf; + long style = (long) maildir_parameters(GET_COURIERSTYLE, NIL); + + courier = IS_COURIER(mailbox); + strcpy(mbx, mailbox); + mv = maildir_valid(mbx) ? 1 : 0; + maildir_file_path(mailbox, tmp, sizeof(tmp)); + if (mailbox[strlen(mailbox) - 1] == MDSEPARATOR(courier)){ + create_dir++; + mailbox[strlen(mailbox) - 1] = '\0'; + } + + if(!loop && courier){ + if(mv){ + if(create_dir){ + if(style == CCLIENT) + strcpy (err,"Can not create directory: folder exists. Create subfolder"); + else + strcpy(err,"Folder and Directory already exist"); + } + else + strcpy (err, "Can't create mailbox: mailbox already exists"); + } + else{ + if(create_dir) + strcpy(err, "Can not create directory. Cread folder instead"); + else + err[0] = '\0'; + } + if(err[0]){ + mm_log (err,ERROR); + return NIL; + } + } + + fnlen = strlen(tmp); + if ((s = strrchr(mailbox,MDSEPARATOR(courier))) != NULL){ + c = *++s; + *s = '\0'; + if ((stat(tmp,&sbuf) || ((sbuf.st_mode & S_IFMT) != S_IFDIR)) && + !maildir_create_work (mailbox, ++loop)) + return NIL; + *s = c; + } + tmp[fnlen] = '\0'; + + if (mkdir(tmp,0700) && errno != EEXIST) + return NIL; + + if (create_dir) + mailbox[fnlen] = '/'; + + if (create_dir){ + if(style == CCLIENT){ + if(!courier){ + FILE *fp = NULL; + snprintf(tmp2, sizeof(tmp2), "%s%s", tmp, MDDIR); + if ((fp = fopen(tmp2,"w")) == NULL){ + snprintf (err, sizeof(err), "Problem creating %s: %s", tmp2, strerror(errno)); + mm_log (err,ERROR); + return NIL; + } + fclose(fp); + } + } + return T; + } + else + return maildir_create_folder(tmp); +} + +long maildir_create (MAILSTREAM *stream,char *mailbox) +{ + char tmp[MAILTMPLEN], err[MAILTMPLEN]; + int rv, create_dir; + + create_dir = mailbox ? + (mailbox[strlen(mailbox) - 1] == + MDSEPARATOR(IS_COURIER(mailbox))) : 0; + maildir_file_path(mailbox, tmp, sizeof(tmp)); + strcpy(tmp, mailbox); + rv = maildir_create_work(mailbox, 0); + strcpy(mailbox, tmp); + if (rv == 0){ + snprintf (err, sizeof(err), "Can't create %s %s", + (create_dir ? "directory" : "mailbox"), mailbox); + mm_log (err,ERROR); + } + return rv ? LONGT : NIL; +} + +#define MAXTRY 10000 +void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt) +{ + char oldfile[MAILTMPLEN],newfile[MAILTMPLEN],fn[MAILTMPLEN]; + char *s; + int ren, try = 0; + + if (elt->valid){ + for (try = 1; try > 0 && try < MAXTRY; try++){ + /* build the new filename */ + snprintf (oldfile, sizeof(oldfile), "%s/%s",LOCAL->path[Cur], MDFILE(elt)); + fn[0] = '\0'; + if ((ren = maildir_message_exists(stream, MDFILE(elt), fn)) == 0){ + errno = ENOENT; + try = MAXTRY; + } + if (*fn) /* new oldfile! */ + snprintf (oldfile,sizeof(oldfile),"%s/%s", LOCAL->path[Cur], fn); + if ((s = strrchr (MDFILE(elt), FLAGSEP))) *s = '\0'; + snprintf (fn, sizeof(fn), "%s%s%s%s%s%s%s", MDFILE(elt), MDSEP(2), + MDFLAG(Draft, elt->draft), MDFLAG(Flagged, elt->flagged), + MDFLAG(Replied, elt->answered), MDFLAG(Seen, elt->seen), + MDFLAG(Trashed, elt->deleted)); + snprintf (newfile, sizeof(newfile), "%s/%s",LOCAL->path[Cur],fn); + if (ren != 0 && rename (oldfile,newfile) >= 0) + try = -1; + } + + if (try > 0){ + snprintf(oldfile, sizeof(oldfile), "Unable to write flags to disk: %s", + (errno == ENOENT) ? "message is gone!" : strerror (errno)); + mm_log(oldfile,ERROR); + return; + } +#ifdef __CYGWIN__ + utime(LOCAL->path[Cur], NIL); /* make sure next scan will catch the change */ +#endif + maildir_free_file_only ((void **) &elt->private.spare.ptr); + MDFILE(elt) = cpystr (fn); + } +} + +long maildir_expunge (MAILSTREAM *stream, char *sequence, long options) +{ + long ret; + MESSAGECACHE *elt; + unsigned long i, n = 0L; + unsigned long recent = stream->recent; + char tmp[MAILTMPLEN]; + + mm_critical (stream); /* go critical */ + ret = sequence ? ((options & EX_UID) ? + mail_uid_sequence (stream,sequence) : + mail_sequence (stream,sequence)) : LONGT; + if(ret == 0L) + return 0L; + for (i = 1L; i <= stream->nmsgs;){ + elt = mail_elt (stream,i); + if (elt->deleted && (sequence ? elt->sequence : T)){ + snprintf (tmp, sizeof(tmp), "%s/%s", LOCAL->path[Cur], MDFILE(elt)); + if (unlink (tmp) < 0) {/* try to delete the message */ + snprintf (tmp, sizeof(tmp), "Expunge of message %ld failed, aborted: %s",i, + strerror (errno)); + if (!stream->silent) + mm_log (tmp,WARN); + break; + } + if (elt->private.spare.ptr) + maildir_free_file ((void **) &elt->private.spare.ptr); + if (elt->recent) --recent;/* if recent, note one less recent message */ + mail_expunged (stream,i); /* notify upper levels */ + n++; /* count up one more expunged message */ + } + else i++; + } + if(n){ /* output the news if any expunged */ + snprintf (tmp, sizeof(tmp), "Expunged %ld messages", n); + if (!stream->silent) + mm_log (tmp,(long) NIL); + } + else + if (!stream->silent) + mm_log ("No messages deleted, so no update needed",(long) NIL); + mm_nocritical (stream); /* release critical */ + /* notify upper level of new mailbox size */ + mail_exists (stream, stream->nmsgs); + mail_recent (stream, recent); + return ret; +} + +long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options) +{ + STRING st; + MESSAGECACHE *elt; + unsigned long len; + int fd; + unsigned long i; + struct stat sbuf; + char tmp[MAILTMPLEN], flags[MAILTMPLEN], path[MAILTMPLEN], *s; + /* copy the messages */ + if ((options & CP_UID) ? mail_uid_sequence (stream, sequence) : + mail_sequence (stream,sequence)) + for (i = 1L; i <= stream->nmsgs; i++) + if ((elt = mail_elt (stream,i))->sequence){ + MSGPATH(path, LOCAL->dir, MDFILE(elt), MDLOC(elt)); + if (((fd = open (path,O_RDONLY,NIL)) < 0) + ||((!elt->rfc822_size && + ((stat(path, &sbuf) < 0) || !S_ISREG (sbuf.st_mode))))) + return NIL; + if(!elt->rfc822_size) + MDSIZE(elt) = sbuf.st_size; + s = (char *) fs_get(MDSIZE(elt) + 1); + read (fd,s,MDSIZE(elt)); + s[MDSIZE(elt)] = '\0'; + close (fd); + len = strcrlfcpy (&LOCAL->buf,&LOCAL->buflen, s, MDSIZE(elt)); + INIT (&st,mail_string, LOCAL->buf, len); + elt->rfc822_size = len; + fs_give ((void **)&s); + + flags[0] = flags[1] = '\0'; + if (elt->seen) strcat (flags," \\Seen"); + if (elt->draft) strcat (flags," \\Draft"); + if (elt->deleted) strcat (flags," \\Deleted"); + if (elt->flagged) strcat (flags," \\Flagged"); + if (elt->answered) strcat (flags," \\Answered"); + flags[0] = '('; /* open list */ + strcat (flags,")"); /* close list */ + mail_date (tmp,elt); /* generate internal date */ + if (!mail_append_full (NIL, mailbox, flags, tmp, &st)) + return NIL; + if (options & CP_MOVE) elt->deleted = T; + } + return LONGT; /* return success */ +} + +long maildir_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data) +{ + int fd, k; + STRING *message; + char c,*s, *flags, *date; + char tmp[MAILTMPLEN],file[MAILTMPLEN],path1[MAILTMPLEN],path2[MAILTMPLEN]; + MESSAGECACHE elt; + long i, size = 0L, ret = LONGT, f; + unsigned long uf, ti; + static unsigned int transact = 0; + + if (!maildir_valid(mailbox)) { + snprintf (tmp, sizeof(tmp), "Not a valid Maildir mailbox: %s", mailbox); + mm_log (tmp,ERROR); + return NIL; + } + + if (!*mdlocaldomain) + md_domain_name(); /* get domain name for maildir files in mdlocaldomain now! */ + + if (mypid == (pid_t) 0) + mypid = getpid(); + + if (!stream){ + stream = &maildirproto; + + for (k = 0; k < NUSERFLAGS && stream->user_flags[k]; ++k) + fs_give ((void **) &stream->user_flags[k]); + } + + if (!(*af)(stream, data, &flags, &date, &message)) return NIL; + + mm_critical (stream); /* go critical */ + /* call time(0) only once, use transact to distinguish instead */ + ti = time(0); + do { + if (!SIZE (message)) { /* guard against zero-length */ + mm_log ("Append of zero-length message",ERROR); + ret = NIL; + break; + } + if (date && !mail_parse_date(&elt,date)){ + snprintf (tmp, sizeof(tmp), "Bad date in append: %.80s",date); + mm_log (tmp,ERROR); + ret = NIL; + break; + } + f = mail_parse_flags (stream,flags,&uf); + /* build file name we will use */ + snprintf (file, sizeof(file), "%lu.%d_%09u.%s%s%s%s%s%s", + ti, mypid, transact++, mdlocaldomain, (f ? MDSEP(2) : ""), + MDFLAG(Draft, f&fDRAFT), MDFLAG(Flagged, f&fFLAGGED), + MDFLAG(Replied, f&fANSWERED), MDFLAG(Seen, f&fSEEN)); + /* build tmp file name */ + if (maildir_file_path(mailbox, tmp, sizeof(tmp))) + MSGPATH(path1, tmp, file, Tmp); + + if ((fd = open (path1,O_WRONLY|O_CREAT|O_EXCL,S_IREAD|S_IWRITE)) < 0) { + snprintf (tmp, sizeof(tmp), "Can't open append mailbox: %s", strerror (errno)); + mm_log (tmp, ERROR); + return NIL; + } + for (size = 0,i = SIZE (message),s = (char *) fs_get (i + 1); i; --i) + if ((c = SNX (message)) != '\015') s[size++] = c; + if ((write (fd, s, size) < 0) || fsync (fd)) { + unlink (path1); /* delete message */ + snprintf (tmp, sizeof(tmp), "Message append failed: %s", strerror (errno)); + mm_log (tmp, ERROR); + ret = NIL; + } + fs_give ((void **) &s); /* flush the buffer */ + close (fd); /* close the file */ + /* build final filename to use */ + if (maildir_file_path(mailbox, tmp, sizeof(tmp))) + MSGPATH(path2, tmp, file, New); + if (rename (path1,path2) < 0) { + snprintf (tmp, sizeof(tmp), "Message append failed: %s", strerror (errno)); + mm_log (tmp, ERROR); + ret = NIL; + } + unlink (path1); + + if (ret) + if (!(*af) (stream,data,&flags,&date,&message)) ret = NIL; + + } while (ret && message); /* write the data */ + mm_nocritical (stream); /* release critical */ + return ret; +} + +long maildir_delete (MAILSTREAM *stream,char *mailbox) +{ + DIR *dirp; + struct direct *d; + int i, remove_dir = 0, mddir = 0, rv, error = 0; + char tmp[MAILTMPLEN],tmp2[MAILTMPLEN], realname[MAILTMPLEN]; + struct stat sbuf; + int courier = IS_COURIER(mailbox); + + if (mailbox[strlen(mailbox) - 1] == MDSEPARATOR(courier)){ + remove_dir++; + mailbox[strlen(mailbox) -1] = '\0'; + } + + if (!maildir_valid(mailbox)){ + maildir_file_path(mailbox, tmp, sizeof(tmp)); + if (stat(tmp, &sbuf) < 0 || !S_ISDIR(sbuf.st_mode)){ + snprintf(tmp, sizeof(tmp), "Can not remove %s", mailbox); + error++; + } + } + + if (!error && remove_dir && !maildir_dir_is_empty(mailbox)){ + snprintf(tmp, sizeof(tmp), "Can not remove directory %s/: directory not empty", mailbox); + error++; + } + + if(error){ + mm_log (tmp,ERROR); + return NIL; + } + + maildir_close(stream,0); /* even if stream was NULL */ + + maildir_file_path(mailbox, realname, sizeof(realname)); + + if (remove_dir){ + snprintf(tmp, sizeof(tmp), "%s/%s", realname, MDDIR); + if ((rv = stat (tmp,&sbuf)) == 0 && S_ISREG(sbuf.st_mode)) + rv = unlink(tmp); + else if (errno == ENOENT) + rv = 0; + if (rv != 0){ + snprintf(tmp, sizeof(tmp), "Can not remove %s/%s: %s", tmp2, MDDIR, strerror(errno)); + mm_log (tmp,ERROR); + return NIL; + } + if (!maildir_valid(realname) && rmdir(realname) != 0){ + snprintf(tmp, sizeof(tmp), "Can not remove %s/: %s", mailbox, strerror(errno)); + mm_log (tmp, ERROR); + return NIL; + } + return LONGT; + } + /* else remove just the folder. Remove all hidden files, except MDDIR */ + for (i = Cur; i != EndDir; i++){ + MDFLD(tmp, realname, i); + + if (!(dirp = opendir (tmp))){ + snprintf(tmp, sizeof(tmp), "Can not read %s/: %s", mailbox, strerror(errno)); + mm_log (tmp, ERROR); + return NIL; + } + + while ((d = readdir(dirp)) != NULL){ + if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..")){ + snprintf(tmp2, sizeof(tmp2), "%s/%s", tmp, d->d_name); + if (unlink(tmp2) != 0){ + snprintf(tmp2, sizeof(tmp2), "Can not remove %s: %s", mailbox, strerror(errno)); + mm_log (tmp2, ERROR); + return NIL; + } + } + } + closedir(dirp); + if (rmdir(tmp) != 0){ + snprintf(tmp, sizeof(tmp), "Can not remove %s: %s", mailbox, strerror(errno)); + mm_log (tmp, ERROR); + return NIL; + } + } + /* + * ok we have removed all subdirectories of the folder mailbox, Remove the + * hidden files. + */ + + if(!(dirp = opendir (realname))){ + snprintf(tmp, sizeof(tmp), "Can not read %s/: %s", realname, strerror(errno)); + mm_log (tmp, ERROR); + return NIL; + } + + while ((d = readdir(dirp)) != NULL){ + if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..") + && (!strcmp(d->d_name, MDDIR) + || !strncmp(d->d_name, MDUIDLAST, strlen(MDUIDLAST)) + || !strncmp(d->d_name, MDUIDTEMP, strlen(MDUIDTEMP)))){ + if(strcmp(d->d_name, MDDIR) == 0) + mddir++; + snprintf(tmp, sizeof(tmp), "%s/%s", realname, d->d_name); + if (unlink(tmp) != 0) + error++; + } + } + closedir(dirp); + if (error || + (maildir_dir_is_empty(mailbox) && mddir == 0 && rmdir(realname) < 0)){ + snprintf(tmp, sizeof(tmp), "Can not remove folder %s: %s", mailbox, strerror(errno)); + mm_log (tmp, ERROR); + return NIL; + } + return LONGT; +} + +long maildir_rename (MAILSTREAM *stream, char *old, char *new) +{ + char tmp[MAILTMPLEN], tmpnew[MAILTMPLEN], realold[MAILTMPLEN]; + char realnew[MAILTMPLEN]; + int courier = IS_COURIER(old) && IS_COURIER(new); + int i; + long rv = LONGT; + COURIER_S *cdir; + + if((IS_COURIER(old) || IS_COURIER(new)) && !courier){ + snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s to %s", old, new); + mm_log (tmp, ERROR); + return NIL; + } + + if (!maildir_valid(old)){ + snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s: folder not in maildir format",old); + mm_log (tmp, ERROR); + return NIL; + } + maildir_file_path(old, realold, sizeof(realold)); + if (!maildir_valid_name(new) && new[0] == '#'){ + snprintf (tmp, sizeof(tmp), "Cannot rename mailbox %s: folder not in maildir format", new); + mm_log (tmp, ERROR); + return NIL; + } + maildir_file_path(new, realnew, sizeof(realnew)); + if (access(tmpnew,F_OK) == 0){ /* new mailbox name must not exist */ + snprintf (tmp, sizeof(tmp), "Cannot rename to mailbox %s: destination already exists", new); + mm_log (tmp, ERROR); + return NIL; + } + + if(!courier){ + if (rename(realold, realnew)){ /* try to rename the directory */ + snprintf(tmp, sizeof(tmp), "Can't rename mailbox %s to %s: %s", old, new, + strerror(errno)); + mm_log(tmp,ERROR); + return NIL; + } + return LONGT; /* return success */ + } + + cdir = courier_list_dir(old); + for (i = 0; cdir && i < cdir->total; i++){ + if(strstr(cdir->data[i]->name, old)){ + snprintf(tmp, sizeof(tmp), "%s%s", new, cdir->data[i]->name+strlen(old)); + maildir_file_path(cdir->data[i]->name, realold, sizeof(realold)); + maildir_file_path(tmp, realnew, sizeof(realnew)); + if (rename(realold, realnew)){ + snprintf (tmp, sizeof(tmp), "Can't rename mailbox %s to %s: %s", old, new, + strerror(errno)); + mm_log(tmp,ERROR); + rv = NIL; + } + } + } + courier_free_cdir(&cdir); + return rv; +} + +long maildir_sub(MAILSTREAM *stream,char *mailbox) +{ + return sm_subscribe(mailbox); +} + +long maildir_unsub(MAILSTREAM *stream,char *mailbox) +{ + return sm_unsubscribe(mailbox); +} + +void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat) +{ + void *sdb = NIL; + char *s, test[MAILTMPLEN]; + /* get canonical form of name */ + if (maildir_canonicalize (test, ref, pat) && (s = sm_read (&sdb))) { + do if (pmatch_full (s, test, '/')) mm_lsub (stream, '/', s, NIL); + while ((s = sm_read (&sdb)) != NULL); /* until no more subscriptions */ + } +} + +long maildir_canonicalize (char *pattern,char *ref,char *pat) +{ + if (ref && *ref) { /* have a reference */ + strcpy (pattern,ref); /* copy reference to pattern */ + /* # overrides mailbox field in reference */ + if (*pat == '#') strcpy (pattern,pat); + /* pattern starts, reference ends, with / */ + else if ((*pat == '/') && (pattern[strlen (pattern) - 1] == '/')) + strcat (pattern,pat + 1); /* append, omitting one of the period */ + + else strcat (pattern,pat); /* anything else is just appended */ + } + else strcpy (pattern,pat); /* just have basic name */ + return maildir_valid_name(pattern) ? LONGT : NIL; +} + +void maildir_list_work (MAILSTREAM *stream,char *dir,char *pat,long level) +{ + DIR *dp; + struct direct *d; + struct stat sbuf; + char curdir[MAILTMPLEN],name[MAILTMPLEN], tmp[MAILTMPLEN]; + char realpat[MAILTMPLEN]; + long i; + char *maildirpath = mdirpath(); + + snprintf(curdir, sizeof(curdir), "%s/%s/", myrootdir(pat), dir ? dir : maildirpath); + if ((dp = opendir (curdir)) != NULL){ + if (dir) snprintf (name, sizeof(name), "%s%s/",MDPREFIX(CCLIENT),dir); + else strcpy (name, pat); + + if (level == 0 && !strpbrk(pat,"%*")){ + if(maildir_valid(pat)){ + i = maildir_contains_folder(pat, NULL) + ? LATT_HASCHILDREN + : (maildir_is_dir(pat, NULL) + ? LATT_HASNOCHILDREN : LATT_NOINFERIORS); + maildir_file_path(pat, realpat, sizeof(realpat)); + i += maildir_any_new_msgs(realpat) + ? LATT_MARKED : LATT_UNMARKED; + mm_list (stream,'/', pat, i); + } + else + if(pat[strlen(pat) - 1] == '/') + mm_list (stream,'/', pat, LATT_NOSELECT); + } + + while ((d = readdir (dp)) != NULL) + if(strcmp(d->d_name, ".") && strcmp(d->d_name,"..") + && strcmp(d->d_name, MDNAME(Cur)) + && strcmp(d->d_name, MDNAME(Tmp)) + && strcmp(d->d_name, MDNAME(New))){ + + if (dir) snprintf (tmp, sizeof(tmp), "%s%s", name,d->d_name); + else strcpy(tmp, d->d_name); + + if(pmatch_full (tmp, pat,'/')){ + snprintf(tmp, sizeof(tmp), "%s/%s/%s", myrootdir(d->d_name), + (dir ? dir : maildirpath), d->d_name); + if(stat (tmp,&sbuf) == 0 + && ((sbuf.st_mode & S_IFMT) == S_IFDIR)){ + if (dir) snprintf (tmp, sizeof(tmp), "%s%s", name,d->d_name); + else strcpy(tmp, d->d_name); + i = maildir_valid(tmp) + ? (maildir_contains_folder(dir, d->d_name) + ? LATT_HASCHILDREN + : (maildir_is_dir(dir, d->d_name) + ? LATT_HASNOCHILDREN : LATT_NOINFERIORS)) + : LATT_NOSELECT; + i += maildir_any_new_msgs(tmp) + ? LATT_MARKED : LATT_UNMARKED; + mm_list (stream,'/',tmp, i); + strcat (tmp, "/"); + if(dmatch (tmp, pat,'/') && + (level < (long) mail_parameters (NIL,GET_LISTMAXLEVEL,NIL))){ + snprintf(tmp, sizeof(tmp), "%s/%s",dir,d->d_name); + maildir_list_work (stream,tmp,pat,level+1); + } + } + } + } + closedir (dp); + } +} + +void courier_list_work (MAILSTREAM *stream, char *dir, char *pat, long level) +{ + char c, curdir[MAILTMPLEN], tmp[MAILTMPLEN]; + char realname[MAILTMPLEN], realpat[MAILTMPLEN] = {'\0'}; + int i, found; + long style = (long) maildir_parameters(GET_COURIERSTYLE, NIL), j; + char *maildirpath = mdirpath(); + COURIER_S *cdir; + + if(!strpbrk(pat,"%*")){ /* a mailbox */ + maildir_file_path(pat, curdir, sizeof(curdir)); + i = strlen(curdir) - 1; + if(curdir[i] == '/') + curdir[i] = '\0'; + cdir = courier_list_dir(curdir); + if(cdir){ + found = 0; j = 0L; + if(maildir_valid_name(pat)){ + for(i = 0; !found && i < cdir->total; i++) + if(strstr(curdir, cdir->data[i]->name)){ + if(strlen(curdir) < strlen(cdir->data[i]->name)) + found += 2; + else if(strlen(curdir) == strlen(cdir->data[i]->name)) + found -= 1; + } + if(found > 0) + j = LATT_HASCHILDREN; + else if(found == 0) + j = (style == COURIER) ? LATT_HASNOCHILDREN : LATT_NOINFERIORS; + } + else + j = LATT_NOSELECT; + j += maildir_any_new_msgs(curdir) ? LATT_MARKED : LATT_UNMARKED; + if (found) + mm_list (stream, '.', pat, j); + courier_free_cdir(&cdir); + } + return; + } + + strcpy(tmp,pat + 4); /* a directory */ + j = strlen(pat) - 1; + maildir_file_path(pat, realpat, sizeof(realpat)); + c = pat[j]; + pat[j] = '\0'; + realname[0] = '\0'; + if(dir) + maildir_file_path(dir, realname, sizeof(realname)); + snprintf(curdir, sizeof(curdir), "%s%s%s/%s", (dir ? "" : myrootdir(pat)), (dir ? "" : "/"), + (dir ? realname : maildirpath), (dir ? "" : ".")); + snprintf(tmp, sizeof(tmp), "%s%s/.", MDPREFIX(COURIER), dir ? dir : maildirpath); + if (level == 0 && tmp && pmatch_full (tmp, realpat, '.')) + mm_list (stream,'.', tmp, LATT_NOSELECT); + + cdir = courier_list_dir(pat); + pat[j] = c; + for (i = 0; cdir && i < cdir->total; i++) + if(pmatch_full (cdir->data[i]->name, pat, '.')){ + snprintf(tmp, sizeof(tmp), "%s.", cdir->data[i]->name); + courier_list_info(&cdir, tmp, i); + mm_list (stream,'.',cdir->data[i]->name, cdir->data[i]->attribute); + } + courier_free_cdir(&cdir); +} + +int +same_maildir_file(char *name1, char *name2) +{ + char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN]; + char *s; + + strcpy(tmp1, name1 ? name1 : ""); + strcpy(tmp2, name2 ? name2 : ""); + if ((s = strrchr(tmp1, FLAGSEP)) != NULL) + *s = '\0'; + if (((s = strrchr(tmp1, SIZESEP)) != NULL) && (strchr(s,'.') == NULL)) + *s = '\0'; + if ((s = strrchr(tmp2, FLAGSEP)) != NULL) + *s = '\0'; + if (((s = strrchr(tmp2, SIZESEP)) != NULL) && (strchr(s,'.') == NULL)) + *s = '\0'; + + return !strcmp(tmp1, tmp2); +} + +unsigned long antoul(char *seed) +{ + int i, error = 0; + unsigned long val = 0L, rv1 = 0L, t; + char c, *p; + if(!seed) + return 0L; + t = strtoul(seed, &p, 10); + if(p && (*p == '.' || *p == '_')) + return t; + /* else */ + if((p = strchr(seed,'.')) != NULL) + *p = '\0'; + error = (strlen(seed) > 6); /* too long */ + for(i= strlen(seed)-1; error == 0 && i >= 0; i--){ + c = seed[i]; + if (c >= 'A' && c <= 'Z') val = c - 'A'; + else if (c >= 'a' && c <= 'z') val = c - 'a' + 26; + else if (c >= '0' && c <= '9') val = c - '0' + 26 + 26; + else if (c == '-') val = c - '-' + 26 + 26 + 10; + else if (c == '_') val = c - '_' + 26 + 26 + 10 + 1; + else error++; + rv1 = val + (rv1 << 6); + } + if(p) + *p = '.'; + return error ? 0L : rv1; +} + +unsigned long mdfntoul (char *name) +{ + unsigned long t; + char *r, last; + + if((*name == '_') && ((r = strpbrk(name,".,%+")) != NULL)){ /* Grrr!!! */ + last = *r; + *r = '\0'; + t = antoul(r+1); + *r = last; + } + else + t = antoul(name); + return t; +} + +int comp_maildir_file(char *name1, char *name2) +{ + int uset1 = 1, uset2 = 1, i, j, cmp; + unsigned long t1, t2; + char *s1, *s2; + + if (!(name1 && *name1)) + return (name2 && *name2) ? (*name2 == FLAGSEP ? 0 : -1) : 0; + + if (!(name2 && *name2)) + return (name1 && *name1) ? (*name1 == FLAGSEP ? 0 : 1) : 0; + + if((cmp = strcmp(name1,name2)) == 0) + return 0; + + t1 = strtoul(name1, &s1, 10); + t2 = strtoul(name2, &s2, 10); + + if(!s1 || *s1 != '.') + uset1 = 0; + + if(!s2 || *s2 != '.') + uset2 = 0; + + if(uset1 && uset2) /* normal sort order */ + return (t1 < t2) ? -1 : (t1 > t2 ? 1 : (cmp < 0 ? -1 : 1)); + + /* If we make it here we say Grrrr.... first, then we try to figure out + * how to sort this mess. + * These are the rules. + * If there is a number at the beginning it is bigger than anything else. + * If there are digits, then the number of digits decides which one is bigger. + */ + + for(i = 0; isdigit(name1[i]); i++); + for(j = 0; isdigit(name2[j]); j++); + + return(uset1 ? 1 + : (uset2 ? -1 + : (i < j ? -1 : (i > j ? 1 : (cmp < 0 ? -1 : 1))))); +} + +void +maildir_getflag(char *name, int *d, int *f, int *r ,int *s, int *t) +{ + char tmp[MAILTMPLEN], *b; + int offset = 0; + int tmpd, tmpf, tmpr, tmps, tmpt; + + if(d) *d = 0; + if(f) *f = 0; + if(r) *r = 0; + if(s) *s = 0; + if(t) *t = 0; + + tmpd = tmpf = tmpr = tmps = tmpt = NIL; /* no flags set by default */ + strcpy(tmp,name); + while ((b = strrchr(tmp+offset, FLAGSEP)) != NULL){ + char flag,last; + int k; + if (!++b) break; + switch (*b){ + case '1': + case '2': + case '3': flag = *b; b += 2; + for (k = 0; b[k] && b[k] != FLAGSEP && b[k] != ','; k++); + last = b[k]; + b[k] = '\0'; + if (flag == '2' || flag == '3'){ + tmpd = strchr (b, MDFLAGC(Draft)) ? T : NIL; + tmpf = strchr (b, MDFLAGC(Flagged)) ? T : NIL; + tmpr = strchr (b, MDFLAGC(Replied)) ? T : NIL; + tmps = strchr (b, MDFLAGC(Seen)) ? T : NIL; + tmpt = strchr (b, MDFLAGC(Trashed)) ? T : NIL; + } + b[k] = last; + b += k; + for (; tmp[offset] && tmp[offset] != FLAGSEP; offset++); + offset++; + break; + default: break; /* Should we crash?... Nahhh */ + } + } + if(d) *d = tmpd; + if(f) *f = tmpf; + if(r) *r = tmpr; + if(s) *s = tmps; + if(t) *t = tmpt; +} + +int +maildir_message_in_list(char *msgname, struct direct **names, + unsigned long bottom, unsigned long top, unsigned long *pos) +{ + unsigned long middle = (bottom + top)/2; + int test; + + if (!msgname) + return NIL; + + if (pos) *pos = middle; + + if (same_maildir_file(msgname, names[middle]->d_name)) + return T; + + if (middle == bottom){ /* 0 <= 0 < 1 */ + int rv = NIL; + if (same_maildir_file(msgname, names[middle]->d_name)){ + rv = T; + if (pos) *pos = middle; + } + else + if (same_maildir_file(msgname, names[top]->d_name)){ + rv = T; + if (pos) *pos = top; + } + return rv; + } + + test = comp_maildir_file(msgname, names[middle]->d_name); + + if (top <= bottom) + return test ? NIL : T; + + if (test < 0 ) /* bottom < msgname < middle */ + return maildir_message_in_list(msgname, names, bottom, middle, pos); + else if (test > 0) /* middle < msgname < top */ + return maildir_message_in_list(msgname, names, middle, top, pos); + else return T; +} + +void +maildir_abort(MAILSTREAM *stream) +{ + if (LOCAL){ + DirNamesType i; + + if(LOCAL->candouid) + maildir_read_uid(stream, NULL, &stream->uid_validity); + if (LOCAL->dir) fs_give ((void **) &LOCAL->dir); + for (i = Cur; i < EndDir; i++) + if(LOCAL->path[i]) fs_give ((void **) &LOCAL->path[i]); + fs_give ((void **) &LOCAL->path); + if (LOCAL->buf) fs_give ((void **) &LOCAL->buf); + if(LOCAL->uidtempfile){ + unlink(LOCAL->uidtempfile); + fs_give ((void **) &LOCAL->uidtempfile); + } + fs_give ((void **) &stream->local); + } + if (mdfpath) fs_give((void **)&mdfpath); + stream->dtb = NIL; +} + +int +maildir_contains_folder(char *dirname, char *name) +{ + char tmp[MAILTMPLEN], tmp2[MAILTMPLEN]; + int rv = 0; + DIR *dir; + struct direct *d; + + maildir_file_path(dirname, tmp2, sizeof(tmp2)); + if(name){ + strcat(tmp2,"/"); + strcat(tmp2, name); + } + + if (!(dir = opendir (tmp2))) + return NIL; + + while ((d = readdir(dir)) != NULL){ + if (strcmp(d->d_name, ".") && strcmp(d->d_name,"..") + && strcmp(d->d_name, MDNAME(Cur)) + && strcmp(d->d_name, MDNAME(Tmp)) + && strcmp(d->d_name, MDNAME(New))){ + + snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); + if(maildir_valid(tmp)){ + rv++; + break; + } + } + } + closedir(dir); + return rv; +} + +int +maildir_is_dir(char *dirname, char *name) +{ + char tmp[MAILTMPLEN]; + struct stat sbuf; + + maildir_file_path(dirname, tmp, sizeof(tmp)); + if(name){ + strcat(tmp, "/"); + strcat(tmp, name); + } + strcat(tmp, "/"); + strcat(tmp, MDDIR); + + return ((stat(tmp, &sbuf) == 0) && S_ISREG (sbuf.st_mode)) ? 1 : 0; +} + +int +maildir_dir_is_empty(char *mailbox) +{ + char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], tmp3[MAILTMPLEN],*s; + int rv = 1, courier = IS_COURIER(mailbox); + DIR *dir; + struct direct *d; + struct stat sbuf; + + maildir_file_path(mailbox, tmp2, sizeof(tmp2)); + + if(courier){ + strcpy(tmp3, tmp2); + if(s = strrchr(tmp2, '/')) + *s = '\0'; + } + + if (!(dir = opendir (tmp2))) + return rv; + + if(courier){ + while((d = readdir(dir)) != NULL){ + snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); + if(!strncmp(tmp, tmp3, strlen(tmp3)) + && tmp[strlen(tmp3)] == '.'){ + rv = 0; + break; + } + } + } + else + while ((d = readdir(dir)) != NULL){ + snprintf(tmp, sizeof(tmp), "%s/%s", tmp2, d->d_name); + if (strcmp(d->d_name, ".") + && strcmp(d->d_name,"..") + && strcmp(d->d_name, MDNAME(Cur)) + && strcmp(d->d_name, MDNAME(Tmp)) + && strcmp(d->d_name, MDNAME(New)) + && strcmp(d->d_name, MDDIR) + && strcmp(d->d_name, MDUIDVALIDITY) + && !(d->d_name[0] == '.' + && stat (tmp,&sbuf) == 0 + && S_ISREG(sbuf.st_mode))){ + rv = 0; + break; + } + } + closedir(dir); + return rv; +} + +void +maildir_get_file (MAILDIRFILE **mdfile) +{ + MAILDIRFILE *md; + + md = (MAILDIRFILE *) fs_get(sizeof(MAILDIRFILE)); + memset(md, 0, sizeof(MAILDIRFILE)); + *mdfile = md; +} + +void +maildir_free_file (void **mdfile) +{ + MAILDIRFILE *md = (mdfile && *mdfile) ? (MAILDIRFILE *) *mdfile : NULL; + + if (md){ + if (md->name) fs_give((void **)&md->name); + fs_give((void **)&md); + } +} + +void +maildir_free_file_only (void **mdfile) +{ + MAILDIRFILE *md = (mdfile && *mdfile) ? (MAILDIRFILE *) *mdfile : NULL; + + if (md && md->name) + fs_give((void **)&md->name); +} + +int +maildir_any_new_msgs(char *mailbox) +{ + char tmp[MAILTMPLEN]; + int rv = NIL; + DIR *dir; + struct direct *d; + + MDFLD(tmp, mailbox, New); + + if (!(dir = opendir (tmp))) + return rv; + + while ((d = readdir(dir)) != NULL){ + if (d->d_name[0] == '.') + continue; + rv = T; + break; + } + closedir(dir); + return rv; +} + + +void +maildir_get_date(MAILSTREAM *stream, unsigned long msgno) +{ + MESSAGECACHE *elt; + struct tm *t; + time_t ti; + int i,k; + + elt = mail_elt (stream,msgno); + if(elt && elt->year != 0) + return; + if ((ti = mdfntoul(MDFILE(elt))) > 0L && (t = gmtime(&ti))){ + i = t->tm_hour * 60 + t->tm_min; + k = t->tm_yday; + t = localtime(&ti); + i = t->tm_hour * 60 + t->tm_min - i; + if((k = t->tm_yday - k) != 0) + i += ((k < 0) == (abs (k) == 1)) ? -24*60 : 24*60; + k = abs (i); + elt->hours = t->tm_hour; + elt->minutes = t->tm_min; + elt->seconds = t->tm_sec; + elt->day = t->tm_mday; elt->month = t->tm_mon + 1; + elt->year = t->tm_year - (BASEYEAR - 1900); + elt->zoccident = (k == i) ? 0 : 1; + elt->zhours = k/60; + elt->zminutes = k % 60; + } +} + +/* Support for Courier Style directories + When this code is complete there will be two types of support, which + will be configurable. The problem is the following: In Courier style + folder structure, a "folder" may have a subfolder called + "folder.subfolder", which is not natural in the file system in the + sense that I can not stat for "folder.subfolder" wihtout knowing what + "subfolder" is. It needs to be guessed. Because of this I need to look + in the list of folders if there is a folder with a name + "folder.subfolder", before I can say if the folder is dual or not. One + can avoid this annoyance if one ignores the problem by declaring that + every folder is dual. I will however code as the default the more + complicated idea of scaning the containing directory each time it is + modified and search for subfolders, and list the entries it found. + */ + +int courier_dir_select (const struct direct *name) +{ + return name->d_name[0] == '.' && (strlen(name->d_name) > 2 + || (strlen(name->d_name) == 2 && name->d_name[1] != '.')); +} + +int courier_dir_sort (const struct direct **d1, const struct direct **d2) +{ + const struct direct *e1 = *(const struct direct **) d1; + const struct direct *e2 = *(const struct direct **) d2; + + return strcmp((char *) e1->d_name, (char *) e2->d_name); +} + +void courier_free_cdir (COURIER_S **cdir) +{ + int i; + + if (!*cdir) + return; + + if ((*cdir)->path) fs_give((void **)&((*cdir)->path)); + for (i = 0; i < (*cdir)->total; i++) + if((*cdir)->data[i]->name) fs_give((void **)&((*cdir)->data[i]->name)); + fs_give((void **)&((*cdir)->data)); + fs_give((void **)&(*cdir)); +} + +COURIER_S *courier_get_cdir (int total) +{ + COURIER_S *cdir; + + cdir = (COURIER_S *)fs_get(sizeof(COURIER_S)); + memset(cdir, 0, sizeof(COURIER_S)); + cdir->data = (COURIERLOCAL **) fs_get(total*sizeof(COURIERLOCAL *)); + memset(cdir->data, 0, sizeof(COURIERLOCAL *)); + cdir->total = total; + return cdir; +} + +int courier_search_list(COURIERLOCAL **data, char *name, int first, int last) +{ + int try = (first + last)/2; + + if(!strstr(data[try]->name, name)){ + if(first == try) /* first == last || first + 1 == last */ + return strstr(data[last]->name, name) ? 1 : 0; + if(strcmp(data[try]->name, name) < 0) /*data[try] < name < data[end] */ + return courier_search_list(data, name, try, last); + else /* data[begin] < name < data[try] */ + return courier_search_list(data, name, first, try); + } + return 1; +} + +/* Lists all directories that are subdirectories of a given directory */ + +COURIER_S *courier_list_dir(char *curdir) +{ + struct direct **names = NIL; + struct stat sbuf; + unsigned long ndir; + COURIER_S *cdir = NULL; + char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], pathname[MAILTMPLEN], + realname[MAILTMPLEN]; + int i, j, scand, td; + + /* There are two cases, either curdir is + #mc/INBOX. #mc/INBOX.foo + or + #mc/Maildir/. #mc/Maildir/.foo + */ + strcpy(tmp,curdir + 4); + if(!strncmp(ucase(tmp), "INBOX", 5)) + strcpy(tmp, "#mc/INBOX."); + else{ + strcpy(tmp, curdir); + for (i = strlen(tmp) - 1; tmp[i] && tmp[i] != '/'; i--); + tmp[i+2] = '\0'; /* keep the last "." intact */ + } + maildir_file_path(tmp, realname, sizeof(realname)); + maildir_scandir (realname, &names, &ndir, &scand, COURIER); + + if (scand > 0){ + cdir = courier_get_cdir(ndir); + cdir->path = cpystr(realname); + for(i = 0, j = 0; i < ndir; i++){ + td = realname[strlen(realname) - 1] == '.' + && *names[i]->d_name == '.'; + snprintf(tmp2, sizeof(tmp2), "%s%s", tmp, names[i]->d_name+1); + snprintf(pathname, sizeof(pathname), "%s%s", realname, names[i]->d_name + td); + if(stat(pathname, &sbuf) == 0 && S_ISDIR(sbuf.st_mode)){ + cdir->data[j] = (COURIERLOCAL *) fs_get(sizeof(COURIERLOCAL)); + cdir->data[j++]->name = cpystr(tmp2); + } + fs_give((void **)&names[i]); + } + cdir->total = j; + if(cdir->total == 0) + courier_free_cdir(&cdir); + } + if(names) + fs_give((void **) &names); + return cdir; +} + +void +courier_list_info(COURIER_S **cdirp, char *data, int i) +{ + long style = (long) maildir_parameters(GET_COURIERSTYLE, NIL); + COURIER_S *cdir = *cdirp; + + if(maildir_valid(cdir->data[i]->name)){ + if(courier_search_list(cdir->data, data, 0, cdir->total - 1)) + cdir->data[i]->attribute = LATT_HASCHILDREN; + else + cdir->data[i]->attribute = (style == COURIER) + ? LATT_HASNOCHILDREN : LATT_NOINFERIORS; + } + else + cdir->data[i]->attribute = LATT_NOSELECT; + cdir->data[i]->attribute += maildir_any_new_msgs(cdir->data[i]->name) + ? LATT_MARKED : LATT_UNMARKED; +} + +/* UID Support */ +/* Yes, I know I procastinated a lot about this, but here it is finally */ + +/* return code: + bigger than zero: this session can assign uids + zero: this session will not assign uid + smaller than zero: this session temporarily suspends assigning uids + */ +int +maildir_can_assign_uid (MAILSTREAM *stream) +{ + unsigned int rv = 0; + int ownuid, existuid; + unsigned long t; + char tmp[MAILTMPLEN], tmp2[MAILTMPLEN], *p, *s; + DIR *dir; + struct direct *d; + + if(!stream || stream->rdonly + || !LOCAL || !LOCAL->dir || !(dir = opendir(LOCAL->dir))) + return 0; + + if(mypid == (pid_t) 0) + mypid = getpid(); + + snprintf(tmp, sizeof(tmp), "%s.%d", MDUIDTEMP, mypid); + + ownuid = existuid = 0; + s = NULL; + while ((d = readdir(dir)) != NULL){ + if(strncmp(d->d_name, tmp, strlen(tmp)) == 0){ + existuid++; ownuid++; + if(ownuid > 1){ + snprintf(tmp2, sizeof(tmp), "%s/%s", LOCAL->dir, d->d_name); + unlink(tmp2); + if(s){ + snprintf(tmp2, sizeof(tmp2), "%s/%s", LOCAL->dir, s); + unlink(tmp2); + fs_give((void **)&s); + } + } + else + s = cpystr(d->d_name); + } + else if(strncmp(d->d_name, MDUIDTEMP, strlen(MDUIDTEMP)) == 0) + existuid++; + } + + closedir(dir); + if(s) + fs_give((void **)&s); + + if(ownuid == 1 && existuid == 1) + rv = 1; + + if(ownuid == 0 && existuid == 0){ /* nobody owns the uid? */ + FILE *fp; + snprintf(tmp, sizeof(tmp), "%s/%s.%d.%lu", LOCAL->dir, MDUIDTEMP, mypid, time(0)); + if(fp = fopen(tmp, "w")){ + fclose(fp); + if(LOCAL->uidtempfile) + fs_give((void **)&LOCAL->uidtempfile); + LOCAL->uidtempfile = cpystr(tmp); + } + rv = 1; + } + + if(ownuid == 0 && existuid > 0) /* someone else owns uid assignment */ + return 0; + + /* if we own the uid, check that we do not own it more than once + * or that we share ownership. If any of these situations happens, + * give up the ownership until we can recover it + */ + + if(ownuid > 0){ + if(ownuid > 1) /* impossible, two lock files for the same session */ + return (-1)*ownuid; + + if(ownuid != existuid){ /* lock files for different sessions */ + if(LOCAL->uidtempfile){ + unlink(LOCAL->uidtempfile); + fs_give((void **)&LOCAL->uidtempfile); + } + return (-1)*ownuid; + } + } + + return rv; +} + +void +maildir_read_uid(MAILSTREAM *stream, unsigned long *uid_last, + unsigned long *uid_validity) +{ + int createuid, deleteuid = 0; + char tmp[MAILTMPLEN], *s = NULL; + DIR *dir; + struct direct *d; + + if(uid_last) *uid_last = 0L; + if(uid_last && uid_validity) *uid_validity = time(0); + if(!stream || !LOCAL || !LOCAL->dir || !(dir = opendir(LOCAL->dir))) + return; + + while ((d = readdir(dir)) != NULL){ + if(!strncmp(d->d_name, MDUIDLAST, strlen(MDUIDLAST))) + break; + } + createuid = d == NULL ? 1 : 0; + if(uid_last == NULL) + deleteuid++; + if(d){ + if(uid_last){ + s = d->d_name + strlen(MDUIDLAST) + 1; + *uid_last = strtoul(s, &s, 10); + if(!s || *s != '.'){ + deleteuid++; + createuid++; + *uid_last = 0L; + } + } + if(s && *s == '.'){ + if(uid_validity){ + s++; + *uid_validity = strtoul(s, &s, 10); + if(s && *s != '\0'){ + *uid_validity = time(0); + deleteuid++; + createuid++; + } + } + } + else{ + deleteuid++; + createuid++; + } + } + if(deleteuid){ + snprintf(tmp, sizeof(tmp), "%s/%s", LOCAL->dir, d->d_name); + unlink(tmp); + } + if(createuid) + maildir_write_uid(stream, (uid_last ? *uid_last : stream->uid_last), + uid_validity ? *uid_validity : time(0)); + closedir(dir); +} + +void +maildir_write_uid(MAILSTREAM *stream, unsigned long uid_last, + unsigned long uid_validity) +{ + char tmp[MAILTMPLEN]; + FILE *fp; + + if(!stream || stream->rdonly || !LOCAL || !LOCAL->dir) + return; + + snprintf(tmp, sizeof(tmp), "%s/%s.%010lu.%010lu", LOCAL->dir, MDUIDLAST, + uid_last, uid_validity); + if(fp = fopen(tmp, "w")) + fclose(fp); +} + +unsigned long +maildir_get_uid(char *name) +{ + char *s; + unsigned long rv = 0L; + + if(!name || (s = strstr(name,MDUIDSEP)) == NULL) + return rv; + + s += strlen(MDUIDSEP); + rv = strtoul(s, NULL, 10); + return rv; +} + + +void +maildir_delete_uid(MAILSTREAM *stream, unsigned long msgno) +{ + char old[MAILTMPLEN], new[MAILTMPLEN], *s, *t; + MESSAGECACHE *elt; + + elt = mail_elt(stream, msgno); + if(!stream || !elt || !elt->private.spare.ptr || !LOCAL || !LOCAL->dir) + return; + + snprintf(old, sizeof(old), "%s/%s/%s", LOCAL->dir, MDNAME(Cur), MDFILE(elt)); + t = MDFILE(elt); + if(s = strstr(MDFILE(elt), MDUIDSEP)){ + *s = '\0'; + s += strlen(MDUIDSEP); + strtoul(s, &s, 10); + snprintf(new, sizeof(new), "%s/%s/%s%s", LOCAL->dir, MDNAME(Cur), t, s); + if(rename(old, new) == 0){ + maildir_free_file_only ((void **)&elt->private.spare.ptr); + s = strrchr(new, '/'); + MDFILE(elt) = cpystr(s+1); + } + elt->private.uid = 0L; + } +} + +void +maildir_assign_uid(MAILSTREAM *stream, unsigned long msgno, unsigned long uid) +{ + int createuid, deleteuid = 0; + char old[MAILTMPLEN], new[MAILTMPLEN], *s, *t; + MESSAGECACHE *elt; + + elt = mail_elt(stream, msgno); + if(!stream || !elt || !elt->private.spare.ptr || !LOCAL || !LOCAL->dir) + return; + + maildir_delete_uid(stream, msgno); + snprintf(old, sizeof(old), "%s/%s/%s", LOCAL->dir, MDNAME(Cur), MDFILE(elt)); + t = MDFILE(elt); + if((s = strrchr(MDFILE(elt),FLAGSEP)) != NULL){ + *s++ = '\0'; + snprintf(new, sizeof(new), "%s/%s/%s%s%lu%c%s", + LOCAL->dir, MDNAME(Cur), t, MDUIDSEP, uid, FLAGSEP, s); + if(rename(old, new) == 0){ + maildir_free_file_only ((void **)&elt->private.spare.ptr); + s = strrchr(new, '/'); + MDFILE(elt) = cpystr(s+1); + stream->uid_validity = time(0); + } + elt->private.uid = uid; + } +} + +void +maildir_uid_renew_tempfile(MAILSTREAM *stream) +{ + char tmp[MAILTMPLEN]; + + if(!stream || stream->rdonly + || !LOCAL || !LOCAL->candouid || !LOCAL->dir || !LOCAL->uidtempfile) + return; + + if(mypid == (pid_t) 0) + mypid = getpid(); + + snprintf(tmp, sizeof(tmp), "%s/%s.%d.%lu", LOCAL->dir, MDUIDTEMP, mypid, time(0)); + if(rename(LOCAL->uidtempfile, tmp) == 0){ + fs_give((void **)&LOCAL->uidtempfile); + LOCAL->uidtempfile = cpystr(tmp); + } +} diff --git a/imap/src/osdep/unix/maildir.h b/imap/src/osdep/unix/maildir.h new file mode 100644 index 00000000..c1eef9e4 --- /dev/null +++ b/imap/src/osdep/unix/maildir.h @@ -0,0 +1,226 @@ +/* + * A few definitions that try to make this module portable to other + * platforms (e.g. Cygwin). This module is based on the information from + * http://cr.yp.to/proto/maildir.html + */ + +/* First we deal with the separator character */ +#ifndef FLAGSEP +#define FLAGSEP ':' +#endif +#define SIZESEP ',' + +const char sep1[] = {FLAGSEP, '1', ',', '\0'}; /* experimental semantics*/ +const char sep2[] = {FLAGSEP, '2', ',', '\0'}; /* Flags Information */ +const char sep3[] = {FLAGSEP, '3', ',', '\0'}; /* Grrrr.... */ + +const char *sep[] = { sep1, sep2, sep3, NULL}; + +#define MDSEP(i) sep[((i) - 1)] + +/* Now we deal with flags. Woohoo! */ +typedef enum {Draft, Flagged, Passed, Replied, Seen, Trashed, + EmptyFlag, EndFlags} MdFlagNamesType; +const int mdimapflags[] = {Draft, Flagged, Replied, Seen, Trashed, EmptyFlag, EndFlags}; +const int mdkwdflags[] = {Passed, EmptyFlag, EndFlags}; + +/* this array lists the codes for mdflgnms (maildir flag names) above */ +const char *mdflags[] = { "D", "F", "P", "R", "S", "T", "", NULL}; +/* and as characters too */ +const char cmdflags[] = { 'D', 'F', 'P', 'R', 'S', 'T', '0', '\0'}; + +/* MDFLAG(Seen, elt->seen) */ +#define MDFLAG(i,j) mdflags[j ? (i) : EmptyFlag] +/* MDFLAGC(Seen) */ +#define MDFLAGC(i) cmdflags[(i)] + +/* Now we deal with the directory structure */ +typedef enum {Cur, Tmp, New, EndDir} DirNamesType; +char *mdstruct[] = {"cur", "tmp", "new", NULL}; +#define MDNAME(i) mdstruct[(i)] +#define MDFLD(tmp, dir, i) sprintf((tmp),"%s/%s", (dir), mdstruct[(i)]) +#define MSGPATH(tmp, dir, msg,i) sprintf((tmp),"%s/%s/%s", (dir), mdstruct[(i)],(msg)) + +/* Files associated to a maildir directory */ + +#define MDUIDVALIDITY ".uidvalidity" /* support for old maildirs */ +#define MDDIR ".mdir" /* this folder is a directory */ +#define MDUIDLAST ".uidlast" /* last assigned uid */ +#define MDUIDTEMP ".uidtemp" /* We assign uid's no one else */ + + + +/* Support of Courier Structure */ +#define CCLIENT 0 +#define COURIER 1 +#define IS_CCLIENT(t) \ + (((t) && (t)[0] == '#' && ((t)[1] == 'm' || (t)[1] == 'M')\ + && ((t)[2] == 'd' || (t)[2] == 'D')\ + && (t)[3] == '/' && (t)[4] != '\0') ? 1 : 0) + +#define IS_COURIER(t) \ + (((t) && (t)[0] == '#' && ((t)[1] == 'm' || (t)[1] == 'M')\ + && ((t)[2] == 'c' || (t)[2] == 'C')\ + && (t)[3] == '/' && (t)[4] != '\0') ? 1 : 0) +#define MDPREFIX(s) ((s) ? "#mc/" : "#md/") +#define MDSEPARATOR(s) ((s) ? '.' : '/') + +/* UID Support */ + +#define MAXTEMPUID (unsigned long) 180L +const char mduid[] = {',','u','=','\0'}; +#define MDUIDSEP mduid + + +/* Now we deal with messages filenames */ +char mdlocaldomain[MAILTMPLEN+1] = {'\0'}; +pid_t mypid = (pid_t) 0; +static char *mdfpath = NULL; +static char myMdInboxDir[50] = { '\0' };/* Location of the Maildir INBOX */ +static long CourierStyle = CCLIENT; + +#define CHUNK 16384 /* from unix.h */ + +typedef struct courier_local { + char *name; /* name of directory/folder */ + int attribute; /* attributes (children/marked/etc) */ +} COURIERLOCAL; + +typedef struct courier { + char *path; /* Path to collection */ + time_t scantime; /* time at which information was generated */ + int total; /* total number of elements in data */ + COURIERLOCAL **data; +} COURIER_S; + +/* In gdb this is the *(struct maildir_local *)stream->local structure */ +typedef struct maildir_local { + unsigned int dirty : 1; /* diskcopy needs updating */ + unsigned int courier : 1; /* It is Courier style file system */ + unsigned int link : 1; /* There is a symbolic link */ + int candouid; /* we can assign uids and no one else */ + char *uidtempfile; /* path to uid temp file */ + int fd; /* fd of open message */ + char *dir; /* mail directory name */ + char **path; /* path to directories cur, new and tmp */ + unsigned char *buf; /* temporary buffer */ + unsigned long buflen; /* current size of temporary buffer */ + time_t scantime; /* last time directory scanned */ +} MAILDIRLOCAL; + +/* Convenient access to local data */ +#define LOCAL ((MAILDIRLOCAL *) stream->local) + +typedef struct maildir_file_info { + char *name; /* name of the file */ + DirNamesType loc; /* location of this file */ + unsigned long pos; /* place in list where this file is listed */ + off_t size; /* size in bytes, on disk */ + time_t atime; /* last access time */ + time_t mtime; /* last modified time */ + time_t ctime; /* last changed time */ +} MAILDIRFILE; + +#define MDFILE(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->name) +#define MDLOC(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->loc) +#define MDPOS(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->pos) +#define MDSIZE(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->size) +#define MDATIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->atime) +#define MDMTIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->mtime) +#define MDCTIME(F) (((MAILDIRFILE *)((F)->private.spare.ptr))->ctime) + +/* Function prototypes */ + +DRIVER *maildir_valid (char *name); +MAILSTREAM *maildir_open (MAILSTREAM *stream); +void maildir_close (MAILSTREAM *stream, long options); +long maildir_ping (MAILSTREAM *stream); +void maildir_check (MAILSTREAM *stream); +long maildir_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags); +char *maildir_header (MAILSTREAM *stream,unsigned long msgno, + unsigned long *length, long flags); +void maildir_list (MAILSTREAM *stream,char *ref,char *pat); +void *maildir_parameters (long function,void *value); +int maildir_create_folder (char *mailbox); +long maildir_create (MAILSTREAM *stream,char *mailbox); +void maildir_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt); /*check */ +long maildir_expunge (MAILSTREAM *stream, char *sequence, long options); +long maildir_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options); +long maildir_append (MAILSTREAM *stream,char *mailbox, append_t af, void *data); +long maildir_delete (MAILSTREAM *stream,char *mailbox); +long maildir_rename (MAILSTREAM *stream,char *old,char *new); +long maildir_sub (MAILSTREAM *stream,char *mailbox); +long maildir_unsub (MAILSTREAM *stream,char *mailbox); +void maildir_lsub (MAILSTREAM *stream,char *ref,char *pat); +void courier_list (MAILSTREAM *stream,char *ref, char *pat); + +/* utility functions */ +void courier_realname (char *name, char *realname); +long maildir_dirfmttest (char *name); +char *maildir_file (char *dst,char *name); +int maildir_select (const struct direct *name); +int maildir_namesort (const struct direct **d1, const struct direct **d2); +unsigned long antoul (char *seed); +unsigned long mdfntoul (char *name); +int courier_dir_select (const struct direct *name); +int courier_dir_sort (const struct direct **d1, const struct direct **d2); +long maildir_canonicalize (char *pattern,char *ref,char *pat); +void maildir_list_work (MAILSTREAM *stream,char *subdir,char *pat,long level); +void courier_list_work (MAILSTREAM *stream,char *subdir,char *pat,long level); +int maildir_file_path(char *name, char *tmp, size_t sizeoftmp); +int maildir_valid_name (char *name); +int maildir_valid_dir (char *name); +int is_valid_maildir (char **name); +int maildir_message_exists(MAILSTREAM *stream,char *name, char *tmp); +char *maildir_remove_root(char *name); +char *maildir_text_work (MAILSTREAM *stream,MESSAGECACHE *elt, unsigned long *length,long flags); +unsigned long maildir_parse_message(MAILSTREAM *stream, unsigned long msgno, + DirNamesType dirtype); +int maildir_eliminate_duplicate (char *name, struct direct ***flist, + unsigned long *nfiles); +int maildir_doscandir (char *name, struct direct ***flist, int flag); +unsigned long maildir_scandir (char *name, struct direct ***flist, + unsigned long *nfiles, int *scand, int flag); +void maildir_parse_folder (MAILSTREAM *stream, int full); +void md_domain_name (void); +char *myrootdir (char *name); +char *mdirpath (void); +int maildir_initial_check (MAILSTREAM *stream, DirNamesType dirtype); +unsigned long maildir_parse_dir(MAILSTREAM *stream, unsigned long nmsgs, + DirNamesType dirtype, struct direct **names, unsigned long nfiles, int full); +int same_maildir_file(char *name1, char *name2); +int comp_maildir_file(char *name1, char *name2); +int maildir_message_in_list(char *msgname, struct direct **names, + unsigned long bottom, unsigned long top, unsigned long *pos); +void maildir_getflag(char *name, int *d, int *f, int *r ,int *s, int *t); +int maildir_update_elt_maildirp(MAILSTREAM *stream, unsigned long msgno); +void maildir_abort (MAILSTREAM *stream); +int maildir_contains_folder(char *dirname, char *name); +int maildir_is_dir(char *dirname, char *name); +int maildir_dir_is_empty(char *mailbox); +int maildir_create_work (char *mailbox, int loop); +void maildir_get_file (MAILDIRFILE **mdfile); +void maildir_free_file (void **mdfile); +void maildir_free_file_only (void **mdfile); +int maildir_any_new_msgs(char *mailbox); +void maildir_get_date(MAILSTREAM *stream, unsigned long msgno); +void maildir_fast (MAILSTREAM *stream,char *sequence,long flags); + +/* Courier server support */ +void courier_free_cdir (COURIER_S **cdir); +COURIER_S *courier_get_cdir (int total); +int courier_search_list(COURIERLOCAL **data, char *name, int first, int last); +COURIER_S *courier_list_dir(char *curdir); +void courier_list_info(COURIER_S **cdirp, char *data, int i); + +/* UID Support */ +int maildir_can_assign_uid (MAILSTREAM *stream); +void maildir_read_uid(MAILSTREAM *stream, unsigned long *uid_last, + unsigned long *uid_validity); +void maildir_write_uid(MAILSTREAM *stream, unsigned long uid_last, + unsigned long uid_validity); +unsigned long maildir_get_uid(char *name); +void maildir_delete_uid(MAILSTREAM *stream, unsigned long msgno); +void maildir_assign_uid(MAILSTREAM *stream, unsigned long msgno, unsigned long uid); +void maildir_uid_renew_tempfile(MAILSTREAM *stream); + diff --git a/imap/src/osdep/unix/os_cyg.h b/imap/src/osdep/unix/os_cyg.h index 061db332..81397225 100644 --- a/imap/src/osdep/unix/os_cyg.h +++ b/imap/src/osdep/unix/os_cyg.h @@ -47,6 +47,7 @@ #define setpgrp setpgid #define SYSTEMUID 18 /* Cygwin returns this for SYSTEM */ +#define FLAGSEP ';' #define geteuid Geteuid uid_t Geteuid (void); diff --git a/pico/basic.c b/pico/basic.c index 49b04bd6..4479d099 100644 --- a/pico/basic.c +++ b/pico/basic.c @@ -26,9 +26,10 @@ static char rcsid[] = "$Id: basic.c 831 2007-11-27 01:04:19Z hubert@u.washington * framing, are hard. */ #include "headers.h" - +#include "../pith/osdep/color.h" #include "osdep/terminal.h" +int indent_match(char **, LINE *, char *, int, int); /* * Move the cursor to the @@ -285,7 +286,7 @@ int gotobop(int f, int n) { int quoted, qlen; - UCS qstr[NLINE], qstr2[NLINE]; + char qstr[NLINE], qstr2[NLINE], ind_str[NLINE], pqstr[NLINE];; if (n < 0) /* the other way...*/ return(gotoeop(f, -n)); @@ -297,6 +298,14 @@ gotobop(int f, int n) curwp->w_dotp = lback(curwp->w_dotp); curwp->w_doto = 0; } + + if (indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp,ind_str, NLINE, 0)){ + if (n){ /* look for another paragraph ? */ + curwp->w_dotp = lback(curwp->w_dotp); + continue; + } + break; + } /* scan line by line until we come to a line ending with * a <NL><NL> or <NL><TAB> or <NL><SPACE> @@ -304,20 +313,58 @@ gotobop(int f, int n) * PLUS: if there's a quote string, a quoted-to-non-quoted * line transition. */ - quoted = glo_quote_str ? quote_match(glo_quote_str, curwp->w_dotp, qstr, NLINE) : 0; - qlen = quoted ? ucs4_strlen(qstr) : 0; + quoted = quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 0); + qlen = quoted ? strlen(qstr) : 0; while(lback(curwp->w_dotp) != curbp->b_linep && llength(lback(curwp->w_dotp)) > qlen - && (glo_quote_str - ? (quoted == quote_match(glo_quote_str, - lback(curwp->w_dotp), - qstr2, NLINE) - && !ucs4_strcmp(qstr, qstr2)) - : 1) - && lgetc(curwp->w_dotp, qlen).c != TAB - && lgetc(curwp->w_dotp, qlen).c != ' ') + && (quoted == quote_match(default_qstr(glo_quote_str, 1), + lback(curwp->w_dotp), qstr2, NLINE, 0)) + && !strcmp(qstr, qstr2) /* processed string */ + && (quoted == quote_match(default_qstr(glo_quote_str, 1), + lback(curwp->w_dotp), qstr2, NLINE, 1)) + && !strcmp(qstr, qstr2) /* raw string */ + && !indent_match(default_qstr(glo_quote_str, 1), + lback(curwp->w_dotp),ind_str, NLINE, 0) + && !ISspace(lgetc(curwp->w_dotp, qlen).c)) curwp->w_dotp = lback(curwp->w_dotp); + /* + * Ok, we made it here and we assume that we are at the begining + * of the paragraph. Let's double check this now. In order to do + * so we shell check if the first line was indented in a special + * way. + */ + if(lback(curwp->w_dotp) == curbp->b_linep) + break; + else{ + int i, j; + + /* + * First we test if the preceding line is indented. + * for the following test we need to have the raw values, + * not the processed values + */ + quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 1); + quote_match(default_qstr(glo_quote_str, 1), lback(curwp->w_dotp), qstr2, NLINE, 1); + for (i = 0, j = 0; + qstr[i] && qstr2[i] && (qstr[i] == qstr2[i]); i++, j++); + for (; ISspace(qstr2[i]); i++); + for (; ISspace(qstr[j]); j++); + if ((indent_match(default_qstr(glo_quote_str, 1), lback(curwp->w_dotp), + ind_str, NLINE, 1) + && (strlenis(qstr2) + + strlenis(ind_str) >= strlenis(qstr))) + || (lback(curwp->w_dotp) != curbp->b_linep + && llength(lback(curwp->w_dotp)) > qlen + && (quoted == quote_match(default_qstr(glo_quote_str, 1), + lback(curwp->w_dotp), pqstr, NLINE, 0)) + && !strcmp(qstr, pqstr) + && !ISspace(lgetc(curwp->w_dotp, qlen).c) + && (strlenis(qstr2) > strlenis(qstr))) + && !qstr2[i] && !qstr[j]) + curwp->w_dotp = lback(curwp->w_dotp); + } + if(n){ /* keep looking */ if(lback(curwp->w_dotp) == curbp->b_linep) @@ -330,7 +377,7 @@ gotobop(int f, int n) else{ /* leave cursor on first word in para */ curwp->w_doto = 0; - while(ucs4_isspace(lgetc(curwp->w_dotp, curwp->w_doto).c)) + while(ISspace(lgetc(curwp->w_dotp, curwp->w_doto).c)) if(++curwp->w_doto >= llength(curwp->w_dotp)){ curwp->w_doto = 0; curwp->w_dotp = lforw(curwp->w_dotp); @@ -344,6 +391,189 @@ gotobop(int f, int n) return(TRUE); } +unsigned char GetAccent() +{ + UCS c,d; + c = GetKey(); + if ((c == '?') || (c == '!')) { + d = c; + c = '\\'; + } + else + if ((c == 's') || (c == 'S')){ + c = d = 's'; + } + else + if ((c == 'l') || (c == 'L')){ + c = d = 'l'; + } + else + d = GetKey(); + return accent(c,d); +} + +int pineaccent(f,n) + int f,n; +{ unsigned char e; + + if (e = GetAccent()) + execute(e, 0, 1); + return 1; +} + +unsigned char accent(f,n) +UCS f,n; +{ UCS c,d; + + c = f; + d = n; + switch(c){ + case '~' : + switch(d){ + case 'a' : return '\343'; + case 'n' : return '\361'; + case 'o' : return '\365'; + case 'A' : return '\303'; + case 'N' : return '\321'; + case 'O' : return '\325'; + } + break; + case '\047' : + switch(d){ + case 'a' : return '\341'; + case 'e' : return '\351'; + case 'i' : return '\355'; + case 'o' : return '\363'; + case 'u' : return '\372'; + case 'y' : return '\375'; + case 'A' : return '\301'; + case 'E' : return '\311'; + case 'I' : return '\315'; + case 'O' : return '\323'; + case 'U' : return '\332'; + case 'Y' : return '\335'; + } + break; + case '"' : + switch(d){ + case 'a' : return '\344'; + case 'e' : return '\353'; + case 'i' : return '\357'; + case 'o' : return '\366'; + case 'u' : return '\374'; + case 'y' : return '\377'; + case 'A' : return '\304'; + case 'E' : return '\313'; + case 'I' : return '\317'; + case 'O' : return '\326'; + case 'U' : return '\334'; + } + break; + case '^' : + switch(d){ + case 'a' : return '\342'; + case 'e' : return '\352'; + case 'i' : return '\356'; + case 'o' : return '\364'; + case 'u' : return '\373'; + case 'A' : return '\302'; + case 'E' : return '\312'; + case 'I' : return '\316'; + case 'O' : return '\324'; + case 'U' : return '\333'; + case '0' : return '\260'; + case '1' : return '\271'; + case '2' : return '\262'; + case '3' : return '\263'; + } + break; + case '`' : + switch(d){ + case 'a' : return '\340'; + case 'e' : return '\350'; + case 'i' : return '\354'; + case 'o' : return '\362'; + case 'u' : return '\371'; + case 'A' : return '\300'; + case 'E' : return '\310'; + case 'I' : return '\314'; + case 'O' : return '\322'; + case 'U' : return '\331'; + } + break; + case 'o' : + switch(d){ + case 'a' : return '\345'; + case 'A' : return '\305'; + case '/' : return '\370'; + case 'r' : return '\256'; + case 'R' : return '\256'; + case 'c' : return '\251'; + case 'C' : return '\251'; + } + break; + case '-' : + switch(d){ + case 'o' : return '\272'; + case 'O' : return '\272'; + case '0' : return '\272'; + case 'a' : return '\252'; + case 'A' : return '\252'; + case 'l' : return '\243'; + case 'L' : return '\243'; + } + break; + case 'O' : + switch(d){ + case '/' : return '\330'; + case 'r' : return '\256'; + case 'R' : return '\256'; + case 'c' : return '\251'; + case 'C' : return '\251'; + } + case '/' : + switch(d){ + case 'o' : return '\370'; + case 'O' : return '\330'; + } + break; + case 'a' : + switch(d){ + case 'e' : return '\346'; + case 'E' : return '\346'; + } + break; + case 'A' : + switch(d){ + case 'E' : return '\306'; + case 'e' : return '\306'; + } + break; + case ',' : + switch(d){ + case 'c' : return '\347'; + case 'C' : return '\307'; + } + break; + case '\\' : + switch(d){ + case '?' : return '\277'; + case '!' : return '\241'; + } + break; + case 's' : + switch(d){ + case 's' : return '\337'; + } + break; + case 'l' : + switch(d){ + case 'l' : return '\243'; + } + break; + } + return '\0'; +} /* * go forword to the end of the current paragraph @@ -353,8 +583,9 @@ gotobop(int f, int n) int gotoeop(int f, int n) { - int quoted, qlen; - UCS qstr[NLINE], qstr2[NLINE]; + int quoted, qlen, indented, changeqstr = 0; + int i,j, fli = 0; /* fli = first line indented a boolean variable */ + char qstr[NLINE], qstr2[NLINE], ind_str[NLINE]; if (n < 0) /* the other way...*/ return(gotobop(f, -n)); @@ -367,27 +598,70 @@ gotoeop(int f, int n) break; } + /* + * We need to figure out if this line is the first line of + * a paragraph that has been indented in a special way. If this + * is the case, we advance one more line before we use the + * algorithm below + */ + + if(curwp->w_dotp != curbp->b_linep){ + quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, 1); + quote_match(default_qstr(glo_quote_str, 1), lforw(curwp->w_dotp), qstr2, NLINE, 1); + indented = indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, ind_str, + NLINE, 1); + if (strlenis(qstr) + + strlenis(ind_str) < strlenis(qstr2)){ + curwp->w_doto = llength(curwp->w_dotp); + if(n){ /* this line is a paragraph by itself */ + curwp->w_dotp = lforw(curwp->w_dotp); + continue; + } + break; + } + for (i=0,j=0; qstr[i] && qstr2[i] && (qstr[i] == qstr2[i]);i++,j++); + for (; ISspace(qstr[i]); i++); + for (; ISspace(qstr2[j]); j++); + if (!qstr[i] && !qstr2[j] && indented){ + fli++; + if (indent_match(default_qstr(glo_quote_str, 1), lforw(curwp->w_dotp), + ind_str, NLINE, 0)){ + if (n){ /* look for another paragraph ? */ + curwp->w_dotp = lforw(curwp->w_dotp); + continue; + } + } + else{ + if (!lisblank(lforw(curwp->w_dotp))) + curwp->w_dotp = lforw(curwp->w_dotp); + } + } + } + /* scan line by line until we come to a line ending with * a <NL><NL> or <NL><TAB> or <NL><SPACE> * * PLUS: if there's a quote string, a quoted-to-non-quoted * line transition. */ - quoted = glo_quote_str - ? quote_match(glo_quote_str, - curwp->w_dotp, qstr, NLINE) : 0; - qlen = quoted ? ucs4_strlen(qstr) : 0; + /* if the first line is indented (fli == 1), then the test below + is on the second line, and in that case we will need the raw + string, not the processed string + */ + quoted = quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstr, NLINE, fli); + qlen = quoted ? strlen(qstr) : 0; while(curwp->w_dotp != curbp->b_linep && llength(lforw(curwp->w_dotp)) > qlen - && (glo_quote_str - ? (quoted == quote_match(glo_quote_str, - lforw(curwp->w_dotp), - qstr2, NLINE) - && !ucs4_strcmp(qstr, qstr2)) - : 1) - && lgetc(lforw(curwp->w_dotp), qlen).c != TAB - && lgetc(lforw(curwp->w_dotp), qlen).c != ' ') + && (quoted == quote_match(default_qstr(glo_quote_str, 1), + lforw(curwp->w_dotp), qstr2, NLINE, fli)) + && !strcmp(qstr, qstr2) + && (quoted == quote_match(default_qstr(glo_quote_str, 1), + lforw(curwp->w_dotp), qstr2, NLINE, 1)) + && !strcmp(qstr, qstr2) + && !indent_match(default_qstr(glo_quote_str, 1), + lforw(curwp->w_dotp), ind_str, NLINE, 0) + && !ISspace(lgetc(lforw(curwp->w_dotp), qlen).c)) curwp->w_dotp = lforw(curwp->w_dotp); curwp->w_doto = llength(curwp->w_dotp); @@ -684,7 +958,57 @@ scrolldownline(int f, int n) return (scrollforw (1, FALSE)); } +/* deltext deletes from the specified position until the end of the file + * or until the signature (when called from Pine), whichever comes first. + */ +int +deltext (f,n) +int f,n; +{ + LINE *currline = curwp->w_dotp; + static int firsttime = 0; + + if ((lastflag&CFKILL) == 0) + kdelete(); + + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = curwp->w_doto; + + while (curwp->w_dotp != curbp->b_linep){ + if ((Pmaster) + && (llength(curwp->w_dotp) == 3) + && (lgetc(curwp->w_dotp, 0).c == '-') + && (lgetc(curwp->w_dotp, 1).c == '-') + && (lgetc(curwp->w_dotp, 2).c == ' ')){ + if (curwp->w_dotp == currline){ + if (curwp->w_doto) + curwp->w_dotp = lforw(curwp->w_dotp); + else + break; + } + else{ + curwp->w_dotp = lback(curwp->w_dotp); + curwp->w_doto = llength(curwp->w_dotp); + break; + } + } + else{ + if(lforw(curwp->w_dotp) != curbp->b_linep) + curwp->w_dotp = lforw(curwp->w_dotp); + else{ + curwp->w_doto = llength(curwp->w_dotp); + break; + } + } + } + killregion(FALSE,1); + lastflag |= CFKILL; + if(firsttime == 0) + emlwrite("Deleted text can be recovered with the ^U command", NULL); + firsttime = 1; + return TRUE; +} /* * Scroll to a position. diff --git a/pico/blddate.c b/pico/blddate.c index 00f0b558..5797c3c5 100644 --- a/pico/blddate.c +++ b/pico/blddate.c @@ -19,7 +19,7 @@ main(argc, argv) char **argv; { struct tm *t; - FILE *outfile=stdout; + FILE *outfile=stdout, *infile; time_t ltime; if(argc > 1 && (outfile = fopen(argv[1], "w")) == NULL){ @@ -46,6 +46,12 @@ main(argc, argv) 1900 + t->tm_year); fprintf(outfile, "char hoststamp[]=\"random-pc\";\n"); + if((infile = fopen("../patchlevel", "r")) != NULL){ + int c; + while ((c = getc(infile)) != EOF) putc(c, outfile); + fclose(infile); + } + else fprintf(outfile, "char plevstamp[]=\"No information available\";\n"); fclose(outfile); diff --git a/pico/composer.c b/pico/composer.c index c801d385..e709cd67 100644 --- a/pico/composer.c +++ b/pico/composer.c @@ -1872,6 +1872,8 @@ AppendAttachment(char *fn, char *sz, char *cmt) } UpdateHeader(0); + if(sendnow) + return(status !=0); PaintHeader(COMPOSER_TOP_LINE, status != 0); PaintBody(1); return(status != 0); @@ -2015,7 +2017,7 @@ LineEdit(int allowedit, UCS *lastch) tbufp = &strng[ods.p_len]; if(VALID_KEY(ch)){ /* char input */ - /* +insert_char:/* * if we are allowing editing, insert the new char * end up leaving tbufp pointing to newly * inserted character in string, and offset to the @@ -2095,6 +2097,13 @@ LineEdit(int allowedit, UCS *lastch) } else { /* interpret ch as a command */ switch (ch = normalize_cmd(ch, ckm, 2)) { + case (CTRL|'\\') : + if (ch = GetAccent()) + goto insert_char; + else + clearcursor(); + break; + case (CTRL|KEY_LEFT): /* word skip left */ if(ods.p_ind > 0) /* Scoot one char left if possible */ ods.p_ind--; @@ -3362,6 +3371,9 @@ display_delimiter(int state) { UCS *bufp, *buf; + if (sendnow) + return; + if(ComposerTopLine - 1 >= BOTTOM()) /* silently forget it */ return; @@ -3418,6 +3430,9 @@ InvertPrompt(int entry, int state) UCS *end; int i; + if (sendnow) + return(TRUE); + buf = utf8_to_ucs4_cpystr(headents[entry].prompt); /* fresh prompt paint */ if(!buf) return(-1); @@ -4373,6 +4388,9 @@ is_blank(int row, int col, int n) void ShowPrompt(void) { + if (sendnow) + return; + if(headents[ods.cur_e].key_label){ menu_header[TO_KEY].name = "^T"; menu_header[TO_KEY].label = headents[ods.cur_e].key_label; diff --git a/pico/display.c b/pico/display.c index 2741be3e..6923e156 100644 --- a/pico/display.c +++ b/pico/display.c @@ -387,6 +387,9 @@ update(void) int scroll = 0; CELL c; + if (sendnow) + return; + #if TYPEAH if (typahead()) return; @@ -916,7 +919,7 @@ updateline(int row, /* row on screen */ int nbflag; /* non-blanks to the right flag? */ int cleartoeol = 0; - if(row < 0 || row > term.t_nrow) + if(row < 0 || row > term.t_nrow || sendnow) return; /* set up pointers to virtual and physical lines */ @@ -1285,7 +1288,7 @@ get_cursor(int *row, int *col) void mlerase(void) { - if (term.t_nrow < term.t_mrow) + if (term.t_nrow < term.t_mrow || sendnow) return; movecursor(term.t_nrow - term.t_mrow, 0); @@ -1360,6 +1363,10 @@ mlyesno(UCS *prompt, int dflt) menu_yesno[6].label = N_("Cancel"); menu_yesno[7].name = "N"; menu_yesno[7].label = (dflt == FALSE) ? "[" N_("No") "]" : N_("No"); + if(Pmaster && Pmaster->onctrlc){ + menu_yesno[8].name = "T"; + menu_yesno[8].label = N_("counT"); + } wkeyhelp(menu_yesno); /* paint generic menu */ sgarbk = TRUE; /* mark menu dirty */ if(Pmaster && curwp) @@ -1438,6 +1445,14 @@ mlyesno(UCS *prompt, int dflt) km_popped++; break; } + + case 'T': + case 't': + if(Pmaster && Pmaster->onctrlc){ + pputs_utf8(_("counT"), 1); + rv = COUNT; + break; + } /* else fall through */ default: @@ -1751,6 +1766,11 @@ mlreplyd(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) b = &buf[ucs4_strlen(buf)]; continue; + case (CTRL|'\\'): + if (c = GetAccent()) + goto text; + continue; + case (CTRL|'F') : /* CTRL-F forward a char*/ case KEY_RIGHT : if(*b == '\0') @@ -1760,6 +1780,18 @@ mlreplyd(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) continue; + case (CTRL|'N'): /* Insert pattern */ + if (pat[0] != '\0'){ + ucs4_strncpy(buf+ucs4_strlen(buf), pat, NPAT); + pputs(pat,1); + b = &buf[ucs4_strlen(buf)]; + dline.vused += ucs4_strlen(pat); + changed = TRUE; + } + else + (*term.t_beep)(); + continue; + case (CTRL|'G') : /* CTRL-G help */ if(term.t_mrow == 0 && km_popped == 0){ movecursor(term.t_nrow-2, 0); @@ -1869,7 +1901,7 @@ mlreplyd(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) #endif default : - +text: /* look for match in extra_v */ for(i = 0; i < 12; i++) if(c && c == extra_v[i]){ @@ -1963,7 +1995,7 @@ emlwrite_ucs4(UCS *message, EML *eml) mlerase(); - if(!(message && *message) || term.t_nrow < 2) + if(!(message && *message) || term.t_nrow < 2 || sendnow) return; /* nothing to write or no space to write, bag it */ bufp = message; @@ -2152,8 +2184,9 @@ mlwrite(UCS *fmt, void *arg) } ret = ttcol; - while(ttcol < term.t_ncol) - pputc(' ', 0); + if(sendnow == 0) + while(ttcol < term.t_ncol) + pputc(' ', 0); movecursor(term.t_nrow - term.t_mrow, ret); @@ -2632,6 +2665,8 @@ pputc(UCS c, /* char to write */ { int ind, width, printable_ascii = 0; + if(sendnow) + return; /* * This is necessary but not sufficient to allow us to draw. Note that * ttrow runs from 0 to t_nrow (so total number of rows is t_nrow+1) @@ -2686,6 +2721,8 @@ void pputs(UCS *s, /* string to write */ int a) /* and its attribute */ { + if(sendnow) + return; while (*s != '\0') pputc(*s++, a); } @@ -2696,6 +2733,8 @@ pputs_utf8(char *s, int a) { UCS *ucsstr = NULL; + if(sendnow) + return; if(s && *s){ ucsstr = utf8_to_ucs4_cpystr(s); if(ucsstr){ @@ -2996,6 +3035,9 @@ wkeyhelp(KEYMENU *keymenu) char nbuf[NLINE]; #endif + if(sendnow) + return; + #ifdef _WINDOWS pico_config_menu_items (keymenu); #endif diff --git a/pico/ebind.h b/pico/ebind.h index 4f1687b4..f8cbbbc3 100644 --- a/pico/ebind.h +++ b/pico/ebind.h @@ -61,7 +61,7 @@ KEYTAB keytab[NBINDS] = { #ifdef MOUSE {KEY_MOUSE, mousepress}, #ifndef _WINDOWS - {CTRL|'\\', toggle_xterm_mouse}, + {CTRL|'|', toggle_xterm_mouse}, #endif #endif {CTRL|'A', gotobol}, @@ -100,7 +100,9 @@ KEYTAB keytab[NBINDS] = { {CTRL|KEY_HOME, gotobob}, {CTRL|KEY_END, gotoeob}, {0x7F, backdel}, - {0, NULL} + {CTRL|'\\', pineaccent}, + {0, +NULL} }; @@ -123,7 +125,7 @@ KEYTAB pkeytab[NBINDS] = { #ifdef MOUSE {KEY_MOUSE, mousepress}, #ifndef _WINDOWS - {CTRL|'\\', toggle_xterm_mouse}, + {CTRL|'|', toggle_xterm_mouse}, #endif #endif {CTRL|'A', gotobol}, diff --git a/pico/edef.h b/pico/edef.h index 209a4600..bea0ed1e 100644 --- a/pico/edef.h +++ b/pico/edef.h @@ -32,6 +32,7 @@ /* initialized global definitions */ +int sendnow = 0; /* should we send now */ int fillcol = 72; /* Current fill column */ int userfillcol = -1; /* Fillcol set from cmd line */ UCS pat[NPAT]; /* Search pattern */ @@ -84,6 +85,7 @@ void *input_cs; /* passed to mbtow() via kbseq() */ /* initialized global external declarations */ +extern int sendnow; /* should we send now */ extern int fillcol; /* Fill column */ extern int userfillcol; /* Fillcol set from cmd line */ extern UCS pat[]; /* Search pattern */ diff --git a/pico/efunc.h b/pico/efunc.h index b551dfc7..52b2bcc5 100644 --- a/pico/efunc.h +++ b/pico/efunc.h @@ -54,8 +54,12 @@ extern int forwline(int, int); extern int backline(int, int); extern int gotobop(int, int); extern int gotoeop(int, int); +extern int pineaccent(int, int); +extern unsigned char accent(UCS, UCS); +extern unsigned char GetAccent(void); extern int forwpage(int, int); extern int backpage(int, int); +extern int deltext (int, int); extern int scrollupline(int, int); extern int scrolldownline(int, int); extern int scrollto(int, int); @@ -249,10 +253,16 @@ extern int forwword(int, int); extern int fillpara(int, int); extern int fillbuf(int, int); extern int inword(void); -extern int quote_match(UCS *, LINE *, UCS *, size_t); +extern int quote_match(char **, LINE *, char *, size_t, int); +extern void flatten_qstring(QSTRING_S *, char *, int); +extern void free_qs(QSTRING_S **); +extern QSTRING_S *do_quote_match (char **, char *, char *, char *, char *, int, int); +extern QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **); +extern int indent_match(char **, LINE *, char *, int, int); extern int ucs4_isalnum(UCS); extern int ucs4_isalpha(UCS); extern int ucs4_isspace(UCS); extern int ucs4_ispunct(UCS); #endif /* EFUNC_H */ + diff --git a/pico/fileio.c b/pico/fileio.c index 5cf124c0..2b598e0c 100644 --- a/pico/fileio.c +++ b/pico/fileio.c @@ -95,6 +95,7 @@ ffgetline(UCS buf[], size_t nbuf, size_t *charsreturned, int msg) { size_t i; UCS ucs; + static int shown = 0; if(charsreturned) *charsreturned = 0; @@ -121,8 +122,10 @@ ffgetline(UCS buf[], size_t nbuf, size_t *charsreturned, int msg) if(charsreturned) *charsreturned = nbuf - 1; - if(msg) + if(msg && !shown){ + shown = 1; emlwrite("File has long line", NULL); + } return FIOLNG; } @@ -131,6 +134,7 @@ ffgetline(UCS buf[], size_t nbuf, size_t *charsreturned, int msg) } if(ucs == CCONV_EOF){ + shown = 0; /* warn the next time, again, only once */ if(ferror(g_pico_fio.fp)){ emlwrite("File read error", NULL); if(charsreturned) diff --git a/pico/line.c b/pico/line.c index e5df3670..fe6847d3 100644 --- a/pico/line.c +++ b/pico/line.c @@ -608,14 +608,12 @@ int lisblank(LINE *line) { int n = 0; - UCS qstr[NLINE]; + char qstr[NLINE]; - n = (glo_quote_str - && quote_match(glo_quote_str, line, qstr, NLINE)) - ? ucs4_strlen(qstr) : 0; + n = quote_match(default_qstr(glo_quote_str, 1), line, qstr, NLINE, 1); for(; n < llength(line); n++) - if(!ucs4_isspace(lgetc(line, n).c)) + if(!ISspace(lgetc(line, n).c)) return(FALSE); return(TRUE); diff --git a/pico/main.c b/pico/main.c index ac5bc388..3036b69a 100644 --- a/pico/main.c +++ b/pico/main.c @@ -163,6 +163,7 @@ main(int argc, char *argv[]) char *file_to_edit = NULL; char *display_charmap = NULL, *dc; char *keyboard_charmap = NULL; + int line_information_on = FALSE; int use_system = 0; char *err = NULL; @@ -416,6 +417,12 @@ main(int argc, char *argv[]) emlwrite(_("You may possibly have new mail."), NULL); } + if (c == (CTRL|'\\')){ + c = GetAccent(); + if (!c) + c = NODATA; + } + if(km_popped) switch(c){ case NODATA: @@ -437,14 +444,29 @@ main(int argc, char *argv[]) mlerase(); } - f = FALSE; + f = (c == (CTRL|'J')); n = 1; + if (!line_information_on) + line_information_on = (c == (CTRL|'C')); + else + line_information_on = ((c == KEY_DOWN) || (c == KEY_UP) || + (c == KEY_RIGHT) || (c == KEY_LEFT) || + (c == (CTRL|'V')) || (c == (CTRL|'Y')) || + (c == (CTRL|'D')) || (c == (CTRL|'F')) || + (c == (CTRL|'B')) || (c == (CTRL|'N')) || + (c == (CTRL|'P')) || (c == (CTRL|'A')) || + (c == (CTRL|'E')) || (c == (CTRL|'U'))) + && (c != (CTRL|'C')); #ifdef MOUSE clear_mfunc(mouse_in_content); #endif /* Do it. */ execute(normalize_cmd(c, fkm, 1), f, n); + if (line_information_on){ + c = (CTRL|'C'); + execute(normalize_cmd(c, fkm, 1), f, n); + } } } diff --git a/pico/osdep/color.h b/pico/osdep/color.h index 0dced80c..dce05118 100644 --- a/pico/osdep/color.h +++ b/pico/osdep/color.h @@ -33,6 +33,10 @@ void pico_endcolor(void); void pico_toggle_color(int); void pico_set_nfg_color(void); void pico_set_nbg_color(void); +char **default_qstr(void *, int); +void add_allowed_qstr(void *, int); +void free_allowed_qstr(void); +void record_quote_string (QSTRING_S *); #endif /* PICO_OSDEP_COLOR_INCLUDED */ diff --git a/pico/osdep/getkey.c b/pico/osdep/getkey.c index b55dd092..827b7da0 100644 --- a/pico/osdep/getkey.c +++ b/pico/osdep/getkey.c @@ -131,6 +131,16 @@ GetKey(void) { UCS ch, status, cc; + if(sendnow){ + ch = Pmaster && Pmaster->auto_cmds && *Pmaster->auto_cmds + ? *Pmaster->auto_cmds++ : NODATA; + + if (ch >= 0x00 && ch <= 0x1F) + ch = CTRL | (ch+'@'); + + return(ch); + } + if(!ReadyForKey(FUDGE-5)) return(NODATA); diff --git a/pico/osdep/terminal.c b/pico/osdep/terminal.c index 72206c01..eb24d90e 100644 --- a/pico/osdep/terminal.c +++ b/pico/osdep/terminal.c @@ -26,6 +26,7 @@ static char rcsid[] = "$Id: terminal.c 921 2008-01-31 02:09:25Z hubert@u.washing #include "../keydefs.h" #include "../pico.h" #include "../mode.h" +#include "../edef.h" #include "raw.h" #include "color.h" @@ -478,6 +479,12 @@ tinfoopen(void) { int row, col; + if (sendnow){ + term.t_nrow = 23; + term.t_ncol = 80; + return 0; + } + /* * determine the terminal's communication speed and decide * if we need to do optimization ... @@ -1253,6 +1260,12 @@ tcapopen(void) { int row, col; + if (sendnow){ + term.t_nrow = 23; + term.t_ncol = 80; + return 0; + } + /* * determine the terminal's communication speed and decide * if we need to do optimization ... diff --git a/pico/pico.c b/pico/pico.c index 166a3d3d..0a47d7f9 100644 --- a/pico/pico.c +++ b/pico/pico.c @@ -138,6 +138,15 @@ pico(PICO *pm) pico_all_done = 0; km_popped = 0; + if (pm->auto_cmds){ + int i; +#define CTRL_X 24 + for (i = 0; pm->auto_cmds[i]; i++); + if ((i > 1) && (pm->auto_cmds[i - 2] == CTRL_X) && + ((pm->auto_cmds[i - 1] == 'y') || (pm->auto_cmds[i-1] == 'Y'))) + sendnow++; + } + if(!vtinit()) /* Init Displays. */ return(COMP_CANCEL); @@ -638,12 +647,19 @@ abort_composer(int f, int n) result = ""; Pmaster->arm_winch_cleanup++; + Pmaster->onctrlc++; if(Pmaster->canceltest){ if(((Pmaster->pine_flags & MDHDRONLY) && !any_header_changes()) || (result = (*Pmaster->canceltest)(redraw_pico_for_callback))){ - pico_all_done = COMP_CANCEL; emlwrite(result, NULL); Pmaster->arm_winch_cleanup--; + if(Pmaster->curpos[0]){ + curwp->w_flag |= WFMODE; /* and modeline so we */ + sgarbk = TRUE; /* redraw the keymenu */ + pclear(term.t_nrow - 1, term.t_nrow + 1); + return(FALSE); + } + pico_all_done = COMP_CANCEL; return(TRUE); } else{ @@ -672,6 +688,12 @@ abort_composer(int f, int n) emlwrite(_("\007Cancel Cancelled"), NULL); break; + case COUNT: + showcpos(1,0); + emlwrite(Pmaster->curpos, NULL); + Pmaster->onctrlc--; + break; + default: mlerase(); } @@ -714,6 +736,19 @@ wquit(int f, int n) return(FALSE); } + /* When we send a message using the command line we are going to + ignore if the user wants to spell check, we assume he already + did */ + if (sendnow){ + ret = (*Pmaster->exittest)(Pmaster->headents, + redraw_pico_for_callback, + Pmaster->allow_flowed_text, + &result); + if (!ret) + pico_all_done = COMP_EXIT; + return(result ? FALSE : TRUE); + } + #ifdef SPELLER if(Pmaster->always_spell_check) if(spell(0, 0) == -1) diff --git a/pico/pico.h b/pico/pico.h index cf25abea..51bc26ff 100644 --- a/pico/pico.h +++ b/pico/pico.h @@ -199,11 +199,13 @@ typedef struct pico_struct { PCOLORS *colors; /* colors for titlebar and keymenu */ void *input_cs; /* passed to mbtow() via kbseq() */ long pine_flags; /* entry mode flags */ + char curpos[80]; /* where are we now? */ /* The next few bits are features that don't fit in pine_flags */ /* If we had this to do over, it would probably be one giant bitmap */ unsigned always_spell_check:1; /* always spell-checking upon quit */ unsigned strip_ws_before_send:1; /* don't default strip bc of flowed */ unsigned allow_flowed_text:1; /* clean text when done to keep flowed */ + unsigned onctrlc; /* are we on ctrl-c command? */ int (*helper)(); /* Pine's help function */ int (*showmsg)(); /* Pine's display_message */ UCS (*suspend)(); /* Pine's suspend */ @@ -222,6 +224,7 @@ typedef struct pico_struct { void (*winch_cleanup)(); /* callback handling screen resize */ void (*newthread)(); /* callback to create new thread */ int arm_winch_cleanup; /* do the winch_cleanup if resized */ + int *auto_cmds; /* Initial keystroke commands */ HELP_T search_help; HELP_T ins_help; HELP_T ins_m_help; diff --git a/pico/random.c b/pico/random.c index 780c083e..4b52bcac 100644 --- a/pico/random.c +++ b/pico/random.c @@ -74,7 +74,10 @@ showcpos(int f, int n) thisline+1, lines+1, (int)((100L*(thisline+1))/(lines+1)), nbc, nch, (nch) ? (int)((100L*nbc)/nch) : 0); - emlwrite(buffer, NULL); + if(Pmaster) + strcpy(Pmaster->curpos, buffer); + else + emlwrite(buffer, NULL); return (TRUE); } diff --git a/pico/search.c b/pico/search.c index 0886b24c..70829082 100644 --- a/pico/search.c +++ b/pico/search.c @@ -36,7 +36,7 @@ int srpat(char *, UCS *, size_t, int); int readpattern(char *, int); int replace_pat(UCS *, int *); int replace_all(UCS *, UCS *); - +int deletepara(int, int); #define FWS_RETURN(RV) { \ thisflag |= CFSRCH; \ @@ -76,6 +76,10 @@ N_(" brackets. This string is the default search prompt."), N_("~ Hitting only ~R~e~t~u~r~n or at the prompt will cause the"), N_(" search to be made with the default value."), " ", +N_("~ Hitting ~^~N will reinsert the last string you searched for"), +N_(" so that you can edit it (in case you made a mistake entering the"), +N_(" search pattern the first time)."), +" ", N_(" The text search is not case sensitive, and will examine the"), N_(" entire message."), " ", @@ -232,10 +236,19 @@ forwsearch(int f, int n) mlerase(); FWS_RETURN(TRUE); + case (CTRL|'P'): + deletepara(0, 1); + mlerase(); + FWS_RETURN(TRUE); + case (CTRL|'R'): /* toggle replacement option */ repl_mode = !repl_mode; break; + case (CTRL|'X'): + deltext(f,n); + FWS_RETURN(TRUE); + default: if(status == ABORT) emlwrite(_("Search Cancelled"), NULL); @@ -274,7 +287,7 @@ forwsearch(int f, int n) } if(status + curwp->w_doto >= llength(curwp->w_dotp) || - !eq(defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c)) + !eq((unsigned char)defpat[status],lgetc(curwp->w_dotp, curwp->w_doto + status).c)) break; /* do nothing! */ status++; } @@ -600,7 +613,7 @@ srpat(char *utf8prompt, UCS *defpat, size_t defpatlen, int repl_mode) UCS *b; UCS prompt[NPMT]; UCS *promptp; - EXTRAKEYS menu_pat[8]; + EXTRAKEYS menu_pat[10]; menu_pat[i = 0].name = "^Y"; menu_pat[i].label = N_("FirstLine"); @@ -618,6 +631,11 @@ srpat(char *utf8prompt, UCS *defpat, size_t defpatlen, int repl_mode) KS_OSDATASET(&menu_pat[i], KS_NONE); if(!repl_mode){ + menu_pat[++i].name = "^X"; + menu_pat[i].label = N_("DelEnd"); + menu_pat[i].key = (CTRL|'X'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + menu_pat[++i].name = "^T"; menu_pat[i].label = N_("LineNumber"); menu_pat[i].key = (CTRL|'T'); @@ -634,6 +652,11 @@ srpat(char *utf8prompt, UCS *defpat, size_t defpatlen, int repl_mode) menu_pat[i].key = (CTRL|'O'); KS_OSDATASET(&menu_pat[i], KS_NONE); + menu_pat[++i].name = "^P"; + menu_pat[i].label = N_("Delete Para"); + menu_pat[i].key = (CTRL|'P'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + menu_pat[++i].name = "^U"; /* TRANSLATORS: Instead of justifying (formatting) just a single paragraph, Full Justify justifies the entire @@ -769,7 +792,7 @@ readpattern(char *utf8prompt, int text_mode) UCS *b; UCS tpat[NPAT+20]; UCS *tpatp; - EXTRAKEYS menu_pat[7]; + EXTRAKEYS menu_pat[9]; menu_pat[i = 0].name = "^Y"; menu_pat[i].label = N_("FirstLine"); @@ -782,6 +805,11 @@ readpattern(char *utf8prompt, int text_mode) KS_OSDATASET(&menu_pat[i], KS_NONE); if(text_mode){ + menu_pat[++i].name = "^X"; + menu_pat[i].label = N_("DelEnd"); + menu_pat[i].key = (CTRL|'X'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + menu_pat[++i].name = "^T"; menu_pat[i].label = N_("LineNumber"); menu_pat[i].key = (CTRL|'T'); @@ -797,6 +825,11 @@ readpattern(char *utf8prompt, int text_mode) menu_pat[i].key = (CTRL|'O'); KS_OSDATASET(&menu_pat[i], KS_NONE); + menu_pat[++i].name = "^P"; + menu_pat[i].label = N_("Delete Para"); + menu_pat[i].key = (CTRL|'P'); + KS_OSDATASET(&menu_pat[i], KS_NONE); + menu_pat[++i].name = "^U"; menu_pat[i].label = N_("FullJustify"); menu_pat[i].key = (CTRL|'U'); @@ -927,7 +960,7 @@ forscan(int *wrapt, /* boolean indicating search wrapped */ c = lgetc(curline, curoff++).c; /* get the char */ /* test it against first char in pattern */ - if (eq(c, patrn[0]) != FALSE) { /* if we find it..*/ + if (eq(c, (unsigned char)patrn[0]) != FALSE) { /* if we find it..*/ /* setup match pointers */ matchline = curline; matchoff = curoff; @@ -948,7 +981,7 @@ forscan(int *wrapt, /* boolean indicating search wrapped */ return(FALSE); /* and test it against the pattern */ - if (eq(*patptr, c) == FALSE) + if (eq((unsigned char) *patptr, c) == FALSE) goto fail; } @@ -1035,3 +1068,25 @@ chword(UCS *wb, UCS *cb) curwp->w_flag |= WFEDIT; } + +int +deletepara(int f, int n) /* Delete the current paragraph */ +{ + if(curbp->b_mode&MDVIEW) /* don't allow this command if */ + return(rdonly()); /* we are in read only mode */ + + if(!lisblank(curwp->w_dotp)) + gotobop(FALSE, 1); + + curwp->w_markp = curwp->w_dotp; + curwp->w_marko = 0; + + gotoeop(FALSE, 1); + if (curwp->w_dotp != curbp->b_linep){ /* if we are not at the end of buffer */ + curwp->w_dotp = lforw(curwp->w_dotp); /* get one more line */ + curwp->w_doto = 0; /* but only the beginning */ + } + killregion(f,n); + return(TRUE); +} + diff --git a/pico/word.c b/pico/word.c index 145fc1d1..62fa84ea 100644 --- a/pico/word.c +++ b/pico/word.c @@ -25,10 +25,10 @@ static char rcsid[] = "$Id: word.c 769 2007-10-24 00:15:40Z hubert@u.washington. */ #include "headers.h" - +#include "../pith/osdep/color.h" int fpnewline(UCS *quote); -int fillregion(UCS *qstr, REGION *addedregion); +int fillregion(UCS *qstr, UCS *istr, REGION *addedregion); int setquotelevelinregion(int quotelevel, REGION *addedregion); int is_user_separator(UCS c); @@ -431,42 +431,156 @@ is_user_separator(UCS c) return 0; } +/* Support of indentation of paragraphs */ +#define is_indent_char(c) (((c) == '.' || (c) == '}' || (c) == RPAREN || \ + (c) == '*' || (c) == '+' || is_a_digit(c) || \ + ISspace(c) || (c) == '-' || \ + (c) == ']') ? 1 : 0) +#define allowed_after_digit(c,word,k) ((((c) == '.' && \ + allowed_after_period(next((word),(k)))) ||\ + (c) == RPAREN || (c) == '}' || (c) == ']' ||\ + ISspace(c) || is_a_digit(c) || \ + ((c) == '-' ) && \ + allowed_after_dash(next((word),(k)))) \ + ? 1 : 0) +#define allowed_after_period(c) (((c) == RPAREN || (c) == '}' || (c) == ']' ||\ + ISspace(c) || (c) == '-' || \ + is_a_digit(c)) ? 1 : 0) +#define allowed_after_parenth(c) (ISspace(c) ? 1 : 0) +#define allowed_after_space(c) (ISspace(c) ? 1 : 0) +#define allowed_after_braces(c) (ISspace(c) ? 1 : 0) +#define allowed_after_star(c) ((ISspace(c) || (c) == RPAREN ||\ + (c) == ']' || (c) == '}') ? 1 : 0) +#define allowed_after_dash(c) ((ISspace(c) || is_a_digit(c)) ? 1 : 0) +#define EOLchar(c) (((c) == '.' || (c) == ':' || (c) == '?' ||\ + (c) == '!') ? 1 : 0) + +int indent_match(char **, LINE *, char *, int, int); + +/* Extended justification support */ +#define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':') +#define is_cword(c) ((((c) >= 'a') && ((c) <= 'z')) || \ + (((c) >= 'A') && ((c) <= 'Z')) || \ + (((c) >= '0') && ((c) <= '9')) || \ + ((c) == ' ') || ((c) == '?') || \ + ((c) == '@') || ((c) == '.') || \ + ((c) == '!') || ((c) == '\'') || \ + ((c) == ',') || ((c) == '\"') ? 1 : 0) +#define isaquote(c) ((c) == '\"' || (c) == '\'') +#define is8bit(c) ((((int) (c)) & 0x80) ? 1 : 0) +#define iscontrol(c) (iscntrl(((int) (c)) & 0x7f) ? 1 : 0) +#define forbidden(c) (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\ + ((c) == ',') || ((c) == '.') || ((c) == '-') ||\ + ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\ + ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\ + (((c) >= '0') && ((c) <= '9')) || ((c) == '?')) +#define is_cletter(c) ((((c) >= 'a') && ((c) <= 'z'))) ||\ + ((((c) >= 'A') && ((c) <= 'Z'))||\ + is8bit(c)) +#define is_cnumber(c) ((c) >= '0' && (c) <= '9') +#define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c)) +#define allwd_after_qsword(c) (((c) != '\\') && ((c) != RPAREN)) +#define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0) +#define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0) +#define now(w,i) ((w)[(i)]) +#define is_qsword(c) (((c) == ':') || ((c) == RPAREN) ? 1 : 0) +#define is_colon(c) (((c) == ':') ? 1 : 0) +#define is_rarrow(c) (((c) == '>') ? 1 : 0) +#define is_tilde(c) (((c) == '~') ? 1 : 0) +#define is_dash(c) (((c) == '-') ? 1 : 0) +#define is_pound(c) (((c) == '#') ? 1 : 0) +#define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0) +#define is_allowed(c) (is_cquote(c) || is_cword(c) || is_dash(c) || \ + is_pound(c)) +#define qs_allowed(a) (((a)->qstype != qsGdb) && ((a)->qstype != qsProg)) + +/* Internal justification functions */ +QSTRING_S *qs_quote_match(char **, LINE *, char *, int); +int ucs4_strlenis(UCS *); +void linencpy(char *, LINE *, int); + +void +linencpy(word, l, buflen) + char word[NSTRING]; + LINE *l; + int buflen; +{ + int i; + UCS ucs_word[NSTRING]; + char *utf_word; + + word[0] = '\0'; + if(l){ + for (i = 0; i < buflen && i < llength(l) + && (ucs_word[i] = lgetc(l,i).c); i++); + ucs_word[i == buflen ? i-1 : i] = '\0'; + utf_word = ucs4_to_utf8_cpystr(ucs_word); + strncpy(word, utf_word, (NSTRING < buflen ? NSTRING : buflen)); + word[NSTRING-1] = '\0'; + if(utf_word) fs_give((void **)&utf_word); + } +} + + /* + * This function returns the quote string as a structure. In this way we + * have two ways to get the quote string: as a char * or as a QSTRING_S * + * directly. + */ +QSTRING_S * +qs_quote_match(char **q, LINE *l, char *rqstr, int rqstrlen) +{ + char GLine[NSTRING], NLine[NSTRING], PLine[NSTRING]; + LINE *nl = l != curbp->b_linep ? lforw(l) : NULL; + LINE *pl = lback(l) != curbp->b_linep ? lback(l) : NULL; + int plb = 1; + + linencpy(GLine, l, NSTRING); + linencpy(NLine, nl, NSTRING); + + if (pl){ + linencpy(PLine, pl, NSTRING); + if(lback(pl) != curbp->b_linep){ + char PPLine[NSTRING]; + + linencpy(PPLine, lback(pl), NSTRING); + plb = line_isblank(q, PLine, GLine, PPLine, NSTRING); + } + } + return do_quote_match(q, GLine, NLine, PLine, rqstr, rqstrlen, plb); +} /* * Return number of quotes if whatever starts the line matches the quote string + * rqstr is a pointer to raw qstring; buf points to processed qstring */ int -quote_match(UCS *q, LINE *l, UCS *buf, size_t buflen) +quote_match(char **q, LINE *l, char *buf, size_t buflen, int raw) { - register int i, n, j, qb; - - *buf = '\0'; - if(*q == '\0') - return(1); - - qb = (ucs4_strlen(q) > 1 && q[ucs4_strlen(q)-1] == ' ') ? 1 : 0; - for(n = 0, j = 0; ;){ - for(i = 0; j <= llength(l) && qb ? q[i+1] : q[i]; i++, j++) - if(q[i] != lgetc(l, j).c) - return(n); - - n++; - if((!qb && q[i] == '\0') || (qb && q[i+1] == '\0')){ - if(ucs4_strlen(buf) + ucs4_strlen(q) + 1 < buflen){ - ucs4_strncat(buf, q, buflen-ucs4_strlen(q)-1); - buf[buflen-1] = '\0'; - if(qb && (j > llength(l) || lgetc(l, j).c != ' ')) - buf[ucs4_strlen(buf)-1] = '\0'; - } - } - if(j > llength(l)) - return(n); - else if(qb && lgetc(l, j).c == ' ') - j++; + QSTRING_S *qs; + char rqstr[NSTRING]; + + qs = qs_quote_match(q, l, rqstr, NSTRING); + if(qs) + record_quote_string(qs); + flatten_qstring(qs, buf, buflen); + if (qs) free_qs(&qs); + + if(raw){ + strncpy(buf, rqstr, buflen < NSTRING ? buflen : NSTRING); + buf[buflen-1] = '\0'; } - return(n); /* never reached */ + + return buf && buf[0] ? strlen(buf) : 0; } +int ucs4_strlenis(UCS *ucs_qstr) +{ + char *str = ucs4_to_utf8_cpystr(ucs_qstr); + int i = (int) strlenis(str); + + if(str) fs_give((void **)&str); + return i; +} /* Justify the entire buffer instead of just a paragraph */ int @@ -721,6 +835,7 @@ fillpara(int f, int n) } if(action == 'R' && curwp->w_markp){ + char qstrfl[NSTRING]; /* let yank() know that it may be restoring a paragraph */ thisflag |= CFFILL; @@ -733,21 +848,25 @@ fillpara(int f, int n) /* determine if we're justifying quoted text or not */ qstr = (glo_quote_str - && quote_match(glo_quote_str, - curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp, - qstr2, NSTRING) - && *qstr2) ? qstr2 : NULL; - + && quote_match(default_qstr(glo_quote_str, 1), + (curwp->w_doto > 0 ? curwp->w_dotp->l_fp : curwp->w_dotp), + qstrfl, NSTRING, 0) + && *qstrfl) ? utf8_to_ucs4_cpystr(qstrfl) : NULL; /* * Fillregion moves dot to the end of the filled region. */ - if(!fillregion(qstr, &addedregion)) + if(!fillregion(qstr, NULL, &addedregion)) return(FALSE); set_last_region_added(&addedregion); + + if(qstr) + fs_give((void **)&qstr); } else if(action == 'P'){ + char ind_str[NSTRING], qstrfl[NSTRING]; + UCS *istr; /* * Justfiy the current paragraph. @@ -759,17 +878,16 @@ fillpara(int f, int n) if(gotoeop(FALSE, 1) == FALSE) return(FALSE); - /* determine if we're justifying quoted text or not */ - qstr = (glo_quote_str - && quote_match(glo_quote_str, - curwp->w_dotp, qstr2, NSTRING) - && *qstr2) ? qstr2 : NULL; - setmark(0,0); /* mark last line of para */ /* jump back to the beginning of the paragraph */ gotobop(FALSE, 1); + istr = indent_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, ind_str, NSTRING, 0) + && *ind_str ? utf8_to_ucs4_cpystr(ind_str) : NULL; + qstr = (quote_match(default_qstr(glo_quote_str, 1), curwp->w_dotp, qstrfl, NSTRING, 0) + && *qstrfl) ? utf8_to_ucs4_cpystr(qstrfl) : NULL; + /* let yank() know that it may be restoring a paragraph */ thisflag |= (CFFILL | CFFLPA); @@ -783,9 +901,15 @@ fillpara(int f, int n) /* * Fillregion moves dot to the end of the filled region. */ - if(!fillregion(qstr, &addedregion)) + if(!fillregion(qstr, istr, &addedregion)) return(FALSE); + if(qstr) + fs_give((void **)&qstr); + + if(istr) + fs_give((void **)&istr); + set_last_region_added(&addedregion); /* Leave cursor on first char of first line after justified region */ @@ -827,16 +951,16 @@ fillpara(int f, int n) * can delete it and restore the saved part. */ int -fillregion(UCS *qstr, REGION *addedregion) +fillregion(UCS *qstr, UCS *istr, REGION *addedregion) { long c, sz, last_char = 0; - int i, j, qlen, same_word, + int i, j, qlen, same_word, qi, pqi, qlenis, spaces, word_len, word_ind, line_len, ww; int starts_midline = 0; int ends_midline = 0; int offset_into_start; LINE *line_before_start, *lp; - UCS line_last, word[NSTRING]; + UCS line_last, word[NSTRING], quoid[NSTRING], qstr2[NSTRING]; REGION region; /* if region starts midline insert a newline */ @@ -847,6 +971,35 @@ fillregion(UCS *qstr, REGION *addedregion) if(curwp->w_marko > 0 && curwp->w_marko < llength(curwp->w_markp)) ends_midline++; + for (i = 0; (i < NSTRING) && qstr && (quoid[i] = qstr[i]); i++); + for (j = 0; ((i + j) < NSTRING) && istr && (quoid[i] = istr[j]); i++,j++); + quoid[i] = '\0'; + qi = ucs4_strlen(quoid); + if (istr) /* strip trailing spaces */ + for (;ISspace(quoid[qi - 1]); qi--); + quoid[qi] = '\0'; /* we have closed quoid at "X" in the first line */ + + if (ucs4_strlenis(quoid) > fillcol) + return FALSE; /* Too wide, we can't justify this! */ + + if (qstr && istr){ + for (i = ucs4_strlen(qstr) - 1; ISspace(qstr[i]); i--); + qstr[i + 1] = '\0'; /* qstrfl */ + } + qlen = ucs4_strlen(qstr); /* qstrfl*/ + qlenis = ucs4_strlenis(qstr); + + for(i = 0, qstr2[0] = '\0'; qstr && qstr[i] && (qstr2[i] = qstr[i]); i++); + + if (istr && ((j = ucs4_strlenis(quoid) - ucs4_strlenis(qstr)) > 0)){ + pqi = ucs4_strlen(qstr); + for (i = 0; (i < j) && (qstr2[pqi + i] = ' '); i++); + if (ISspace(istr[ucs4_strlen(istr) - 1])) + qstr2[pqi + i++] = ' '; + qstr2[pqi + i] = '\0'; + qstr = qstr2; + } + /* cut the paragraph into our fill buffer */ fdelete(); if(!getregion(®ion, curwp->w_markp, curwp->w_marko)) @@ -863,28 +1016,36 @@ fillregion(UCS *qstr, REGION *addedregion) /* Now insert it back wrapped */ spaces = word_len = word_ind = line_len = same_word = 0; - qlen = qstr ? ucs4_strlen(qstr) : 0; /* Beginning with leading quoting... */ - if(qstr){ - i = 0; - while(qstr[i]){ - ww = wcellwidth(qstr[i]); - line_len += (ww >= 0 ? ww : 1); - linsert(1, qstr[i++]); - } + if(qstr || istr){ + for(i = 0; quoid[i] != '\0' ; i++) + linsert(1, quoid[i]); line_last = ' '; /* no word-flush space! */ + line_len = ucs4_strlenis(quoid); /* we demand a recount! */ } /* remove first leading quotes if any */ if(starts_midline) i = 0; - else - for(i = qlen; (c = fremove(i)) == ' ' || c == TAB; i++){ + else{ + if(qstr || istr){ + for (i = 0; (c = fremove(i)) != '\0'; i++){ + word[i] = c; + word[i+1] = '\0'; + if(ucs4_strlenis(word) >= ucs4_strlenis(quoid)) + break; + } + i++; + } + else + i = 0; + for(; ISspace(c = fremove(i)); i++){ linsert(1, line_last = (UCS) c); line_len += ((c == TAB) ? (~line_len & 0x07) + 1 : 1); } + } /* then digest the rest... */ while((c = fremove(i++)) >= 0){ @@ -905,21 +1066,22 @@ fillregion(UCS *qstr, REGION *addedregion) case TAB : case ' ' : + case NBSP: spaces++; break; default : if(spaces){ /* flush word? */ - if((line_len - qlen > 0) + if((line_len - qlenis > 0) && line_len + word_len + 1 > fillcol - && ((ucs4_isspace(line_last)) + && ((ISspace(line_last)) || (linsert(1, ' '))) && same_word == 0 && (line_len = fpnewline(qstr))) line_last = ' '; /* no word-flush space! */ if(word_len){ /* word to write? */ - if(line_len && !ucs4_isspace(line_last)){ + if(line_len && !ISspace(line_last)){ linsert(1, ' '); /* need padding? */ line_len++; } @@ -941,8 +1103,8 @@ fillregion(UCS *qstr, REGION *addedregion) if(word_ind + 1 >= NSTRING){ /* Magic! Fake that we output a wrapped word */ - if((line_len - qlen > 0) && same_word == 0){ - if(!ucs4_isspace(line_last)) + if((line_len - qlenis > 0) && same_word == 0){ + if(!ISspace(line_last)) linsert(1, ' '); line_len = fpnewline(qstr); } @@ -964,12 +1126,12 @@ fillregion(UCS *qstr, REGION *addedregion) } if(word_len){ - if((line_len - qlen > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){ - if(!ucs4_isspace(line_last)) + if((line_len - qlenis > 0) && (line_len + word_len + 1 > fillcol) && same_word == 0){ + if(!ISspace(line_last)) linsert(1, ' '); (void) fpnewline(qstr); } - else if(line_len && !ucs4_isspace(line_last)) + else if(line_len && !ISspace(line_last)) linsert(1, ' '); for(j = 0; j < word_ind; j++) @@ -1027,11 +1189,11 @@ fpnewline(UCS *quote) int len; lnewline(); - for(len = 0; quote && *quote; quote++){ + for(len = ucs4_strlenis(quote); quote && *quote; quote++){ int ww; - ww = wcellwidth(*quote); - len += (ww >= 0 ? ww : 1); +/* ww = wcellwidth(*quote); + len += (ww >= 0 ? ww : 1);*/ linsert(1, *quote); } @@ -1175,5 +1337,45 @@ setquotelevelinregion(int quotelevel, REGION *addedregion) markregion(1); } + /* + * This puts us at the end of the quoted region instead + * of on the following line. This makes it convenient + * for the user to follow a quotelevel adjustment with + * a Justify if desired. + */ + if(backuptoprevline){ + curwp->w_doto = 0; + backchar(0, 1); + } + + if(ends_midline){ /* doesn't need fixing otherwise */ + unmarkbuffer(); + markregion(1); + } + return (TRUE); } + +/* + * If there is an indent string this function returns + * its length + */ +int +indent_match(char **q, LINE *l, char *buf, int buflen, int raw) +{ + char GLine[NSTRING]; + int i, k, plb; + + k = quote_match(q,l, buf, buflen, raw); + linencpy(GLine, l, NSTRING); + plb = (lback(l) != curbp->b_linep) ? lisblank(lback(l)) : 1; + if (!plb){ + i = llength(lback(l)) - 1; + for (; i >= 0 && ISspace(lgetc(lback(l), i).c); i--); + if (EOLchar(lgetc(lback(l), i).c)) + plb++; + } + + return get_indent_raw_line(q, GLine, buf, buflen, k, plb); +} + diff --git a/pith/Makefile.am b/pith/Makefile.am index ce6c78a3..7def45ef 100644 --- a/pith/Makefile.am +++ b/pith/Makefile.am @@ -25,7 +25,7 @@ libpith_a_SOURCES = ablookup.c abdlc.c addrbook.c addrstring.c adrbklib.c bldadd filter.c flag.c folder.c handle.c help.c helpindx.c hist.c icache.c imap.c init.c \ keyword.c ldap.c list.c mailcap.c mailcmd.c mailindx.c maillist.c mailview.c \ margin.c mimedesc.c mimetype.c msgno.c newmail.c news.c pattern.c pipe.c \ - readfile.c remote.c reply.c rfc2231.c save.c search.c sequence.c send.c sort.c \ + readfile.c remote.c reply.c rfc2231.c rules.c save.c search.c sequence.c send.c sort.c \ state.c status.c store.c stream.c string.c strlst.c takeaddr.c tempfile.c text.c \ thread.c adjtime.c url.c util.c helptext.c smkeys.c smime.c diff --git a/pith/Makefile.in b/pith/Makefile.in index 322291b0..708abccf 100644 --- a/pith/Makefile.in +++ b/pith/Makefile.in @@ -83,7 +83,7 @@ am_libpith_a_OBJECTS = ablookup.$(OBJEXT) abdlc.$(OBJEXT) \ margin.$(OBJEXT) mimedesc.$(OBJEXT) mimetype.$(OBJEXT) \ msgno.$(OBJEXT) newmail.$(OBJEXT) news.$(OBJEXT) \ pattern.$(OBJEXT) pipe.$(OBJEXT) readfile.$(OBJEXT) \ - remote.$(OBJEXT) reply.$(OBJEXT) rfc2231.$(OBJEXT) \ + remote.$(OBJEXT) reply.$(OBJEXT) rfc2231.$(OBJEXT) rules.$(OBJEXT) \ save.$(OBJEXT) search.$(OBJEXT) sequence.$(OBJEXT) \ send.$(OBJEXT) sort.$(OBJEXT) state.$(OBJEXT) status.$(OBJEXT) \ store.$(OBJEXT) stream.$(OBJEXT) string.$(OBJEXT) \ @@ -319,7 +319,7 @@ libpith_a_SOURCES = ablookup.c abdlc.c addrbook.c addrstring.c adrbklib.c bldadd filter.c flag.c folder.c handle.c help.c helpindx.c hist.c icache.c imap.c init.c \ keyword.c ldap.c list.c mailcap.c mailcmd.c mailindx.c maillist.c mailview.c \ margin.c mimedesc.c mimetype.c msgno.c newmail.c news.c pattern.c pipe.c \ - readfile.c remote.c reply.c rfc2231.c save.c search.c sequence.c send.c sort.c \ + readfile.c remote.c reply.c rfc2231.c rules.c save.c search.c sequence.c send.c sort.c \ state.c status.c store.c stream.c string.c strlst.c takeaddr.c tempfile.c text.c \ thread.c adjtime.c url.c util.c helptext.c smkeys.c smime.c @@ -451,6 +451,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/thread.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/url.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/util.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rules.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< diff --git a/pith/adrbklib.c b/pith/adrbklib.c index 01d00353..904928b8 100644 --- a/pith/adrbklib.c +++ b/pith/adrbklib.c @@ -5136,8 +5136,14 @@ init_addrbooks(OpenStatus want_status, int reset_to_top, int open_if_only_one, i if(as.cur >= as.how_many_personals) pab->type |= GLOBAL; - pab->access = adrbk_access(pab); - + if(ps_global->mail_stream && + ps_global->mail_stream->lock && (pab->type & REMOTE_VIA_IMAP)){ + as.initialized = 0; + pab->access = NoAccess; + } + else{ + pab->access = adrbk_access(pab); + } /* global address books are forced readonly */ if(pab->type & GLOBAL && pab->access != NoAccess) pab->access = ReadOnly; diff --git a/pith/charconv/utf8.c b/pith/charconv/utf8.c index 411e1ddd..bca0d26b 100644 --- a/pith/charconv/utf8.c +++ b/pith/charconv/utf8.c @@ -1049,6 +1049,56 @@ utf8_width(char *str) /* + * Returns the screen cells width of the UTF-8 string argument, treating tabs + * in a special way. + */ +unsigned +utf8_widthis(char *str) +{ + unsigned width = 0; + int this_width; + UCS ucs; + unsigned long remaining_octets; + char *readptr; + + if(!(str && *str)) + return(width); + + readptr = str; + remaining_octets = readptr ? strlen(readptr) : 0; + + while(remaining_octets > 0 && *readptr){ + + ucs = (UCS) utf8_get((unsigned char **) &readptr, &remaining_octets); + + if(ucs & U8G_ERROR){ + /* + * This should not happen, but do something to handle it anyway. + * Treat each character as a single width character, which is what should + * probably happen when we actually go to write it out. + */ + remaining_octets--; + readptr++; + this_width = 1; + } + else{ + this_width = (ucs == TAB) ? ((~width & 0x07) + 1) : wcellwidth(ucs); + + /* + * If this_width is -1 that means we can't print this character + * with our current locale. Writechar will print a '?'. + */ + if(this_width < 0) + this_width = 1; + } + + width += (unsigned) this_width; + } + + return(width); +} + +/* * Copy UTF-8 characters from src into dst. * This is intended to be used if you want to truncate a string at * the start instead of the end. For example, you have a long string diff --git a/pith/charconv/utf8.h b/pith/charconv/utf8.h index d22a8a7c..09a2a95c 100644 --- a/pith/charconv/utf8.h +++ b/pith/charconv/utf8.h @@ -81,6 +81,7 @@ UCS *ucs4_strncat(UCS *ucs4dst, UCS *ucs4src, size_t n); UCS *ucs4_strchr(UCS *s, UCS c); UCS *ucs4_strrchr(UCS *s, UCS c); unsigned utf8_width(char *); +unsigned utf8_widthis(char *); size_t utf8_to_width_rhs(char *, char *, size_t, unsigned); int utf8_snprintf(char *, size_t, char *, ...); size_t utf8_to_width(char *, char *, size_t, unsigned, unsigned *); diff --git a/pith/color.c b/pith/color.c index 9794294b..b5dc320c 100644 --- a/pith/color.c +++ b/pith/color.c @@ -21,7 +21,8 @@ static char rcsid[] = "$Id: color.c 769 2007-10-24 00:15:40Z hubert@u.washington #include "../pith/state.h" #include "../pith/conf.h" #include "../pith/filter.h" - +#include "../pith/mailview.h" +#include "../pico/estruct.h" char * color_embed(char *fg, char *bg) @@ -70,23 +71,110 @@ struct quote_colors { struct quote_colors *next; }; +int +is_word (buf, i, j) + char buf[NSTRING]; + int i, j; +{ + return i <= j && is_letter(buf[i]) ? + (i < j ? is_word(buf,i+1,j) : 1) : 0; +} + +int +is_mailbox(buf,i,j) +char buf[NSTRING]; + int i, j; +{ + return i <= j && (is_letter(buf[i]) || is_digit(buf[i]) || buf[i] == '.') + ? (i < j ? is_mailbox(buf,i+1,j) : 1) : 0; +} + +int +next_level_quote(buf, line, i, is_flowed) + char *buf; + char **line; + int i; + int is_flowed; +{ + int j; + + if (!single_level(buf[i])){ + if(is_mailbox(buf,i,i)){ + for (j = i; buf[j] && !isspace(buf[j]); j++); + if (is_word(buf,i,j-1) || is_mailbox(buf,i,j-1)) + j += isspace(buf[j]) ? 2 : 1; + } + else{ + switch(buf[i]){ + case ':' : + if (next(buf,i) != RPAREN) + j = i + 1; + else + j = i + 2; + break; + + case '-' : + if (next(buf,i) != '-') + j = i + 2; + else + j = i + 3; + break; + + case '+' : + case '*' : + if (next(buf,i) != ' ') + j = i + 2; + else + j = i + 3; + break; + + default : + for (j = i; buf[j] && !isspace(buf[j]) + && (!single_level(buf[i]) && !is_letter(buf[j])); j++); + + j += isspace(buf[j]) ? 1 : 0; + break; + } + } + if (line && *line) + (*line) += j - i; + } + else{ + j = i+1; + if (line && *line) + (*line)++; + } + if(!is_flowed){ + if(line && *line) + for(; isspace((unsigned char)*(*line)); (*line)++); + for (i = j; isspace((unsigned char) buf[i]); i++); + } + else i = j; + if (is_flowed && i != j) + buf[i] = '\0'; + return i; +} int color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg) { - int countem = 0; + int countem = 0, i, j = 0; struct variable *vars = ps_global->vars; - char *p; + char *p, buf[NSTRING] = {'\0'}; struct quote_colors *colors = NULL, *cp, *next; COLOR_PAIR *col = NULL; int is_flowed = is_flowed_msg ? *((int *)is_flowed_msg) : 0; + int code; + + code = (is_flowed ? IS_FLOWED : NO_FLOWED) | COLORAQUO; + select_quote(linenum, line, ins, (void *) &code); + strncpy(buf, tmp_20k_buf, NSTRING < SIZEOF_20KBUF ? NSTRING : SIZEOF_20KBUF); + buf[sizeof(buf)-1] = '\0'; p = line; - if(!is_flowed) - while(isspace((unsigned char)*p)) - p++; + for(i = 0; isspace((unsigned char)buf[i]); i++, p++); - if(p[0] == '>'){ + if(buf[i]){ struct quote_colors *c; /* @@ -135,7 +223,7 @@ color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg) free_color_pair(&col); cp = NULL; - while(*p == '>'){ + while(buf[i]){ cp = (cp && cp->next) ? cp->next : colors; if(countem > 0) @@ -145,10 +233,9 @@ color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg) countem = (countem == 1) ? 0 : countem; - p++; - if(!is_flowed) - for(; isspace((unsigned char)*p); p++) - ; + i = next_level_quote(buf, &p, i, is_flowed); + for (; isspace((unsigned char)*p); p++); + for (; isspace((unsigned char)buf[i]); i++); } if(colors){ @@ -211,7 +298,7 @@ color_a_quote(long int linenum, char *line, LT_INS_S **ins, void *is_flowed_msg) } } - return(0); + return(1); } diff --git a/pith/color.h b/pith/color.h index b90d82cf..01bbbb58 100644 --- a/pith/color.h +++ b/pith/color.h @@ -21,6 +21,24 @@ #include "../pith/pattern.h" #include "../pith/osdep/color.h" +#define NO_FLOWED 0x0000 +#define IS_FLOWED 0x0001 +#define DELETEQUO 0x0010 +#define COLORAQUO 0x0100 +#define RAWSTRING 0x1000 + +/* This is needed for justification, I will move it to a better place later + * or maybe not + */ +#define is_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0) + +#define is_letter(c) (((c) >= 'a' && (c) <= 'z') || \ + ((c) >= 'A' && (c) <= 'Z')) + +#define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0) + +#define single_level(c) (((c) == '>') || ((c) == '|') || ((c) == '~') || \ + ((c) == ']')) typedef struct spec_color_s { int inherit; /* this isn't a color, it is INHERIT */ @@ -80,6 +98,7 @@ typedef struct spec_color_s { /* exported protoypes */ char *color_embed(char *, char *); int colorcmp(char *, char *); +int next_level_quote(char *, char **, int, int); int color_a_quote(long, char *, LT_INS_S **, void *); void free_spec_colors(SPEC_COLOR_S **); diff --git a/pith/conf.c b/pith/conf.c index 18eaf8f0..876667e5 100644 --- a/pith/conf.c +++ b/pith/conf.c @@ -29,6 +29,7 @@ static char rcsid[] = "$Id: conf.c 1266 2009-07-14 18:39:12Z hubert@u.washington #include "../pith/remote.h" #include "../pith/keyword.h" #include "../pith/mailview.h" +#include "../pith/rules.h" #include "../pith/list.h" #include "../pith/status.h" #include "../pith/ldap.h" @@ -206,6 +207,8 @@ CONF_TXT_T cf_text_fcc_name_rule[] = "Determines default name for Fcc...\n# Choi CONF_TXT_T cf_text_sort_key[] = "Sets presentation order of messages in Index. Choices:\n# Subject, From, Arrival, Date, Size, To, Cc, OrderedSubj, Score, and Thread.\n# Order may be reversed by appending /Reverse. Default: \"Arrival\"."; +CONF_TXT_T cf_text_thread_sort_key[] = "#Sets presentation order of threads in thread index. Choices:\n#arrival, and thread."; + CONF_TXT_T cf_text_addrbook_sort_rule[] = "Sets presentation order of address book entries. Choices: dont-sort,\n# fullname-with-lists-last, fullname, nickname-with-lists-last, nickname\n# Default: \"fullname-with-lists-last\"."; CONF_TXT_T cf_text_folder_sort_rule[] = "Sets presentation order of folder list entries. Choices: alphabetical,\n# alpha-with-dirs-last, alpha-with-dirs-first.\n# Default: \"alpha-with-directories-last\"."; @@ -222,12 +225,44 @@ CONF_TXT_T cf_text_unk_character_set[] = "Defaults to nothing, which is equivale CONF_TXT_T cf_text_editor[] = "Specifies the program invoked by ^_ in the Composer,\n# or the \"enable-alternate-editor-implicitly\" feature."; +CONF_TXT_T cf_text_compose_rules[] = "Allows a user to set rules when composing messages."; + +CONF_TXT_T cf_text_forward_rules[] = "Allows a user to set rules when forwarding messages."; + +CONF_TXT_T cf_text_reply_rules[] = "Allows a user to set rules when replying messages."; + +CONF_TXT_T cf_text_index_rules[] = "Allows a user to supersede global index format variable in designated folders."; + +CONF_TXT_T cf_text_key_def_rules[] = "Allows a user to override keystrokes in certain screens."; + +CONF_TXT_T cf_text_replace_rules[] = "Allows a user to change the form a specify field in the index-format is \n# displayed."; + +CONF_TXT_T cf_text_reply_indent_rules[] = "Allows a user to change the form a specify a reply-indent-string\n# based of rules."; + +CONF_TXT_T cf_text_reply_leadin_rules[] = "Allows a user to replace the reply-leadin message based on different parameters."; + +CONF_TXT_T cf_text_reply_subject_rules[] = "Allows a user to replace the subject of a message in a customs based way"; + +CONF_TXT_T cf_text_thread_displaystyle_rule[] = "Allows a user to specify the threading style of specific folders"; + +CONF_TXT_T cf_text_thread_indexstyle_rule[] = "Allows a user to specify the threading index style of specific folders"; + +CONF_TXT_T cf_text_save_rules[] = "Allows a user to specify a save folder message for specific senders or folders."; + +CONF_TXT_T cf_text_smtp_rules[] = "Allows a user to specify a smtp server to be used when sending e-mail,\n# according to the rules specified here."; + +CONF_TXT_T cf_text_sort_rules[] = "Allows a user to specify the sort default order of a specific folder."; + +CONF_TXT_T cf_text_startup_rules[] = "Allows a user to specify the position of a highlighted message when opening a \n# folder."; + CONF_TXT_T cf_text_speller[] = "Specifies the program invoked by ^T in the Composer."; CONF_TXT_T cf_text_deadlets[] = "Specifies the number of dead letter files to keep when canceling."; CONF_TXT_T cf_text_fillcol[] = "Specifies the column of the screen where the composer should wrap."; +CONF_TXT_T cf_special_text_color[] = "Specifies a comma separated list of text and regular expresions that Pine\n# will highlight"; + CONF_TXT_T cf_text_replystr[] = "Specifies the string to insert when replying to a message."; CONF_TXT_T cf_text_quotereplstr[] = "Specifies the string to replace quotes with when viewing a message."; @@ -340,6 +375,8 @@ CONF_TXT_T cf_text_stat_msg_delay[] = "The number of seconds to sleep after writ CONF_TXT_T cf_text_busy_cue_rate[] = "Number of times per-second to update busy cue messages"; +CONF_TXT_T cf_text_sleep[] = "The number of seconds between a viewer finishing opening a file and removing\n#it. See more details in configuration screen. Default: 0"; + CONF_TXT_T cf_text_mailcheck[] = "The approximate number of seconds between checks for new mail"; CONF_TXT_T cf_text_mailchecknoncurr[] = "The approximate number of seconds between checks for new mail in folders\n# other than the current folder and inbox.\n# Default is same as mail-check-interval"; @@ -430,6 +467,9 @@ CONF_TXT_T cf_text_window_position[] = "Window position in the format: CxR+X+Y\n CONF_TXT_T cf_text_newsrc_path[] = "Full path and name of NEWSRC file"; +#ifndef _WINDOWS +CONF_TXT_T cf_text_maildir_location[] = "Location relative to your HOME directory of the directory where your INBOX\n# for the maildir format is located. Default value is \"Maildir\". If your\n# inbox is located at \"~/Maildir\" you do not need to change this value.\n# A common value is also \".maildir\""; +#endif /*---------------------------------------------------------------------- These are the variables that control a number of pine functions. They @@ -520,6 +560,8 @@ static struct variable variables[] = { NULL, cf_text_fcc_name_rule}, {"sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_sort_key}, +{"thread-sort-key", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + NULL, cf_text_thread_sort_key}, {"addrbook-sort-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, "Address Book Sort Rule", cf_text_addrbook_sort_rule}, {"folder-sort-rule", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, @@ -542,6 +584,34 @@ static struct variable variables[] = { NULL, cf_text_thread_exp_char}, {"threading-lastreply-character", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, "Threading Last Reply Character", cf_text_thread_lastreply_char}, +{"threading-display-style-rule", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Threading Display Style Rule", cf_text_thread_displaystyle_rule}, +{"threading-index-style-rule", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Threading Index Style Rule", cf_text_thread_indexstyle_rule}, +{"compose-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Compose Rules", cf_text_compose_rules}, +{"forward-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Forward Rules", cf_text_forward_rules}, +{"index-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, + "Index Rules", cf_text_index_rules}, +{"key-definition-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, + "Key Definition Rules", cf_text_key_def_rules}, +{"replace-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, + "Replace Rules", cf_text_replace_rules}, +{"reply-indent-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Reply Indent Rules", cf_text_reply_indent_rules}, +{"reply-leadin-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, + "Reply Leadin Rules", cf_text_reply_leadin_rules}, +{"reply-subject-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, + "Reply Subject Rules", cf_text_reply_subject_rules}, +{"save-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Save Rules", cf_text_save_rules}, +{"smtp-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Smtp Rules", cf_text_smtp_rules}, +{"sort-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Sort Rules", cf_text_sort_rules}, +{"startup-rules", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + "Startup Rules", cf_text_startup_rules}, #ifndef _WINDOWS {"display-character-set", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_disp_char_set}, @@ -560,6 +630,8 @@ static struct variable variables[] = { NULL, cf_text_speller}, {"composer-wrap-column", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_fillcol}, +{"special-text-color", 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, + NULL, cf_special_text_color}, {"reply-indent-string", 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, NULL, cf_text_replystr}, {"reply-leadin", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, @@ -616,6 +688,8 @@ static struct variable variables[] = { NULL, cf_text_stat_msg_delay}, {"busy-cue-rate", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_busy_cue_rate}, +{"sleep-interval-length", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + NULL, cf_text_sleep}, {"mail-check-interval", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_mailcheck}, {"mail-check-interval-noncurrent", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, @@ -630,6 +704,10 @@ static struct variable variables[] = { NULL, cf_text_news_active}, {"news-spool-directory", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_news_spooldir}, +#ifndef _WINDOWS +{"maildir-location", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, + "Maildir Location", cf_text_maildir_location}, +#endif {"upload-command", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, NULL, cf_text_upload_cmd}, {"upload-command-prefix", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, @@ -817,6 +895,8 @@ static struct variable variables[] = { {"incoming-unseen-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, {"signature-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, {"signature-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, +{"special-text-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, +{"special-text-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, {"prompt-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, {"prompt-background-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, {"header-general-foreground-color", 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0}, @@ -1566,7 +1646,7 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) register struct variable *vars = ps->vars; int obs_header_in_reply = 0, /* the obs_ variables are to */ obs_old_style_reply = 0, /* support backwards compatibility */ - obs_save_by_sender, i, def_sort_rev; + obs_save_by_sender, i, def_sort_rev, thread_def_sort_rev; long rvl; PINERC_S *fixedprc = NULL; FeatureLevel obs_feature_level; @@ -1591,6 +1671,7 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) GLO_FEATURE_LEVEL = cpystr("sappling"); GLO_OLD_STYLE_REPLY = cpystr(DF_OLD_STYLE_REPLY); GLO_SORT_KEY = cpystr(DF_SORT_KEY); + GLO_THREAD_SORT_KEY = cpystr(DF_THREAD_SORT_KEY); GLO_SAVED_MSG_NAME_RULE = cpystr(DF_SAVED_MSG_NAME_RULE); GLO_FCC_RULE = cpystr(DF_FCC_RULE); GLO_AB_SORT_RULE = cpystr(DF_AB_SORT_RULE); @@ -1615,6 +1696,7 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) GLO_LOCAL_FULLNAME = cpystr(DF_LOCAL_FULLNAME); GLO_LOCAL_ADDRESS = cpystr(DF_LOCAL_ADDRESS); GLO_OVERLAP = cpystr(DF_OVERLAP); + GLO_SLEEP = cpystr("0"); GLO_MAXREMSTREAM = cpystr(DF_MAXREMSTREAM); GLO_MARGIN = cpystr(DF_MARGIN); GLO_FILLCOL = cpystr(DF_FILLCOL); @@ -1985,6 +2067,8 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) set_current_val(&vars[V_FORM_FOLDER], TRUE, TRUE); set_current_val(&vars[V_EDITOR], TRUE, TRUE); set_current_val(&vars[V_SPELLER], TRUE, TRUE); + set_current_val(&vars[V_SPECIAL_TEXT], TRUE, TRUE); + regex_pattern(VAR_SPECIAL_TEXT); set_current_val(&vars[V_IMAGE_VIEWER], TRUE, TRUE); set_current_val(&vars[V_BROWSER], TRUE, TRUE); set_current_val(&vars[V_SMTP_SERVER], TRUE, TRUE); @@ -2069,6 +2153,13 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) } } + set_current_val(&vars[V_SLEEP], TRUE, TRUE); + ps->sleep = i = 0; + if(SVAR_SLEEP(ps, i, tmp_20k_buf, SIZEOF_20KBUF)) + init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf); + else + ps->sleep = i; + set_current_val(&vars[V_OVERLAP], TRUE, TRUE); ps->viewer_overlap = i = atoi(DF_OVERLAP); if(SVAR_OVERLAP(ps, i, tmp_20k_buf, SIZEOF_20KBUF)) @@ -2258,6 +2349,12 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) mail_parameters(NULL, SET_NEWSSPOOL, (void *)VAR_NEWS_SPOOL_DIR); +#ifndef _WINDOWS + set_current_val(&vars[V_MAILDIR_LOCATION], TRUE, TRUE); + if(VAR_MAILDIR_LOCATION && VAR_MAILDIR_LOCATION[0]) + mail_parameters(NULL, SET_MDINBOXPATH, (void *)VAR_MAILDIR_LOCATION); +#endif + /* guarantee a save default */ set_current_val(&vars[V_DEFAULT_SAVE_FOLDER], TRUE, TRUE); if(!VAR_DEFAULT_SAVE_FOLDER || !VAR_DEFAULT_SAVE_FOLDER[0]) @@ -2497,7 +2594,7 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) set_current_val(&vars[V_ARCHIVED_FOLDERS], TRUE, TRUE); set_current_val(&vars[V_INCOMING_FOLDERS], TRUE, TRUE); set_current_val(&vars[V_SORT_KEY], TRUE, TRUE); - if(decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev) == -1){ + if(decode_sort(VAR_SORT_KEY, &ps->def_sort, &def_sort_rev,0) == -1){ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Sort type \"%.200s\" is invalid", VAR_SORT_KEY); init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf); ps->def_sort = SortArrival; @@ -2506,6 +2603,17 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) else ps->def_sort_rev = def_sort_rev; + set_current_val(&vars[V_THREAD_SORT_KEY], TRUE, TRUE); + if(decode_sort(VAR_THREAD_SORT_KEY, &ps->thread_def_sort, + &thread_def_sort_rev, 1) == -1){ + sprintf(tmp_20k_buf, "Sort type \"%s\" is invalid", VAR_THREAD_SORT_KEY); + init_error(ps, SM_ORDER | SM_DING, 3, 5, tmp_20k_buf); + ps->thread_def_sort = SortThread; + ps->thread_def_sort_rev = 0; + } + else + ps->thread_def_sort_rev = thread_def_sort_rev; + cur_rule_value(&vars[V_SAVED_MSG_NAME_RULE], TRUE, TRUE); {NAMEVAL_S *v; int i; for(i = 0; (v = save_msg_rules(i)); i++) @@ -2592,6 +2700,7 @@ init_vars(struct pine *ps, void (*cmds_f) (struct pine *, char **)) if(cmds_f) (*cmds_f)(ps, VAR_INIT_CMD_LIST); + (void)create_rule_list(ps_global->vars); #ifdef _WINDOWS mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global)); #endif /* _WINDOWS */ @@ -2795,6 +2904,8 @@ feature_list(int index) F_ALWAYS_SPELL_CHECK, h_config_always_spell_check, PREF_COMP, 0}, /* Reply Prefs */ + {"alternate-reply-menu", NULL, + F_ALT_REPLY_MENU, h_config_alt_reply_menu, PREF_RPLY, 0}, {"copy-to-address-to-from-if-it-is-us", "Copy To Address to From if it is Us", F_COPY_TO_TO_FROM, h_config_copy_to_to_from, PREF_RPLY, 0}, {"enable-reply-indent-string-editing", NULL, @@ -2839,6 +2950,8 @@ feature_list(int index) F_NO_FCC_ATTACH, h_config_no_fcc_attach, PREF_SEND, 0}, {"fcc-on-bounce", "Include Fcc When Bouncing Messages", F_FCC_ON_BOUNCE, h_config_fcc_on_bounce, PREF_SEND, 0}, + {"return-path-uses-domain-name", NULL, + F_USE_DOMAIN_NAME, h_config_use_domain, PREF_SEND, 0}, {"mark-fcc-seen", NULL, F_MARK_FCC_SEEN, h_config_mark_fcc_seen, PREF_SEND, 0}, {"fcc-only-without-confirm", "Send to Fcc Only Without Confirming", @@ -2885,6 +2998,10 @@ feature_list(int index) F_SORT_DEFAULT_SAVE_ALPHA, h_config_sort_save_alpha, PREF_FLDR, 0}, {"vertical-folder-list", "Use Vertical Folder List", F_VERTICAL_FOLDER_LIST, h_config_vertical_list, PREF_FLDR, 0}, +#ifndef _WINDOWS + {"use-courier-folder-list", "Courier Style Folder List", + F_COURIER_FOLDER_LIST, h_config_courier_list, PREF_FLDR, 0}, +#endif /* Addr book */ {"combined-addrbook-display", "Combined Address Book Display", @@ -2901,6 +3018,8 @@ feature_list(int index) /* Index prefs */ {"auto-open-next-unread", NULL, F_AUTO_OPEN_NEXT_UNREAD, h_config_auto_open_unread, PREF_INDX, 0}, + {"enable-circular-tab", NULL, + F_AUTO_CIRCULAR_TAB, h_config_circular_tab, PREF_INDX, 0}, {"continue-tab-without-confirm", "Continue NextNew Without Confirming", F_TAB_NO_CONFIRM, h_config_tab_no_prompt, PREF_INDX, 0}, {"convert-dates-to-localtime", NULL, @@ -2913,6 +3032,8 @@ feature_list(int index) F_ENABLE_SPACE_AS_TAB, h_config_cruise_mode, PREF_INDX, 0}, {"enable-cruise-mode-delete", "Enable Cruise Mode With Deleting", F_ENABLE_TAB_DELETES, h_config_cruise_mode_delete, PREF_INDX, 0}, + {"mark-for-me-in-group", "Mark for Group Message to Me", + F_MARK_FOR_GROUP, h_config_mark_for_group, PREF_INDX, 1}, {"mark-for-cc", "Mark for CC", F_MARK_FOR_CC, h_config_mark_for_cc, PREF_INDX, 1}, {"next-thread-without-confirm", "Read Next Thread Without Confirming", @@ -2929,6 +3050,8 @@ feature_list(int index) F_COLOR_LINE_IMPORTANT, h_config_color_thrd_import, PREF_INDX, 0}, {"thread-sorts-by-arrival", "Thread Sorts by Arrival", F_THREAD_SORTS_BY_ARRIVAL, h_config_thread_sorts_by_arrival, PREF_INDX, 0}, + {"enhanced-fancy-thread-support", "Enhanced Fancy Thread Support", + F_ENHANCED_THREAD, h_config_enhanced_thread, PREF_INDX, 0}, /* Viewer prefs */ {"enable-msg-view-addresses", "Enable Message View Address Links", @@ -2939,6 +3062,8 @@ feature_list(int index) F_VIEW_SEL_URL, h_config_enable_view_url, PREF_VIEW, 1}, {"enable-msg-view-web-hostnames", "Enable Message View Web Hostname Links", F_VIEW_SEL_URL_HOST, h_config_enable_view_web_host, PREF_VIEW, 1}, + {"enable-msg-view-long-url", "Enable Recognition of Long URLS without Delimiter", + F_VIEW_LONG_URL, h_config_enable_long_url, PREF_VIEW, 0}, {"enable-msg-view-forced-arrows", "Enable Message View Forced Arrows", F_FORCE_ARROWS, h_config_enable_view_arrows, PREF_VIEW, 0}, /* set to TRUE for windows */ @@ -3036,6 +3161,8 @@ feature_list(int index) F_FORCE_LOW_SPEED, h_config_force_low_speed, PREF_OS_LWSD, 0}, {"auto-move-read-msgs", "Auto Move Read Messages", F_AUTO_READ_MSGS, h_config_auto_read_msgs, PREF_MISC, 0}, + {"auto-move-read-msgs-using-rules", "Auto Move Read Messages Using Rules", + F_AUTO_READ_MSGS_RULES, h_config_auto_read_msgs_rules, PREF_MISC, 0}, {"auto-unselect-after-apply", NULL, F_AUTO_UNSELECT, h_config_auto_unselect, PREF_MISC, 0}, {"auto-unzoom-after-apply", NULL, @@ -3097,6 +3224,8 @@ feature_list(int index) F_FULL_AUTO_EXPUNGE, h_config_full_auto_expunge, PREF_MISC, 0}, {"force-arrow-cursor", NULL, F_FORCE_ARROW, h_config_force_arrow, PREF_MISC, 0}, + {"ignore-size-changes", NULL, + F_IGNORE_SIZE, h_config_ignore_size, PREF_MISC, 0}, {"maildrops-preserve-state", NULL, F_MAILDROPS_PRESERVE_STATE, h_config_maildrops_preserve_state, PREF_MISC, 0}, @@ -6442,6 +6571,7 @@ set_current_color_vals(struct pine *ps) set_color_val(&vars[V_IND_OP_FORE_COLOR], 0); set_color_val(&vars[V_INCUNSEEN_FORE_COLOR], 0); set_color_val(&vars[V_SIGNATURE_FORE_COLOR], 0); + set_color_val(&vars[V_SPECIAL_TEXT_FORE_COLOR], 0); set_current_val(&ps->vars[V_INDEX_TOKEN_COLORS], TRUE, TRUE); set_current_val(&ps->vars[V_VIEW_HDR_COLORS], TRUE, TRUE); @@ -6964,6 +7094,12 @@ toggle_feature(struct pine *ps, struct variable *var, FEATURE_S *f, break; +#ifndef _WINDOWS + case F_COURIER_FOLDER_LIST: + mail_parameters(NULL,SET_COURIERSTYLE,(void *)(F_ON(f->id ,ps)? 1 : 0)); + break; /* COURIER == 1, CCLIENT == 0, see maildir.h */ +#endif + case F_COLOR_LINE_IMPORTANT : case F_DATES_TO_LOCAL : clear_index_cache(ps->mail_stream, 0); @@ -6975,6 +7111,7 @@ toggle_feature(struct pine *ps, struct variable *var, FEATURE_S *f, break; case F_MARK_FOR_CC : + case F_MARK_FOR_GROUP : clear_index_cache(ps->mail_stream, 0); if(THREADING() && sp_viewing_a_thread(ps->mail_stream)) unview_thread(ps, ps->mail_stream, ps->msgmap); @@ -7569,10 +7706,40 @@ config_help(int var, int feature) return(h_config_fcc_rule); case V_SORT_KEY : return(h_config_sort_key); + case V_THREAD_SORT_KEY : + return(h_config_thread_sort_key); case V_AB_SORT_RULE : return(h_config_ab_sort_rule); case V_FLD_SORT_RULE : return(h_config_fld_sort_rule); + case V_THREAD_DISP_STYLE_RULES: + return(h_config_thread_display_style_rule); + case V_THREAD_INDEX_STYLE_RULES: + return(h_config_thread_index_style_rule); + case V_COMPOSE_RULES: + return(h_config_compose_rules); + case V_FORWARD_RULES: + return(h_config_forward_rules); + case V_INDEX_RULES: + return(h_config_index_rules); + case V_KEY_RULES: + return(h_config_key_macro_rules); + case V_REPLACE_RULES: + return(h_config_replace_rules); + case V_REPLY_INDENT_RULES: + return(h_config_reply_indent_rules); + case V_REPLY_LEADIN_RULES: + return(h_config_reply_leadin_rules); + case V_RESUB_RULES: + return(h_config_resub_rules); + case V_SAVE_RULES: + return(h_config_save_rules); + case V_SMTP_RULES: + return(h_config_smtp_rules); + case V_SORT_RULES: + return(h_config_sort_rules); + case V_STARTUP_RULES: + return(h_config_startup_rules); case V_POST_CHAR_SET : return(h_config_post_char_set); case V_UNK_CHAR_SET : @@ -7613,6 +7780,8 @@ config_help(int var, int feature) return(h_config_incoming_second_interv); case V_INCCHECKLIST : return(h_config_incoming_list); + case V_SLEEP : + return(h_config_sleep); case V_OVERLAP : return(h_config_viewer_overlap); case V_MAXREMSTREAM : @@ -7623,6 +7792,8 @@ config_help(int var, int feature) return(h_config_scroll_margin); case V_DEADLETS : return(h_config_deadlets); + case V_SPECIAL_TEXT : + return(h_config_special_text_to_color); case V_FILLCOL : return(h_config_composer_wrap_column); case V_TCPOPENTIMEO : @@ -7745,6 +7916,10 @@ config_help(int var, int feature) return(h_config_newmailwidth); case V_NEWSRC_PATH : return(h_config_newsrc_path); +#ifndef _WINDOWS + case V_MAILDIR_LOCATION : + return(h_config_maildir_location); +#endif case V_BROWSER : return(h_config_browser); #if defined(DOS) || defined(OS2) @@ -7788,6 +7963,9 @@ config_help(int var, int feature) case V_SIGNATURE_FORE_COLOR : case V_SIGNATURE_BACK_COLOR : return(h_config_signature_color); + case V_SPECIAL_TEXT_FORE_COLOR : + case V_SPECIAL_TEXT_BACK_COLOR : + return(h_config_special_text_color); case V_PROMPT_FORE_COLOR : case V_PROMPT_BACK_COLOR : return(h_config_prompt_color); @@ -8271,3 +8449,4 @@ pcpine_general_help(titlebuf) } #endif /* _WINDOWS */ + diff --git a/pith/conf.h b/pith/conf.h index 54c23ff5..2761d65b 100644 --- a/pith/conf.h +++ b/pith/conf.h @@ -144,10 +144,53 @@ #define VAR_SORT_KEY vars[V_SORT_KEY].current_val.p #define GLO_SORT_KEY vars[V_SORT_KEY].global_val.p #define COM_SORT_KEY vars[V_SORT_KEY].cmdline_val.p +#define VAR_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].current_val.p +#define GLO_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].global_val.p +#define COM_THREAD_SORT_KEY vars[V_THREAD_SORT_KEY].cmdline_val.p #define VAR_AB_SORT_RULE vars[V_AB_SORT_RULE].current_val.p #define GLO_AB_SORT_RULE vars[V_AB_SORT_RULE].global_val.p #define VAR_FLD_SORT_RULE vars[V_FLD_SORT_RULE].current_val.p #define GLO_FLD_SORT_RULE vars[V_FLD_SORT_RULE].global_val.p +#define VAR_COMPOSE_RULES vars[V_COMPOSE_RULES].current_val.l +#define GLO_COMPOSE_RULES vars[V_COMPOSE_RULES].global_val.l +#define USR_COMPOSE_RULES vars[V_COMPOSE_RULES].user_val.l +#define VAR_FORWARD_RULES vars[V_FORWARD_RULES].current_val.l +#define GLO_FORWARD_RULES vars[V_FORWARD_RULES].global_val.l +#define USR_FORWARD_RULES vars[V_FORWARD_RULES].user_val.l +#define VAR_INDEX_RULES vars[V_INDEX_RULES].current_val.l +#define GLO_INDEX_RULES vars[V_INDEX_RULES].global_val.l +#define USR_INDEX_RULES vars[V_INDEX_RULES].user_val.l +#define VAR_KEY_RULES vars[V_KEY_RULES].current_val.l +#define GLO_KEY_RULES vars[V_KEY_RULES].global_val.l +#define USR_KEY_RULES vars[V_KEY_RULES].user_val.l +#define VAR_REPLACE_RULES vars[V_REPLACE_RULES].current_val.l +#define GLO_REPLACE_RULES vars[V_REPLACE_RULES].global_val.l +#define USR_REPLACE_RULES vars[V_REPLACE_RULES].user_val.l +#define VAR_REPLY_INDENT_RULES vars[V_REPLY_INDENT_RULES].current_val.l +#define GLO_REPLY_INDENT_RULES vars[V_REPLY_INDENT_RULES].global_val.l +#define USR_REPLY_INDENT_RULES vars[V_REPLY_INDENT_RULES].user_val.l +#define VAR_REPLY_LEADIN_RULES vars[V_REPLY_LEADIN_RULES].current_val.l +#define GLO_REPLY_LEADIN_RULES vars[V_REPLY_LEADIN_RULES].global_val.l +#define USR_REPLY_LEADIN_RULES vars[V_REPLY_LEADIN_RULES].user_val.l +#define VAR_RESUB_RULES vars[V_RESUB_RULES].current_val.l +#define GLO_RESUB_RULES vars[V_RESUB_RULES].global_val.l +#define USR_RESUB_RULES vars[V_RESUB_RULES].user_val.l +#define VAR_THREAD_DISP_STYLE_RULES vars[V_THREAD_DISP_STYLE_RULES].current_val.l +#define GLO_THREAD_DISP_STYLE_RULES vars[V_THREAD_DISP_STYLE_RULES].global_val.l +#define VAR_THREAD_INDEX_STYLE_RULES vars[V_THREAD_INDEX_STYLE_RULES].current_val.l +#define GLO_THREAD_INDEX_STYLE_RULES vars[V_THREAD_INDEX_STYLE_RULES].global_val.l +#define VAR_SAVE_RULES vars[V_SAVE_RULES].current_val.l +#define GLO_SAVE_RULES vars[V_SAVE_RULES].global_val.l +#define USR_SAVE_RULES vars[V_SAVE_RULES].user_val.l +#define VAR_SMTP_RULES vars[V_SMTP_RULES].current_val.l +#define GLO_SMTP_RULES vars[V_SMTP_RULES].global_val.l +#define USR_SMTP_RULES vars[V_SMTP_RULES].user_val.l +#define VAR_SORT_RULES vars[V_SORT_RULES].current_val.l +#define GLO_SORT_RULES vars[V_SORT_RULES].global_val.l +#define USR_SORT_RULES vars[V_SORT_RULES].user_val.l +#define VAR_STARTUP_RULES vars[V_STARTUP_RULES].current_val.l +#define GLO_STARTUP_RULES vars[V_STARTUP_RULES].global_val.l +#define USR_STARTUP_RULES vars[V_STARTUP_RULES].user_val.l #ifndef _WINDOWS #define VAR_CHAR_SET vars[V_CHAR_SET].current_val.p #define GLO_CHAR_SET vars[V_CHAR_SET].global_val.p @@ -161,6 +204,8 @@ #define GLO_EDITOR vars[V_EDITOR].global_val.l #define VAR_SPELLER vars[V_SPELLER].current_val.p #define GLO_SPELLER vars[V_SPELLER].global_val.p +#define VAR_SPECIAL_TEXT vars[V_SPECIAL_TEXT].current_val.l +#define GLO_SPECIAL_TEXT vars[V_SPECIAL_TEXT].global_val.l #define VAR_FILLCOL vars[V_FILLCOL].current_val.p #define GLO_FILLCOL vars[V_FILLCOL].global_val.p #define VAR_DEADLETS vars[V_DEADLETS].current_val.p @@ -229,6 +274,8 @@ #define GLO_OPENING_SEP vars[V_OPENING_SEP].global_val.p #define VAR_ABOOK_FORMATS vars[V_ABOOK_FORMATS].current_val.l #define VAR_INDEX_FORMAT vars[V_INDEX_FORMAT].current_val.p +#define VAR_SLEEP vars[V_SLEEP].current_val.p +#define GLO_SLEEP vars[V_SLEEP].global_val.p #define VAR_OVERLAP vars[V_OVERLAP].current_val.p #define GLO_OVERLAP vars[V_OVERLAP].global_val.p #define VAR_MAXREMSTREAM vars[V_MAXREMSTREAM].current_val.p @@ -250,6 +297,10 @@ #define GLO_NEWS_ACTIVE_PATH vars[V_NEWS_ACTIVE_PATH].global_val.p #define VAR_NEWS_SPOOL_DIR vars[V_NEWS_SPOOL_DIR].current_val.p #define GLO_NEWS_SPOOL_DIR vars[V_NEWS_SPOOL_DIR].global_val.p +#ifndef _WINDOWS +#define VAR_MAILDIR_LOCATION vars[V_MAILDIR_LOCATION].current_val.p +#define GLO_MAILDIR_LOCATION vars[V_MAILDIR_LOCATION].global_val.p +#endif #define VAR_DISABLE_DRIVERS vars[V_DISABLE_DRIVERS].current_val.l #define VAR_DISABLE_AUTHS vars[V_DISABLE_AUTHS].current_val.l #define VAR_REMOTE_ABOOK_METADATA vars[V_REMOTE_ABOOK_METADATA].current_val.p @@ -456,6 +507,8 @@ #define GLO_SIGNATURE_FORE_COLOR vars[V_SIGNATURE_FORE_COLOR].global_val.p #define VAR_SIGNATURE_BACK_COLOR vars[V_SIGNATURE_BACK_COLOR].current_val.p #define GLO_SIGNATURE_BACK_COLOR vars[V_SIGNATURE_BACK_COLOR].global_val.p +#define VAR_SPECIAL_TEXT_FORE_COLOR vars[V_SPECIAL_TEXT_FORE_COLOR].current_val.p +#define VAR_SPECIAL_TEXT_BACK_COLOR vars[V_SPECIAL_TEXT_BACK_COLOR].current_val.p #define VAR_PROMPT_FORE_COLOR vars[V_PROMPT_FORE_COLOR].current_val.p #define VAR_PROMPT_BACK_COLOR vars[V_PROMPT_BACK_COLOR].current_val.p #define VAR_VIEW_HDR_COLORS vars[V_VIEW_HDR_COLORS].current_val.l @@ -671,6 +724,10 @@ */ #define Q_SUPP_LIMIT (4) #define Q_DEL_ALL (-10) +#define SVAR_SLEEP(ps,n,e,el) strtoval((ps)->VAR_SLEEP, \ + &(n), 0, 120, 0, (e), \ + (el), \ + "Sleep-Interval-Length") #define SVAR_OVERLAP(ps,n,e,el) strtoval((ps)->VAR_OVERLAP, \ &(n), 0, 20, 0, (e), \ (el), \ diff --git a/pith/conftype.h b/pith/conftype.h index c654f6c5..496a616f 100644 --- a/pith/conftype.h +++ b/pith/conftype.h @@ -59,6 +59,7 @@ typedef enum { V_PERSONAL_NAME = 0 , V_SAVED_MSG_NAME_RULE , V_FCC_RULE , V_SORT_KEY + , V_THREAD_SORT_KEY , V_AB_SORT_RULE , V_FLD_SORT_RULE , V_GOTO_DEFAULT_RULE @@ -70,6 +71,20 @@ typedef enum { V_PERSONAL_NAME = 0 , V_THREAD_MORE_CHAR , V_THREAD_EXP_CHAR , V_THREAD_LASTREPLY_CHAR + , V_THREAD_DISP_STYLE_RULES + , V_THREAD_INDEX_STYLE_RULES + , V_COMPOSE_RULES + , V_FORWARD_RULES + , V_INDEX_RULES + , V_KEY_RULES + , V_REPLACE_RULES + , V_REPLY_INDENT_RULES + , V_REPLY_LEADIN_RULES + , V_RESUB_RULES + , V_SAVE_RULES + , V_SMTP_RULES + , V_SORT_RULES + , V_STARTUP_RULES #ifndef _WINDOWS , V_CHAR_SET , V_OLD_CHAR_SET @@ -80,6 +95,7 @@ typedef enum { V_PERSONAL_NAME = 0 , V_EDITOR , V_SPELLER , V_FILLCOL + , V_SPECIAL_TEXT , V_REPLY_STRING , V_REPLY_INTRO , V_QUOTE_REPLACE_STRING @@ -108,6 +124,7 @@ typedef enum { V_PERSONAL_NAME = 0 , V_MARGIN , V_STATUS_MSG_DELAY , V_ACTIVE_MSG_INTERVAL + , V_SLEEP , V_MAILCHECK , V_MAILCHECKNONCURR , V_MAILDROPCHECK @@ -115,6 +132,9 @@ typedef enum { V_PERSONAL_NAME = 0 , V_NEWSRC_PATH , V_NEWS_ACTIVE_PATH , V_NEWS_SPOOL_DIR +#ifndef _WINDOWS + , V_MAILDIR_LOCATION +#endif , V_UPLOAD_CMD , V_UPLOAD_CMD_PREFIX , V_DOWNLOAD_CMD @@ -230,6 +250,8 @@ typedef enum { V_PERSONAL_NAME = 0 , V_INCUNSEEN_BACK_COLOR , V_SIGNATURE_FORE_COLOR , V_SIGNATURE_BACK_COLOR + , V_SPECIAL_TEXT_FORE_COLOR + , V_SPECIAL_TEXT_BACK_COLOR , V_PROMPT_FORE_COLOR , V_PROMPT_BACK_COLOR , V_HEADER_GENERAL_FORE_COLOR @@ -327,6 +349,7 @@ typedef enum { F_FULL_AUTO_EXPUNGE, F_EXPUNGE_MANUALLY, F_AUTO_READ_MSGS, + F_AUTO_READ_MSGS_RULES, F_AUTO_FCC_ONLY, F_READ_IN_NEWSRC_ORDER, F_SELECT_WO_CONFIRM, @@ -342,9 +365,11 @@ typedef enum { F_FORCE_ARROW, F_PRUNE_USES_ISO, F_ALT_ED_NOW, + F_IGNORE_SIZE, F_SHOW_DELAY_CUE, F_CANCEL_CONFIRM, F_AUTO_OPEN_NEXT_UNREAD, + F_AUTO_CIRCULAR_TAB, F_DISABLE_INDEX_LOCALE_DATES, F_SELECTED_SHOWN_BOLD, F_QUOTE_ALL_FROMS, @@ -388,10 +413,14 @@ typedef enum { F_PASS_C1_CONTROL_CHARS, F_SINGLE_FOLDER_LIST, F_VERTICAL_FOLDER_LIST, +#ifndef _WINDOWS + F_COURIER_FOLDER_LIST, +#endif F_TAB_CHK_RECENT, F_AUTO_REPLY_TO, F_VERBOSE_POST, F_FCC_ON_BOUNCE, + F_USE_DOMAIN_NAME, F_SEND_WO_CONFIRM, F_USE_SENDER_NOT_X, F_BLANK_KEYMENU, @@ -408,6 +437,7 @@ typedef enum { F_FIRST_SEND_FILTER_DFLT, F_ALWAYS_LAST_FLDR_DFLT, F_TAB_TO_NEW, + F_MARK_FOR_GROUP, F_MARK_FOR_CC, F_WARN_ABOUT_NO_SUBJECT, F_WARN_ABOUT_NO_FCC, @@ -443,6 +473,7 @@ typedef enum { F_VIEW_SEL_ATTACH, F_VIEW_SEL_URL, F_VIEW_SEL_URL_HOST, + F_VIEW_LONG_URL, F_SCAN_ADDR, F_FORCE_ARROWS, F_PREFER_PLAIN_TEXT, @@ -501,11 +532,13 @@ typedef enum { F_MAILDROPS_PRESERVE_STATE, F_EXPOSE_HIDDEN_CONFIG, F_ALT_COMPOSE_MENU, + F_ALT_REPLY_MENU, F_ALT_ROLE_MENU, F_ALWAYS_SPELL_CHECK, F_QUELL_TIMEZONE, F_QUELL_USERAGENT, F_COLOR_LINE_IMPORTANT, + F_ENHANCED_THREAD, F_SLASH_COLL_ENTIRE, F_ENABLE_FULL_HDR_AND_TEXT, F_QUELL_FULL_HDR_RESET, @@ -713,5 +746,6 @@ typedef struct smime_stuff { /* exported protoypes */ +#define DF_THREAD_SORT_KEY "thread" #endif /* PITH_CONFTYPE_INCLUDED */ diff --git a/pith/detoken.c b/pith/detoken.c index 6f0584ab..0546cf04 100644 --- a/pith/detoken.c +++ b/pith/detoken.c @@ -24,7 +24,7 @@ static char rcsid[] = "$Id: detoken.c 769 2007-10-24 00:15:40Z hubert@u.washingt #include "../pith/reply.h" #include "../pith/mailindx.h" #include "../pith/options.h" - +#include "../pith/rules.h" /* * Hook to read signature from local file @@ -90,6 +90,8 @@ detoken(ACTION_S *role, ENVELOPE *env, int prenewlines, int postnewlines, if(is_sig){ /* + * First we check if there is a rule about signatures, if there is + * use it, otherwise keep going and do the following: * If role->litsig is set, we use it; * Else, if VAR_LITERAL_SIG is set, we use that; * Else, if role->sig is set, we use that; @@ -103,14 +105,25 @@ detoken(ACTION_S *role, ENVELOPE *env, int prenewlines, int postnewlines, * there is no reason to mix them, so we don't provide support to * do so. */ - if(role && role->litsig) - literal_sig = role->litsig; - else if(ps_global->VAR_LITERAL_SIG) - literal_sig = ps_global->VAR_LITERAL_SIG; - else if(role && role->sig) - sigfile = role->sig; - else - sigfile = ps_global->VAR_SIGNATURE_FILE; + { RULE_RESULT *rule; + rule = get_result_rule(V_COMPOSE_RULES, FOR_COMPOSE, env); + if (rule){ + sigfile = cpystr(rule->result); + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + } + if (!sigfile){ + if(role && role->litsig) + literal_sig = role->litsig; + else if(ps_global->VAR_LITERAL_SIG) + literal_sig = ps_global->VAR_LITERAL_SIG; + else if(role && role->sig) + sigfile = role->sig; + else + sigfile = ps_global->VAR_SIGNATURE_FILE; + } } else if(role && role->template) sigfile = role->template; @@ -301,7 +314,7 @@ top: } } } - else if(pt->what_for & FOR_REPLY_INTRO) + else if(pt->what_for & (FOR_REPLY_INTRO | FOR_RULE)) repl = get_reply_data(env, role, pt->ctype, subbuf, sizeof(subbuf)-1); diff --git a/pith/filter.c b/pith/filter.c index 692d6319..32309882 100644 --- a/pith/filter.c +++ b/pith/filter.c @@ -46,6 +46,7 @@ static char rcsid[] = "$Id: filter.c 1266 2009-07-14 18:39:12Z hubert@u.washingt #include "../pith/conf.h" #include "../pith/store.h" #include "../pith/color.h" +#include "../pith/osdep/color.h" #include "../pith/escapes.h" #include "../pith/pipe.h" #include "../pith/status.h" @@ -1375,14 +1376,24 @@ gf_b64_binary(FILTER_S *f, int flg) register unsigned char t = f->t; register int n = (int) f->n; register int state = f->f1; + register unsigned char lastc; while(GF_GETC(f, c)){ + lastc = c; + if(f->f2){ + GF_PUTC(f->next, c); + continue; + } + if(state){ state = 0; if (c != '=') { - gf_error("Illegal '=' in base64 text"); - /* NO RETURN */ + f->f2++; + GF_PUTC(f->next, c); + q_status_message(SM_ORDER,3,3, + _("Warning: Illegal '=' in base64 text")); + continue; } } @@ -1399,8 +1410,11 @@ gf_b64_binary(FILTER_S *f, int flg) break; default: /* impossible quantum position */ - gf_error("Internal base64 decoder error"); - /* NO RETURN */ + f->f2++; + GF_PUTC(f->next, lastc); + q_status_message(SM_ORDER,3,3, + _("Warning: Internal base64 decode error")); + break; } } } @@ -1441,6 +1455,7 @@ gf_b64_binary(FILTER_S *f, int flg) dprint((9, "-- gf_reset b64_binary\n")); f->n = 0L; /* quantum position */ f->f1 = 0; /* state holder: equal seen? */ + f->f2 = 0; /* No errors when we start */ } } @@ -7599,6 +7614,7 @@ html_element_comment(FILTER_S *f, char *s) char *p, buf[MAILTMPLEN]; ADDRESS *adr; extern char datestamp[]; + extern char plevstamp[]; if(!strcmp(s = removing_quotes(s + 4), "ALPINE_VERSION")){ p = ALPINE_VERSION; @@ -7612,6 +7628,9 @@ html_element_comment(FILTER_S *f, char *s) else if(!strcmp(s, "ALPINE_COMPILE_DATE")){ p = datestamp; } + else if(!strcmp(s, "ALPINE_PATCHLEVEL")){ + p = plevstamp; + } else if(!strcmp(s, "ALPINE_TODAYS_DATE")){ rfc822_date(p = buf); } @@ -9167,6 +9186,11 @@ typedef struct wrap_col_s { margin_r, indent; char special[256]; + long curlinenum; /* current line number */ + int curqstrpos; /* current position in quote string */ + long linenum; /* line number */ + long qstrlen; /* multiples of 100 */ + char **qstrln; /* qstrln[i] = quote string line i - 1 */ } WRAP_S; #define WRAP_MARG_L(F) (((WRAP_S *)(F)->opt)->margin_l) @@ -9208,6 +9232,12 @@ typedef struct wrap_col_s { #define WRAP_COLOR(F) (((WRAP_S *)(F)->opt)->color) #define WRAP_COLOR_SET(F) ((WRAP_COLOR(F)) && (WRAP_COLOR(F)->fg[0])) #define WRAP_SPACES(F) (((WRAP_S *)(F)->opt)->spaces) +#define WRAP_CURLINE(F) (((WRAP_S *)(F)->opt)->curlinenum) +#define WRAP_CURPOS(F) (((WRAP_S *)(F)->opt)->curqstrpos) +#define WRAP_LINENUM(F) (((WRAP_S *)(F)->opt)->linenum) +#define WRAP_QSTRLEN(F) (((WRAP_S *)(F)->opt)->qstrlen) +#define WRAP_QSTRN(F) (((WRAP_S *)(F)->opt)->qstrln) +#define WRAP_QSTR(F, N) (((WRAP_S *)(F)->opt)->qstrln[(N)]) #define WRAP_PUTC(F,C,W) { \ if((F)->linep == WRAP_LASTC(F)){ \ size_t offset = (F)->linep - (F)->line; \ @@ -9285,6 +9315,8 @@ gf_wrap(FILTER_S *f, int flg) case CCR : /* CRLF or CR in text ? */ state = BOL; /* either way, handle start */ + WRAP_CURLINE(f)++; + WRAP_CURPOS(f) = 0; if(WRAP_FLOW(f)){ /* wrapped line? */ if(f->f2 == 0 && WRAP_SPC_LEN(f) && WRAP_TRL_SPC(f)){ @@ -9378,7 +9410,11 @@ gf_wrap(FILTER_S *f, int flg) case BOL : if(WRAP_FLOW(f)){ - if(c == '>'){ + if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) + && WRAP_QSTR(f, WRAP_CURLINE(f)) + && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] + && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] == c){ + WRAP_CURPOS(f)++; WRAP_FL_QC(f) = 1; /* init it */ state = FL_QLEV; /* go collect it */ } @@ -9392,7 +9428,16 @@ gf_wrap(FILTER_S *f, int flg) } /* quote level change implies new paragraph */ - if(WRAP_FL_QD(f)){ + if (WRAP_CURLINE(f) > 0 + && WRAP_CURLINE(f) < WRAP_QSTRLEN(f) + && (WRAP_QSTR(f, WRAP_CURLINE(f)) != NULL + || WRAP_QSTR(f, WRAP_CURLINE(f) - 1) != NULL) + && ((WRAP_QSTR(f, WRAP_CURLINE(f)) != NULL && + WRAP_QSTR(f, WRAP_CURLINE(f) - 1) == NULL) + || (WRAP_QSTR(f, WRAP_CURLINE(f)) == NULL && + WRAP_QSTR(f, WRAP_CURLINE(f) - 1) != NULL) + || strcmp(WRAP_QSTR(f, WRAP_CURLINE(f)), + WRAP_QSTR(f, WRAP_CURLINE(f) - 1)))){ WRAP_FL_QD(f) = 0; if(WRAP_HARD(f) == 0){ WRAP_HARD(f) = 1; @@ -9444,8 +9489,12 @@ gf_wrap(FILTER_S *f, int flg) break; case FL_QLEV : - if(c == '>'){ /* another level */ - WRAP_FL_QC(f)++; + if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) + && WRAP_QSTR(f, WRAP_CURLINE(f)) + && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] + && WRAP_QSTR(f, WRAP_CURLINE(f))[WRAP_CURPOS(f)] == c){ + WRAP_CURPOS(f)++; + WRAP_FL_QC(f)++; /* another level */ } else { /* if EMBEDed, process it and return here */ @@ -9457,7 +9506,16 @@ gf_wrap(FILTER_S *f, int flg) } /* quote level change signals new paragraph */ - if(WRAP_FL_QC(f) != WRAP_FL_QD(f)){ + if (WRAP_CURLINE(f) > 0 + && WRAP_CURLINE(f) < WRAP_QSTRLEN(f) + && (WRAP_QSTR(f, WRAP_CURLINE(f)) + || WRAP_QSTR(f, WRAP_CURLINE(f) - 1)) + && ((WRAP_QSTR(f, WRAP_CURLINE(f)) && + !WRAP_QSTR(f, WRAP_CURLINE(f) - 1)) + || (!WRAP_QSTR(f, WRAP_CURLINE(f)) && + WRAP_QSTR(f, WRAP_CURLINE(f) - 1)) + || strcmp(WRAP_QSTR(f, WRAP_CURLINE(f)), + WRAP_QSTR(f, WRAP_CURLINE(f) - 1)))){ WRAP_FL_QD(f) = WRAP_FL_QC(f); if(WRAP_HARD(f) == 0){ /* add hard newline */ WRAP_HARD(f) = 1; /* hard newline */ @@ -9514,6 +9572,13 @@ gf_wrap(FILTER_S *f, int flg) state = FL_SIG; break; + case ' ' : /* what? */ + if (WRAP_CURLINE(f) < WRAP_QSTRLEN(f) + && WRAP_QSTR(f, WRAP_CURLINE(f))){ + WRAP_SPC_LEN(f)++; + so_writec(' ', WRAP_SPACES(f)); + } + default : /* something else */ state = DFL; goto case_dfl; /* handle c like DFL */ @@ -9530,7 +9595,7 @@ gf_wrap(FILTER_S *f, int flg) &eob); /* note any embedded*/ wrap_eol(f, 1, &ip, &eib, &op, &eob); /* plunk down newline */ - wrap_bol(f, 1, 1, &ip, &eib, + wrap_bol(f, 1, WRAP_FLOW(f), &ip, &eib, &op, &eob); /* write any prefix */ } @@ -10027,7 +10092,7 @@ gf_wrap(FILTER_S *f, int flg) wrap_flush_embed(f, &ip, &eib, &op, &eob); wrap_eol(f, 1, &ip, &eib, &op, &eob); /* plunk down newline */ - wrap_bol(f,1,1, &ip, &eib, &op, + wrap_bol(f,1,WRAP_FLOW(f), &ip, &eib, &op, &eob); /* write any prefix */ } @@ -10100,6 +10165,13 @@ gf_wrap(FILTER_S *f, int flg) if(WRAP_COLOR(f)) free_color_pair(&WRAP_COLOR(f)); + { long i; + for (i = 0L; i < WRAP_QSTRLEN(f); i++) + if (WRAP_QSTR(f,i)) + fs_give((void **) &(WRAP_QSTR(f,i))); + fs_give((void **)&WRAP_QSTRN(f)); + } + fs_give((void **) &f->line); /* free temp line buffer */ so_give(&WRAP_SPACES(f)); fs_give((void **) &f->opt); /* free wrap widths struct */ @@ -10450,7 +10522,8 @@ wrap_quote_insert(FILTER_S *f, unsigned char **ipp, unsigned char **eibp, { int j, i; COLOR_PAIR *col = NULL; - char *prefix = NULL, *last_prefix = NULL; + char *prefix = NULL, *last_prefix = NULL, *wrap_qstr = NULL; + int level = 0, oldj, len; if(ps_global->VAR_QUOTE_REPLACE_STRING){ get_pair(ps_global->VAR_QUOTE_REPLACE_STRING, &prefix, &last_prefix, 0, 0); @@ -10459,10 +10532,22 @@ wrap_quote_insert(FILTER_S *f, unsigned char **ipp, unsigned char **eibp, last_prefix = NULL; } } + + if(WRAP_CURLINE(f) < WRAP_QSTRLEN(f) && WRAP_QSTR(f, WRAP_CURLINE(f))) + wrap_qstr = cpystr(WRAP_QSTR(f, WRAP_CURLINE(f))); + len = wrap_qstr ? strlen(wrap_qstr) : 0; - for(j = 0; j < WRAP_FL_QD(f); j++){ + for (j = wrap_qstr && *wrap_qstr == ' ' ? 1 : 0; + j < len && isspace((unsigned char)wrap_qstr[j]); j++){ + GF_PUTC_GLO(f->next, wrap_qstr[j]); + f->n += ((wrap_qstr[j] == TAB) ? (~f->n & 0x07) + 1 : 1); + } + + for(; j < len && level < len; level++){ + oldj = j; + j = next_level_quote(wrap_qstr, (char **)NULL, j, WRAP_FLOW(f)); if(WRAP_USE_CLR(f)){ - if((j % 3) == 0 + if((level % 3) == 0 && ps_global->VAR_QUOTE1_FORE_COLOR && ps_global->VAR_QUOTE1_BACK_COLOR && (col = new_color_pair(ps_global->VAR_QUOTE1_FORE_COLOR, @@ -10470,7 +10555,7 @@ wrap_quote_insert(FILTER_S *f, unsigned char **ipp, unsigned char **eibp, && pico_is_good_colorpair(col)){ GF_COLOR_PUTC(f, col); } - else if((j % 3) == 1 + else if((level % 3) == 1 && ps_global->VAR_QUOTE2_FORE_COLOR && ps_global->VAR_QUOTE2_BACK_COLOR && (col = new_color_pair(ps_global->VAR_QUOTE2_FORE_COLOR, @@ -10478,7 +10563,7 @@ wrap_quote_insert(FILTER_S *f, unsigned char **ipp, unsigned char **eibp, && pico_is_good_colorpair(col)){ GF_COLOR_PUTC(f, col); } - else if((j % 3) == 2 + else if((level % 3) == 2 && ps_global->VAR_QUOTE3_FORE_COLOR && ps_global->VAR_QUOTE3_BACK_COLOR && (col = new_color_pair(ps_global->VAR_QUOTE3_FORE_COLOR, @@ -10492,43 +10577,60 @@ wrap_quote_insert(FILTER_S *f, unsigned char **ipp, unsigned char **eibp, } } + if (j > 1 && wrap_qstr[j-1] == ' ') + j -= 1; + if(!WRAP_LV_FLD(f)){ if(!WRAP_FOR_CMPS(f) && ps_global->VAR_QUOTE_REPLACE_STRING && prefix){ for(i = 0; prefix[i]; i++) GF_PUTC_GLO(f->next, prefix[i]); - f->n += utf8_width(prefix); - } - else if(ps_global->VAR_REPLY_STRING - && (!strcmp(ps_global->VAR_REPLY_STRING, ">") - || !strcmp(ps_global->VAR_REPLY_STRING, "\">\""))){ - GF_PUTC_GLO(f->next, '>'); - f->n += 1; + f->n += utf8_widthis(prefix); } else{ - GF_PUTC_GLO(f->next, '>'); - GF_PUTC_GLO(f->next, ' '); - f->n += 2; + for (i = oldj; i < j; i++) + GF_PUTC_GLO(f->next, wrap_qstr[i]); + f->n += j - oldj; } } else{ - GF_PUTC_GLO(f->next, '>'); - f->n += 1; - } + for (i = oldj; i < j; i++) + GF_PUTC_GLO(f->next, wrap_qstr[i]); + f->n += j - oldj; + } + for (i = j; isspace((unsigned char)wrap_qstr[i]); i++); + if(!wrap_qstr[i]){ + f->n += i - j; + for (; j < i; j++) + GF_PUTC_GLO(f->next, ' '); + } + else{ + if((WRAP_LV_FLD(f) + || !ps_global->VAR_QUOTE_REPLACE_STRING || !prefix) + || !ps_global->VAR_REPLY_STRING + || (strcmp(ps_global->VAR_REPLY_STRING, ">") + && strcmp(ps_global->VAR_REPLY_STRING, "\">\""))){ + GF_PUTC_GLO(f->next, ' '); + f->n += 1; + } + } + for (; isspace((unsigned char)wrap_qstr[j]); j++); } if(j && WRAP_LV_FLD(f)){ GF_PUTC_GLO(f->next, ' '); f->n++; } - else if(j && last_prefix){ + else if(j && !value_is_space(wrap_qstr) && last_prefix){ for(i = 0; last_prefix[i]; i++) GF_PUTC_GLO(f->next, last_prefix[i]); - f->n += utf8_width(last_prefix); + f->n += utf8_widthis(last_prefix); } if(prefix) fs_give((void **)&prefix); if(last_prefix) fs_give((void **)&last_prefix); + if (wrap_qstr) + fs_give((void **)&wrap_qstr); return 0; } @@ -10560,6 +10662,12 @@ gf_wrap_filter_opt(int width, int width_max, int *margin, int indent, int flags) wrap->hdr_color = (GFW_HDRCOLOR & flags) == GFW_HDRCOLOR; wrap->for_compose = (GFW_FORCOMPOSE & flags) == GFW_FORCOMPOSE; wrap->handle_soft_hyphen = (GFW_SOFTHYPHEN & flags) == GFW_SOFTHYPHEN; + wrap->curlinenum = 0L; + wrap->curqstrpos = 0; + wrap->linenum = 0L; + wrap->qstrlen = 100L; + wrap->qstrln = (char **) fs_get(100*sizeof(char *)); + memset(wrap->qstrln, 0, 100*sizeof(char *)); return((void *) wrap); } @@ -11003,7 +11111,215 @@ typedef struct _linetest_s { } \ } +#define ADD_QUOTE_STRING(F) { \ + int len = tmp_20k_buf[0] ? strlen(tmp_20k_buf) + 1 : 0; \ + FILTER_S *fltr; \ + \ + for(fltr = (F); fltr && fltr->f != gf_wrap; fltr = fltr->next); \ + if (fltr){ \ + if (WRAP_LINENUM(fltr) >= WRAP_QSTRLEN(fltr)){ \ + fs_resize((void **)&WRAP_QSTRN(fltr), \ + (WRAP_QSTRLEN(fltr) + 100) * sizeof(char *)); \ + memset(WRAP_QSTRN(fltr)+WRAP_QSTRLEN(fltr), 0, \ + 100*sizeof(char*)); \ + WRAP_QSTRLEN(fltr) += 100L; \ + } \ + if (len){ \ + WRAP_QSTR(fltr, WRAP_LINENUM(fltr)) = \ + (char *) fs_get(len*sizeof(char)); \ + WRAP_QSTR(fltr, WRAP_LINENUM(fltr)) = cpystr(tmp_20k_buf);\ + } \ + WRAP_LINENUM(fltr)++; \ + } \ +} + +int end_of_line(char *line) +{ + int i; + + for(i= 0; line && line[i]; i++){ + if((line[i] == '\015' && line[i+1] == '\012') || line[i] == '\012') + break; + } + return i; +} + +/* This macro is used in gf_quote_test. It receives a return code + from a filter. All filters that will print something must send + return code 0, except color_a_quote which must send return code + 1 + */ + +#define GF_ADD_QUOTED_LINE(F, line) \ +{ \ + LT_INS_S *ins = NULL, *insp; \ + int done; \ + char *gline, *cline;\ + unsigned char ch;\ + register char *cp;\ + register int l;\ + \ + for (gline = cline = line; gline && cline; ){\ + if(cline = strchr(gline,'\012'))\ + *cline = '\0';\ + done = (*((LINETEST_S *) (F)->opt)->f)((F)->n++, gline, &ins,\ + ((LINETEST_S *) (F)->opt)->local);\ + if (done < 2){ \ + if(done == 1)\ + ADD_QUOTE_STRING((F));\ + for(insp = ins, cp = gline; *cp ; ){\ + if(insp && cp == insp->where){\ + if(insp->len > 0){ \ + for(l = 0; l < insp->len; l++){\ + ch = (unsigned char) insp->text[l];\ + GF_PUTC((F)->next, ch);\ + }\ + insp = insp->next;\ + continue; \ + } else if(insp->len < 0){ \ + cp -= insp->len; \ + insp = insp->next; \ + continue; \ + } \ + }\ + GF_PUTC((F)->next, *cp);\ + cp++;\ + }\ + while(insp){\ + for(l = 0; l < insp->len; l++){\ + ch = (unsigned char) insp->text[l];\ + GF_PUTC((F)->next, ch);\ + }\ + insp = insp->next;\ + }\ + gf_line_test_free_ins(&ins);\ + if(cline){ \ + *cline = '\012';\ + gline += cline - gline + 1;\ + }\ + GF_PUTC((F)->next, '\015');\ + GF_PUTC((F)->next, '\012');\ + }\ + }\ +} +/* test second line of old line first */ +#define SECOND_LINE_QUOTE_TEST(line, F) \ +{\ + *p = '\0';\ + i = end_of_line((F)->oldline); \ + if (((F)->oldline)[i]){\ + i += (((F)->oldline)[i] == '\015') ? 2 : 1;\ + line = (F)->oldline + i;\ + i = end_of_line(line); \ + if(line[i])\ + line[i] = '\0'; \ + }\ + for (i = 0; ((F)->line) \ + && (i < LINE_TEST_BLOCK) \ + && (i < SIZEOF_20KBUF)\ + && ((F)->line)[i] \ + && (((F)->line)[i] != '\015')\ + && (((F)->line)[i] != '\012')\ + && (tmp_20k_buf[i] = ((F)->line)[i]); i++);\ + tmp_20k_buf[i] = '\0';\ + GF_ADD_QUOTED_LINE((F), line);\ +} + +#define FIRST_LINE_QUOTE_TEST(line, F)\ +{\ + *p = '\0';\ + line = (F)->line;\ + if ((F)->oldline)\ + fs_give((void **)&(F)->oldline);\ + (F)->oldline = cpystr(line);\ + i = end_of_line(line); \ + if (line[i]){ \ + j = (line[i] == '\015') ? 2 : 1;\ + line[i] = '\0'; \ + i += j; \ + }\ + for (j = 0; ((F)->line) \ + && ((i + j) < LINE_TEST_BLOCK) \ + && (j < SIZEOF_20KBUF) \ + && ((F)->line)[i + j] \ + && (((F)->line)[i + j] != '\015')\ + && (((F)->line)[i + j] != '\012')\ + && (tmp_20k_buf[j] = ((F)->line)[i + j]); j++);\ + tmp_20k_buf[j] = '\0';\ + GF_ADD_QUOTED_LINE((F), line);\ +} + + +void +gf_quote_test(f, flg) + FILTER_S *f; + int flg; +{ + register char *p = f->linep; + register char *eobuf = GF_LINE_TEST_EOB(f); + char *line = NULL; + int i, j; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + GF_LINE_TEST_ADD(f, c); + if(c == '\012') + state++; + if(state == 2){ /* two full lines read */ + state = 0; + + /* first process the second line of an old line */ + if (f->oldline && f->oldline[0]) + SECOND_LINE_QUOTE_TEST(line, f); + + /* now we process the first line */ + FIRST_LINE_QUOTE_TEST(line, f); + + p = f->line; + } + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + /* first process the second line of an old line */ + if (f->oldline && f->oldline[0]) + SECOND_LINE_QUOTE_TEST(line, f); + + /* now we process the first line */ + FIRST_LINE_QUOTE_TEST(line, f); + /* We are out of data. In this case we have processed the second + * line of an oldline, then the first line of a line, but we need + * to process the second line of the given line. We do this by + * processing it now!. + */ + if (line[i]){ + tmp_20k_buf[0] = '\0'; /* No next line */ + GF_ADD_QUOTED_LINE(f, line+i); + } + + fs_give((void **) &f->oldline); /* free old line buffer */ + fs_give((void **) &f->line); /* free line buffer */ + fs_give((void **) &f->opt); /* free test struct */ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + f->f1 = 0; /* state */ + f->n = 0L; /* line number */ + f->f2 = LINE_TEST_BLOCK; /* size of alloc'd line */ + f->line = p = (char *) fs_get(f->f2 * sizeof(char)); + } + + f->linep = p; +} /* * this simple filter accumulates characters until a newline, offers it diff --git a/pith/filter.h b/pith/filter.h index 9916803d..e4021f23 100644 --- a/pith/filter.h +++ b/pith/filter.h @@ -216,6 +216,7 @@ void gf_prepend_editorial(FILTER_S *, int); void *gf_prepend_editorial_opt(prepedtest_t, char *); void gf_nvtnl_local(FILTER_S *, int); void gf_local_nvtnl(FILTER_S *, int); +void gf_quote_test(FILTER_S *, int); void *gf_url_hilite_opt(URL_HILITE_S *, HANDLE_S **, int); diff --git a/pith/filttype.h b/pith/filttype.h index 21a1bec5..684299ca 100644 --- a/pith/filttype.h +++ b/pith/filttype.h @@ -35,6 +35,8 @@ typedef struct filter_s { /* type to hold data for filter function */ unsigned char t; /* temporary char */ char *line; /* place for temporary storage */ char *linep; /* pointer into storage space */ + char *oldline; /* the previous line to "line" */ + char *oldlinep; /* the previous line to "line" */ void *opt; /* optional per instance data */ void *data; /* misc internal data pointer */ unsigned char queue[1 + GF_MAXBUF]; diff --git a/pith/flag.c b/pith/flag.c index b1bbf9c4..cf0ea39a 100644 --- a/pith/flag.c +++ b/pith/flag.c @@ -594,14 +594,16 @@ set_lflag(MAILSTREAM *stream, MSGNO_S *msgs, long int n, int f, int v) was_invisible = (pelt->hidden || pelt->colhid) ? 1 : 0; + thrd = fetch_thread(stream, rawno); + if((chk_thrd_cnt = ((msgs->visible_threads >= 0L) && THRD_INDX_ENABLED() && (f & MN_HIDE) && (pelt->hidden != v))) != 0){ thrd = fetch_thread(stream, rawno); if(thrd && thrd->top){ - if(thrd->top == thrd->rawno) + if(top_thread(stream, thrd->top) == thrd->rawno) topthrd = thrd; else - topthrd = fetch_thread(stream, thrd->top); + topthrd = fetch_thread(stream, top_thread(stream, thrd->top)); } if(topthrd){ diff --git a/pith/imap.c b/pith/imap.c index ea4c5b1f..f0b93c9a 100644 --- a/pith/imap.c +++ b/pith/imap.c @@ -967,8 +967,18 @@ imap_get_passwd(MMLOGIN_S *m_list, char *passwd, char *user, STRLIST_S *hostlist && !strcmp(user, l->user) && l->altflag == altflag){ if(passwd){ + if(l->invalidpwd == 0){ strncpy(passwd, l->passwd, NETMAXPASSWD); passwd[NETMAXPASSWD-1] = '\0'; + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 4, + "Failed to login!. Re-enter password."); + dprint((9, "imap_get_passwd: reseting password due to login failure.\n")); + dprint((10, "imap_get_passwd: Old passwd=\"%s\"\n", + passwd ? passwd : "?")); + return FALSE; + } } dprint((9, "imap_get_passwd: match\n")); dprint((10, "imap_get_passwd: trying passwd=\"%s\"\n", @@ -1016,6 +1026,7 @@ imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist, (*l)->altflag = altflag; (*l)->ok_novalidate = ok_novalidate; (*l)->warned = warned; + (*l)->invalidpwd = 0; /* assume correct password for now */ if(!(*l)->user) (*l)->user = cpystr(user); diff --git a/pith/imap.h b/pith/imap.h index 86a0b533..a57400b9 100644 --- a/pith/imap.h +++ b/pith/imap.h @@ -35,6 +35,7 @@ typedef struct _mmlogin_s { unsigned altflag:1; unsigned ok_novalidate:1; unsigned warned:1; + unsigned invalidpwd:1; /* password is invalid, assume valid */ STRLIST_S *hosts; struct _mmlogin_s *next; } MMLOGIN_S; diff --git a/pith/indxtype.h b/pith/indxtype.h index ee01a9bb..031e5ff5 100644 --- a/pith/indxtype.h +++ b/pith/indxtype.h @@ -76,12 +76,15 @@ typedef enum {iNothing, iStatus, iFStatus, iIStatus, iSIStatus, iKey, iKeyInit, iPrefDate, iPrefTime, iPrefDateTime, iCurPrefDate, iCurPrefTime, iCurPrefDateTime, - iSize, iSizeComma, iSizeNarrow, iDescripSize, + iSize, iSizeComma, iSizeNarrow, iDescripSize, iSizeThread, iNewsAndTo, iToAndNews, iNewsAndRecips, iRecipsAndNews, iFromTo, iFromToNotNews, iFrom, iTo, iSender, iCc, iNews, iRecips, iCurNews, iArrow, iMailbox, iAddress, iInit, iCursorPos, iDay2Digit, iMon2Digit, iYear2Digit, + iFolder, iFlag, iCollection, iRole, iProcid, iScreen, iPkey, + iNick, iAddressTo, iAddressCc, iAddressRecip, iBcc, iLcc, + iFfrom, iFadd, iSTime, iKSize, iRoleNick, iNewLine, iHeader, iText, @@ -103,15 +106,26 @@ typedef struct index_parse_tokens { /* these are flags for the what_for field in INDEX_PARSE_T */ -#define FOR_NOTHING 0x00 -#define FOR_INDEX 0x01 -#define FOR_REPLY_INTRO 0x02 -#define FOR_TEMPLATE 0x04 /* or for signature */ -#define FOR_FILT 0x08 -#define DELIM_USCORE 0x10 -#define DELIM_PAREN 0x20 -#define DELIM_COLON 0x40 - +#define FOR_NOTHING 0x00000 +#define FOR_INDEX 0x00001 +#define FOR_REPLY_INTRO 0x00002 +#define FOR_TEMPLATE 0x00004 /* or for signature */ +#define FOR_FILT 0x00008 +#define DELIM_USCORE 0x00010 +#define DELIM_PAREN 0x00020 +#define DELIM_COLON 0x00040 +#define FOR_FOLDER 0x00080 /* for rules */ +#define FOR_RULE 0x00100 /* for rules */ +#define FOR_TRIM 0x00200 /* for rules */ +#define FOR_RESUB 0x00400 /* for rules */ +#define FOR_REPLACE 0x00800 /* for rules */ +#define FOR_SORT 0x01000 /* for rules */ +#define FOR_FLAG 0x02000 /* for rules */ +#define FOR_COMPOSE 0x04000 /* for rules */ +#define FOR_THREAD 0x08000 /* for rules */ +#define FOR_STARTUP 0x10000 /* for rules */ +#define FOR_KEY 0x20000 /* for rules */ +#define FOR_SAVE 0x40000 /* for rules */ #define DEFAULT_REPLY_INTRO "default" diff --git a/pith/init.c b/pith/init.c index d7942dcb..69379bf8 100644 --- a/pith/init.c +++ b/pith/init.c @@ -408,6 +408,9 @@ get_mail_list(CONTEXT_S *list_cntxt, char *folder_base) && stricmp(filename, folder_base)){ #else if(strncmp(filename, folder_base, folder_base_len) == 0 +#ifndef _WINDOWS + && filename[folder_base_len] != list_cntxt->dir->delim +#endif && strcmp(filename, folder_base)){ #endif #endif diff --git a/pith/mailcap.c b/pith/mailcap.c index 34dce329..44285a8a 100644 --- a/pith/mailcap.c +++ b/pith/mailcap.c @@ -53,6 +53,7 @@ typedef struct mcap_entry { int needsterminal; char *contenttype; char *command; + char *nametemplate; char *testcommand; char *label; /* unused */ char *printcommand; /* unused */ @@ -213,6 +214,9 @@ mc_init(void) if(mc->printcommand) dprint((11, " printcommand: %s", mc->printcommand ? mc->printcommand : "?")); + if(mc->nametemplate) + dprint((11, " nametemplate: %s", + mc->nametemplate ? mc->nametemplate : "?")); dprint((11, " needsterminal %d\n", mc->needsterminal)); } } @@ -488,6 +492,11 @@ mc_build_entry(char **tokens) dprint((9, "mailcap: printcommand=%s\n", mc->printcommand ? mc->printcommand : "?")); } + else if(arg && !strucmp(*tokens, "nametemplate")){ + mc->nametemplate = arg; + dprint((9, "mailcap: nametemplate=%s\n", + arg ? arg : "?")); + } else if(arg && !strucmp(*tokens, "compose")){ /* not used */ dprint((9, "mailcap: not using compose=%s\n", @@ -974,3 +983,60 @@ mailcap_free(void) mail_free_stringlist(&MailcapData.raw); mc_free_entry(&MailcapData.head); } + +char * +mc_template(char *tmp_file, BODY *body, int chk_extension) +{ + MailcapEntry *mc; + int quoted = 0; + char *s, *to, *namefile = NULL; + + mc = mc_get_command(body->type, body->subtype, body, chk_extension, NULL); + if(!mc || !mc->nametemplate || !tmp_file) + return tmp_file; + + /* remove extension if requested for a specific extension */ + if(mc->nametemplate && tmp_file && (s = strrchr(tmp_file, '.')) != NULL + && strchr(s, C_FILESEP) == NULL) + *s = '\0'; + + to = tmp_20k_buf; + if((s = strrchr(tmp_file, C_FILESEP)) != NULL){ + *s++ = '\0'; + sstrncpy(&to, tmp_file, SIZEOF_20KBUF-(to-tmp_20k_buf)); + if(to-tmp_20k_buf < SIZEOF_20KBUF) + *to++ = C_FILESEP; + namefile = s; + } + + for(s = mc->nametemplate; *s; s++) + if(quoted){ + quoted = 0; + switch(*s){ + case '%': + if(to-tmp_20k_buf < SIZEOF_20KBUF) + *to++ = '%'; + break; + + case 's': + sstrncpy(&to, namefile ? namefile : tmp_file, SIZEOF_20KBUF-(to-tmp_20k_buf)); + break; + + default: + dprint((9, + "Ignoring unercognized format code in nametemplate: %%%c\n", *s )); + break; + } + } + else if(*s == '%') + quoted = 1; + else if(to-tmp_20k_buf < SIZEOF_20KBUF) + *to++ = *s; + + *to++ = '\0'; + + fs_give((void **)&tmp_file); + tmp_file = cpystr(tmp_20k_buf); + return tmp_file; +} + diff --git a/pith/mailcap.h b/pith/mailcap.h index 25ccd115..c9f7a256 100644 --- a/pith/mailcap.h +++ b/pith/mailcap.h @@ -29,6 +29,7 @@ char *mc_conf_path(char *, char *, char *, int, char *); int mailcap_can_display(int, char *, BODY *, int); MCAP_CMD_S *mailcap_build_command(int, char *, BODY *, char *, int *, int); void mailcap_free(void); +char *mc_template(char *, BODY *, int); /* currently mandatory to implement stubs */ diff --git a/pith/mailcmd.c b/pith/mailcmd.c index 78ba98ad..3838c65d 100644 --- a/pith/mailcmd.c +++ b/pith/mailcmd.c @@ -39,6 +39,7 @@ static char rcsid[] = "$Id: mailcmd.c 1142 2008-08-13 17:22:21Z hubert@u.washing #include "../pith/ablookup.h" #include "../pith/search.h" #include "../pith/charconv/utf8.h" +#include "../pith/rules.h" #ifdef _WINDOWS #include "../pico/osdep/mswin.h" @@ -665,6 +666,7 @@ do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp, strncpy(ps_global->cur_folder, p, sizeof(ps_global->cur_folder)-1); ps_global->cur_folder[sizeof(ps_global->cur_folder)-1] = '\0'; ps_global->context_current = ps_global->context_list; + setup_threading_index_style(); reset_index_format(); clear_index_cache(ps_global->mail_stream, 0); /* MUST sort before restoring msgno! */ @@ -990,6 +992,7 @@ do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp, clear_index_cache(ps_global->mail_stream, 0); reset_index_format(); + setup_threading_index_style(); /* * Start news reading with messages the user's marked deleted @@ -1113,7 +1116,10 @@ do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp, if(!cur_already_set && mn_get_total(ps_global->msgmap) > 0L){ - perfolder_startup_rule = reset_startup_rule(ps_global->mail_stream); + perfolder_startup_rule = get_perfolder_startup_rule(ps_global->mail_stream, + V_STARTUP_RULES, newfolder); + + reset_startup_rule(ps_global->mail_stream); if(ps_global->start_entry > 0){ mn_set_cur(ps_global->msgmap, mn_get_revsort(ps_global->msgmap) @@ -1135,124 +1141,7 @@ do_broach_folder(char *newfolder, CONTEXT_S *new_context, MAILSTREAM **streamp, else use_this_startup_rule = ps_global->inc_startup_rule; - switch(use_this_startup_rule){ - /* - * For news in incoming collection we're doing the same thing - * for first-unseen and first-recent. In both those cases you - * get first-unseen if FAKE_NEW is off and first-recent if - * FAKE_NEW is on. If FAKE_NEW is on, first unseen is the - * same as first recent because all recent msgs are unseen - * and all unrecent msgs are seen (see pine_mail_open). - */ - case IS_FIRST_UNSEEN: -first_unseen: - mn_set_cur(ps_global->msgmap, - (sp_first_unseen(m) - && mn_get_sort(ps_global->msgmap) == SortArrival - && !mn_get_revsort(ps_global->msgmap) - && !get_lflag(ps_global->mail_stream, NULL, - sp_first_unseen(m), MN_EXLD) - && (n = mn_raw2m(ps_global->msgmap, - sp_first_unseen(m)))) - ? n - : first_sorted_flagged(F_UNSEEN | F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID)); - break; - - case IS_FIRST_RECENT: -first_recent: - /* - * We could really use recent for news but this is the way - * it has always worked, so we'll leave it. That is, if - * the FAKE_NEW feature is on, recent and unseen are - * equivalent, so it doesn't matter. If the feature isn't - * on, all the undeleted messages are unseen and we start - * at the first one. User controls with the FAKE_NEW feature. - */ - if(IS_NEWS(ps_global->mail_stream)){ - mn_set_cur(ps_global->msgmap, - first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID)); - } - else{ - mn_set_cur(ps_global->msgmap, - first_sorted_flagged(F_RECENT | F_UNSEEN - | F_UNDEL, - m, pc, - THREADING() ? 0 : FSF_SKIP_CHID)); - } - break; - - case IS_FIRST_IMPORTANT: - mn_set_cur(ps_global->msgmap, - first_sorted_flagged(F_FLAG|F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID)); - break; - - case IS_FIRST_IMPORTANT_OR_UNSEEN: - - if(IS_NEWS(ps_global->mail_stream)) - goto first_unseen; - - { - MsgNo flagged, first_unseen; - - flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID); - first_unseen = (sp_first_unseen(m) - && mn_get_sort(ps_global->msgmap) == SortArrival - && !mn_get_revsort(ps_global->msgmap) - && !get_lflag(ps_global->mail_stream, NULL, - sp_first_unseen(m), MN_EXLD) - && (n = mn_raw2m(ps_global->msgmap, - sp_first_unseen(m)))) - ? n - : first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID); - mn_set_cur(ps_global->msgmap, - (MsgNo) MIN((int) flagged, (int) first_unseen)); - - } - - break; - - case IS_FIRST_IMPORTANT_OR_RECENT: - - if(IS_NEWS(ps_global->mail_stream)) - goto first_recent; - - { - MsgNo flagged, first_recent; - - flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID); - first_recent = first_sorted_flagged(F_RECENT | F_UNSEEN - | F_UNDEL, - m, pc, - THREADING() ? 0 : FSF_SKIP_CHID); - mn_set_cur(ps_global->msgmap, - (MsgNo) MIN((int) flagged, (int) first_recent)); - } - - break; - - case IS_FIRST: - mn_set_cur(ps_global->msgmap, - first_sorted_flagged(F_UNDEL, m, pc, - THREADING() ? 0 : FSF_SKIP_CHID)); - break; - - case IS_LAST: - mn_set_cur(ps_global->msgmap, - first_sorted_flagged(F_UNDEL, m, pc, - FSF_LAST | (THREADING() ? 0 : FSF_SKIP_CHID))); - break; - - default: - panic("Unexpected incoming startup case"); - break; - - } + find_startup_position(use_this_startup_rule, m, pc); } else if(IS_NEWS(ps_global->mail_stream)){ /* @@ -1430,9 +1319,11 @@ expunge_and_close(MAILSTREAM *stream, char **final_msg, long unsigned int flags) /* Save read messages? */ if(VAR_READ_MESSAGE_FOLDER && VAR_READ_MESSAGE_FOLDER[0] && sp_flagged(stream, SP_INBOX) - && (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL))){ + && (F_ON(F_AUTO_READ_MSGS_RULES, ps_global) || + (seen_not_del = count_flagged(stream, F_SEEN | F_UNDEL)))){ if(F_ON(F_AUTO_READ_MSGS,ps_global) + || F_ON(F_AUTO_READ_MSGS_RULES, ps_global) || (pith_opt_read_msg_prompt && (*pith_opt_read_msg_prompt)(seen_not_del, VAR_READ_MESSAGE_FOLDER))) /* move inbox's read messages */ @@ -1703,6 +1594,9 @@ move_read_msgs(MAILSTREAM *stream, char *dstfldr, char *buf, size_t buflen, long char *bufp = NULL; MESSAGECACHE *mc; + if (F_ON(F_AUTO_READ_MSGS_RULES, ps_global)) + return move_read_msgs_using_rules(stream, dstfldr, buf); + if(!is_absolute_path(dstfldr) && !(save_context = default_save_context(ps_global->context_list))) save_context = ps_global->context_list; @@ -1742,8 +1636,9 @@ move_read_msgs(MAILSTREAM *stream, char *dstfldr, char *buf, size_t buflen, long snprintf(buf, buflen, "Moving %s read message%s to \"%s\"", comatose(searched), plural(searched), dstfldr); we_cancel = busy_cue(buf, NULL, 0); - if(save(ps_global, stream, save_context, dstfldr, msgmap, - SV_DELETE | SV_FIX_DELS | SV_INBOXWOCNTXT) == searched) + ps_global->exiting = 1; + if((save(ps_global, stream, save_context, dstfldr, msgmap, + SV_DELETE | SV_FIX_DELS | SV_INBOXWOCNTXT) == searched)) strncpy(bufp = buf + 1, "Moved", MIN(5,buflen)); /* change Moving to Moved */ buf[buflen-1] = '\0'; @@ -1781,7 +1676,9 @@ move_read_incoming(MAILSTREAM *stream, CONTEXT_S *context, char *folder, && ((context_isambig(folder) && folder_is_nick(folder, FOLDERS(context), 0)) || folder_index(folder, context, FI_FOLDER) > 0) - && (seen_undel = count_flagged(stream, F_SEEN | F_UNDEL))){ + && ((seen_undel = count_flagged(stream, F_SEEN | F_UNDEL)) + || (F_ON(F_AUTO_READ_MSGS,ps_global) && + F_ON(F_AUTO_READ_MSGS_RULES, ps_global)))){ for(; f && *archive; archive++){ char *p; @@ -2739,3 +2636,295 @@ get_uname(char *mailbox, char *target, int len) return(*target ? target : NULL); } + +char * +move_read_msgs_using_rules(MAILSTREAM *stream, char *dstfldr, char *buf) +{ + CONTEXT_S *save_context = NULL; + char **folder_to_save = NULL; + int num, we_cancel; + long i, j, success; + MSGNO_S *msgmap = NULL; + unsigned long nmsgs = 0L, stream_nmsgs; + + if(!is_absolute_path(dstfldr) + && !(save_context = default_save_context(ps_global->context_list))) + save_context = ps_global->context_list; + + folder_to_save = (char **)fs_get((stream->nmsgs + 1)*sizeof(char *)); + folder_to_save[0] = NULL; + mn_init(&msgmap, stream->nmsgs); + stream_nmsgs = stream->nmsgs; + for (i = 1L; i <= stream_nmsgs ; i++){ + set_lflag(stream, msgmap, i, MN_SLCT, 0); + folder_to_save[i] = get_lflag(stream, NULL, i, MN_EXLD) + ? NULL : get_folder_to_save(stream, i, dstfldr); + } + for (i = 1L; i <= stream_nmsgs; i++){ + num = 0; + if (folder_to_save[i]){ + mn_init(&msgmap, stream_nmsgs); + for (j = i; j <= stream_nmsgs ; j++){ + if (folder_to_save[j]){ + if (!strcmp(folder_to_save[i], folder_to_save[j])){ + set_lflag(stream, msgmap, j, MN_SLCT, 1); + num++; + if (j != i) + fs_give((void **)&folder_to_save[j]); + } + } + } + pseudo_selected(stream, msgmap); + sprintf(buf, "Moving %s read message%s to \"%.45s\"", + comatose(num), plural(num), folder_to_save[i]); + we_cancel = busy_cue(buf, NULL, 1); + ps_global->exiting = 1; + if(success = save(ps_global, stream,save_context, folder_to_save[i], + msgmap, SV_DELETE | SV_FIX_DELS)) + nmsgs += success; + if(we_cancel) + cancel_busy_cue(success ? 0 : -1); + for (j = i; j <= stream_nmsgs ; j++) + set_lflag(stream, msgmap, j, MN_SLCT, 0); + fs_give((void **)&folder_to_save[i]); + mn_give(&msgmap); + } + } + ps_global->exiting = 0; /* useful if we call from aggregate operations */ + sprintf(buf, "Moved automatically %s message%s", + comatose(nmsgs), plural(nmsgs)); + if (folder_to_save) + fs_give((void **)folder_to_save); + rule_curpos = 0L; + return buf; +} + +char * +get_folder_to_save(MAILSTREAM *stream, long i, char *dstfldr) +{ + MESSAGECACHE *mc = NULL; + RULE_RESULT *rule; + MSGNO_S *msgmap = NULL; + char *folder_to_save = NULL, *save_folder = NULL; + int n; + long msgno; + + /* The plan is as follows: Select each message of the folder. We + * need to set the cursor correctly so that iFlag gets the value + * correctly too, otherwise iFlag will get the value of the position + * of the cursor. After that we need to look for a rule that applies + * to the message and get the saving folder. If we get a saving folder, + * and we used the _FLAG_ token, use that folder, if no + * _FLAG_ token was used, move only if seen and not deleted, to the + * folder specified in the saving rule. If we did not get a saving + * folder from the rule, just save in the default folder. + */ + mn_init(&msgmap, stream->nmsgs); + rule_curpos = i; + msgno = mn_m2raw(msgmap, i); + if (msgno > 0L){ + mc = mail_elt(stream, msgno); + rule = (RULE_RESULT *) + get_result_rule(V_SAVE_RULES, FOR_SAVE, mc->private.msg.env); + if (rule){ + folder_to_save = cpystr(rule->result); + n = rule->number; + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + } + + if (folder_to_save && *folder_to_save){ + RULELIST *list = get_rulelist_from_code(V_SAVE_RULES, + ps_global->rule_list); + RULE_S *prule = get_rule(list, n); + if (condition_contains_token(prule->condition, "_FLAG_") + || (mc->valid && mc->seen && !mc->deleted) + || (!mc->valid && mc->searched)) + save_folder = cpystr(folder_to_save); + else + save_folder = NULL; + } + else + if (!mc || (mc->seen && !mc->deleted)) + save_folder = cpystr(dstfldr); + mn_give(&msgmap); + rule_curpos = 0L; + return save_folder; +} + +unsigned long +rules_cursor_pos(MAILSTREAM *stream) +{ + MSGNO_S *msgmap = sp_msgmap(stream); + return rule_curpos != 0L ? rule_curpos : mn_m2raw(msgmap,mn_get_cur(msgmap)); +} + +void +setup_threading_index_style(void) +{ + RULE_RESULT *rule; + NAMEVAL_S *v; + int i; + + rule = get_result_rule(V_THREAD_INDEX_STYLE_RULES, FOR_THREAD, NULL); + if (rule || ps_global->VAR_THREAD_INDEX_STYLE){ + for(i = 0; v = thread_index_styles(i); i++) + if(!strucmp(rule ? rule->result : ps_global->VAR_THREAD_INDEX_STYLE, + rule ? (v ? v->name : "" ) : S_OR_L(v))){ + ps_global->thread_index_style = v->value; + break; + } + if (rule){ + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + } +} + +unsigned +get_perfolder_startup_rule(MAILSTREAM *stream, int rule_type, char *folder) +{ + unsigned startup_rule; + char *rule_result; + + startup_rule = reset_startup_rule(stream); + rule_result = get_rule_result(FOR_STARTUP, folder, rule_type); + if (rule_result && *rule_result){ + int i; + NAMEVAL_S *v; + + for(i = 0; v = incoming_startup_rules(i); i++) + if(!strucmp(rule_result, v->name)){ + startup_rule = v->value; + break; + } + fs_give((void **)&rule_result); + } + return startup_rule; +} + +void +find_startup_position(int rule, MAILSTREAM *m, long pc) +{ + long n; + switch(rule){ + /* + * For news in incoming collection we're doing the same thing + * for first-unseen and first-recent. In both those cases you + * get first-unseen if FAKE_NEW is off and first-recent if + * FAKE_NEW is on. If FAKE_NEW is on, first unseen is the + * same as first recent because all recent msgs are unseen + * and all unrecent msgs are seen (see pine_mail_open). + */ + case IS_FIRST_UNSEEN: +first_unseen: + mn_set_cur(ps_global->msgmap, + (sp_first_unseen(m) + && mn_get_sort(ps_global->msgmap) == SortArrival + && !mn_get_revsort(ps_global->msgmap) + && !get_lflag(ps_global->mail_stream, NULL, + sp_first_unseen(m), MN_EXLD) + && (n = mn_raw2m(ps_global->msgmap, + sp_first_unseen(m)))) + ? n + : first_sorted_flagged(F_UNSEEN | F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID)); + break; + + case IS_FIRST_RECENT: +first_recent: + /* + * We could really use recent for news but this is the way + * it has always worked, so we'll leave it. That is, if + * the FAKE_NEW feature is on, recent and unseen are + * equivalent, so it doesn't matter. If the feature isn't + * on, all the undeleted messages are unseen and we start + * at the first one. User controls with the FAKE_NEW feature. + */ + if(IS_NEWS(ps_global->mail_stream)){ + mn_set_cur(ps_global->msgmap, + first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID)); + } + else{ + mn_set_cur(ps_global->msgmap, + first_sorted_flagged(F_RECENT | F_UNSEEN + | F_UNDEL, + m, pc, + THREADING() ? 0 : FSF_SKIP_CHID)); + } + break; + + case IS_FIRST_IMPORTANT: + mn_set_cur(ps_global->msgmap, + first_sorted_flagged(F_FLAG|F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID)); + break; + + case IS_FIRST_IMPORTANT_OR_UNSEEN: + + if(IS_NEWS(ps_global->mail_stream)) + goto first_unseen; + + { + MsgNo flagged, first_unseen; + + flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID); + first_unseen = (sp_first_unseen(m) + && mn_get_sort(ps_global->msgmap) == SortArrival + && !mn_get_revsort(ps_global->msgmap) + && !get_lflag(ps_global->mail_stream, NULL, + sp_first_unseen(m), MN_EXLD) + && (n = mn_raw2m(ps_global->msgmap, + sp_first_unseen(m)))) + ? n + : first_sorted_flagged(F_UNSEEN|F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID); + mn_set_cur(ps_global->msgmap, + (MsgNo) MIN((int) flagged, (int) first_unseen)); + + } + + break; + + case IS_FIRST_IMPORTANT_OR_RECENT: + + if(IS_NEWS(ps_global->mail_stream)) + goto first_recent; + + { + MsgNo flagged, first_recent; + + flagged = first_sorted_flagged(F_FLAG|F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID); + first_recent = first_sorted_flagged(F_RECENT | F_UNSEEN + | F_UNDEL, + m, pc, + THREADING() ? 0 : FSF_SKIP_CHID); + mn_set_cur(ps_global->msgmap, + (MsgNo) MIN((int) flagged, (int) first_recent)); + } + + break; + + case IS_FIRST: + mn_set_cur(ps_global->msgmap, + first_sorted_flagged(F_UNDEL, m, pc, + THREADING() ? 0 : FSF_SKIP_CHID)); + break; + + case IS_LAST: + mn_set_cur(ps_global->msgmap, + first_sorted_flagged(F_UNDEL, m, pc, + FSF_LAST | (THREADING() ? 0 : FSF_SKIP_CHID))); + break; + + default: + panic("Unexpected incoming startup case"); + break; + + } +} diff --git a/pith/mailcmd.h b/pith/mailcmd.h index 9e99c6f3..d590b7c1 100644 --- a/pith/mailcmd.h +++ b/pith/mailcmd.h @@ -42,6 +42,8 @@ #define DB_FROMTAB 0x02 /* opening because of TAB command */ #define DB_INBOXWOCNTXT 0x04 /* interpret inbox as one true inbox */ +static MAILSTREAM *saved_stream; +static unsigned long rule_curpos = 0L; /* * generic "is aggregate message command?" test @@ -63,7 +65,13 @@ int do_broach_folder(char *, CONTEXT_S *, MAILSTREAM **, unsigned long); void expunge_and_close(MAILSTREAM *, char **, unsigned long); void agg_select_all(MAILSTREAM *, MSGNO_S *, long *, int); char *move_read_msgs(MAILSTREAM *, char *, char *, size_t, long); +char *move_read_msgs_using_rules (MAILSTREAM *, char *, char *); +unsigned get_perfolder_startup_rule (MAILSTREAM *, int, char *); +void setup_threading_index_style (void); +void find_startup_position (int, MAILSTREAM *, long); +char *get_folder_to_save (MAILSTREAM *, long, char *); char *move_read_incoming(MAILSTREAM *, CONTEXT_S *, char *, char **, char *, size_t); +unsigned long rules_cursor_pos (MAILSTREAM *); void cross_delete_crossposts(MAILSTREAM *); long zoom_index(struct pine *, MAILSTREAM *, MSGNO_S *, int); int unzoom_index(struct pine *, MAILSTREAM *, MSGNO_S *); diff --git a/pith/mailindx.c b/pith/mailindx.c index 09cdc2c8..12fbb5d2 100644 --- a/pith/mailindx.c +++ b/pith/mailindx.c @@ -17,6 +17,7 @@ static char rcsid[] = "$Id: mailindx.c 1266 2009-07-14 18:39:12Z hubert@u.washin #include "../pith/headers.h" #include "../pith/mailindx.h" +#include "../pith/pineelt.h" #include "../pith/mailview.h" #include "../pith/flag.h" #include "../pith/icache.h" @@ -40,6 +41,7 @@ static char rcsid[] = "$Id: mailindx.c 1266 2009-07-14 18:39:12Z hubert@u.washin #include "../pith/send.h" #include "../pith/options.h" #include "../pith/ablookup.h" +#include "../pith/rules.h" #ifdef _WINDOWS #include "../pico/osdep/mswin.h" #endif @@ -104,7 +106,6 @@ char *copy_format_str(int, int, char *, int); void set_print_format(IELEM_S *, int, int); void set_ielem_widths_in_field(IFIELD_S *); - #define BIGWIDTH 2047 @@ -228,6 +229,7 @@ init_index_format(char *format, INDEX_COL_S **answer) case iSTime: case iKSize: case iSize: + case iSizeThread: case iPrioAlpha: (*answer)[column].req_width = 7; break; @@ -374,6 +376,13 @@ reset_index_format(void) PAT_STATE pstate; PAT_S *pat; int we_set_it = 0; + char *rule; + + if(rule = get_rule_result(FOR_INDEX, ps_global->cur_folder, V_INDEX_RULES)){ + init_index_format(rule, &ps_global->index_disp_format); + fs_give((void **)&rule); + return; + } if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){ @@ -447,14 +456,15 @@ free_hdrtok(HEADER_TOK_S **hdrtok) static INDEX_PARSE_T itokens[] = { {"STATUS", iStatus, FOR_INDEX}, {"MSGNO", iMessNo, FOR_INDEX}, - {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"DATE", iDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, {"FROMORTO", iFromTo, FOR_INDEX}, {"FROMORTONOTNEWS", iFromToNotNews, FOR_INDEX}, {"SIZE", iSize, FOR_INDEX}, {"SIZECOMMA", iSizeComma, FOR_INDEX}, + {"SIZETHREAD", iSizeThread, FOR_INDEX}, {"SIZENARROW", iSizeNarrow, FOR_INDEX}, {"KSIZE", iKSize, FOR_INDEX}, - {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"SUBJECT", iSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE|FOR_TRIM}, {"FULLSTATUS", iFStatus, FOR_INDEX}, {"IMAPSTATUS", iIStatus, FOR_INDEX}, {"SHORTIMAPSTATUS", iSIStatus, FOR_INDEX}, @@ -463,55 +473,58 @@ static INDEX_PARSE_T itokens[] = { {"SUBJECTTEXT", iSubjectText, FOR_INDEX}, {"SUBJKEYTEXT", iSubjKeyText, FOR_INDEX}, {"SUBJKEYINITTEXT", iSubjKeyInitText, FOR_INDEX}, - {"OPENINGTEXT", iOpeningText, FOR_INDEX}, - {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX}, - {"KEY", iKey, FOR_INDEX}, - {"KEYINIT", iKeyInit, FOR_INDEX}, + {"OPENINGTEXT", iOpeningText, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_TRIM}, + {"OPENINGTEXTNQ", iOpeningTextNQ, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_TRIM}, + {"KEY", iKey, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_COMPOSE}, + {"KEYINIT", iKeyInit, FOR_INDEX|FOR_RULE|FOR_SAVE|FOR_COMPOSE}, {"DESCRIPSIZE", iDescripSize, FOR_INDEX}, {"ATT", iAtt, FOR_INDEX}, {"SCORE", iScore, FOR_INDEX}, {"PRIORITY", iPrio, FOR_INDEX}, {"PRIORITYALPHA", iPrioAlpha, FOR_INDEX}, - {"PRIORITY!", iPrioBang, FOR_INDEX}, - {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"PRIORITY!", iPrioBang, FOR_INDEX}, + {"LONGDATE", iLDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SHORTDATE1", iS1Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SHORTDATE2", iS2Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SHORTDATE3", iS3Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SHORTDATE4", iS4Date, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"DATEISO", iDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SHORTDATEISO", iDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATE", iSDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTTIME", iSTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATEISO", iSDateIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATESHORTISO",iSDateIsoS, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATES1", iSDateS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATES2", iSDateS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATES3", iSDateS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATES4", iSDateS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIME", iSDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMEISO",iSDateTimeIso, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMESHORTISO",iSDateTimeIsoS,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES1", iSDateTimeS1, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES2", iSDateTimeS2, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES3", iSDateTimeS3, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES4", iSDateTimeS4, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIME24", iSDateTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMEISO24", iSDateTimeIso24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMESHORTISO24",iSDateTimeIsoS24,FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES124", iSDateTimeS124, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES224", iSDateTimeS224, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES324", iSDateTimeS324, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"SMARTDATETIMES424", iSDateTimeS424, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"TIME24", iTime24, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"TIME12", iTime12, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"TIMEZONE", iTimezone, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"MONTHABBREV", iMonAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"DAYOFWEEKABBREV", iDayOfWeekAbb, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"DAYOFWEEK", iDayOfWeek, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"FROM", iFrom, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_COMPOSE}, + {"TO", iTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_COMPOSE}, + {"SENDER", iSender, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"CC", iCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_SAVE|FOR_SAVE}, + {"ADDRESSTO", iAddressTo, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"ADDRESSCC", iAddressCc, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"ADDRESSRECIPS", iAddressRecip, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, {"RECIPS", iRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"NEWS", iNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"TOANDNEWS", iToAndNews, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, @@ -520,56 +533,68 @@ static INDEX_PARSE_T itokens[] = { {"NEWSANDRECIPS", iNewsAndRecips, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"MSGID", iMsgID, FOR_REPLY_INTRO|FOR_TEMPLATE}, {"CURNEWS", iCurNews, FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"DAYDATE", iRDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"PREFDATE", iPrefDate, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"PREFTIME", iPrefTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"PREFDATETIME", iPrefDateTime, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"DAY", iDay, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"DAYORDINAL", iDayOrdinal, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"DAY2DIGIT", iDay2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"MONTHLONG", iMonLong, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"MONTH", iMon, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"MONTH2DIGIT", iMon2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"YEAR", iYear, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"YEAR2DIGIT", iYear2Digit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE|FOR_SAVE}, + {"ADDRESS", iAddress, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_RULE}, {"MAILBOX", iMailbox, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, {"ROLENICK", iRoleNick, FOR_REPLY_INTRO|FOR_TEMPLATE}, {"INIT", iInit, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, - {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, + {"CURDATE", iCurDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURDATEISO", iCurDateIso, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURDATEISOS", iCurDateIsoS, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURTIME24", iCurTime24, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURTIME12", iCurTime12, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURDAY", iCurDay, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURDAY2DIGIT", iCurDay2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURDAYOFWEEK", iCurDayOfWeek, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, {"CURDAYOFWEEKABBREV", iCurDayOfWeekAbb, - FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, + FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURMONTH", iCurMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURMONTH2DIGIT", iCurMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURMONTHLONG", iCurMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURMONTHABBREV", iCurMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURYEAR", iCurYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURYEAR2DIGIT", iCurYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURPREFDATE", iCurPrefDate, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"CURPREFTIME", iCurPrefTime, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, {"CURPREFDATETIME", iCurPrefDateTime, - FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, + FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTMONTH", iLstMon, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTMONTH2DIGIT", iLstMon2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTMONTHLONG", iLstMonLong, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTMONTHABBREV", iLstMonAbb, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTMONTHYEAR", iLstMonYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, {"LASTMONTHYEAR2DIGIT", iLstMonYear2Digit, - FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, - {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT}, + FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTYEAR", iLstYear, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, + {"LASTYEAR2DIGIT", iLstYear2Digit, FOR_REPLY_INTRO|FOR_TEMPLATE|FOR_FILT|FOR_RULE|FOR_SAVE}, {"HEADER", iHeader, FOR_INDEX}, {"TEXT", iText, FOR_INDEX}, {"ARROW", iArrow, FOR_INDEX}, {"NEWLINE", iNewLine, FOR_REPLY_INTRO}, {"CURSORPOS", iCursorPos, FOR_TEMPLATE}, + {"NICK", iNick, FOR_RULE|FOR_SAVE}, + {"FOLDER", iFolder, FOR_RULE|FOR_SAVE|FOR_FOLDER}, + {"ROLE", iRole, FOR_RULE|FOR_RESUB|FOR_TRIM|FOR_TEMPLATE}, + {"PROCID", iProcid, FOR_RULE|FOR_RESUB|FOR_FLAG|FOR_COMPOSE|FOR_TRIM|FOR_TEMPLATE}, + {"PKEY", iPkey, FOR_RULE|FOR_KEY}, + {"SCREEN", iScreen, FOR_RULE|FOR_KEY}, + {"FLAG", iFlag, FOR_RULE|FOR_SAVE|FOR_FLAG}, + {"COLLECTION", iCollection, FOR_RULE|FOR_SAVE|FOR_COMPOSE|FOR_FOLDER}, + {"BCC", iBcc, FOR_COMPOSE|FOR_RULE}, + {"LCC", iLcc, FOR_COMPOSE|FOR_RULE}, + {"FORWARDFROM", iFfrom, FOR_COMPOSE|FOR_RULE}, + {"FORWARDADDRESS", iFadd, FOR_COMPOSE|FOR_RULE}, {NULL, iNothing, FOR_NOTHING} }; @@ -943,7 +968,7 @@ static IndexColType fixed_ctypes[] = { iSDateTimeS1, iSDateTimeS2, iSDateTimeS3, iSDateTimeS4, iSDateTimeIso24, iSDateTimeIsoS24, iSDateTimeS124, iSDateTimeS224, iSDateTimeS324, iSDateTimeS424, - iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize, + iSize, iSizeComma, iSizeNarrow, iKSize, iDescripSize, iSizeThread, iPrio, iPrioBang, iPrioAlpha, iInit, iAtt, iTime24, iTime12, iTimezone, iMonAbb, iYear, iYear2Digit, iDay2Digit, iMon2Digit, iDayOfWeekAbb, iScore, iMonLong, iDayOfWeek @@ -1136,6 +1161,7 @@ setup_index_header_widths(MAILSTREAM *stream) case iTime12: case iSize: case iKSize: + case iSizeThread: cdesc->actual_length = 7; cdesc->adjustment = Right; break; @@ -1229,7 +1255,7 @@ setup_index_header_widths(MAILSTREAM *stream) cdesc->ctype != iNothing; cdesc++) if(cdesc->ctype == iSize || cdesc->ctype == iKSize || - cdesc->ctype == iSizeNarrow || + cdesc->ctype == iSizeNarrow || cdesc->ctype == iSizeThread || cdesc->ctype == iSizeComma || cdesc->ctype == iDescripSize){ if(cdesc->actual_length == 0){ if((fix=cdesc->width) > 0){ /* had this reserved */ @@ -1612,10 +1638,12 @@ build_header_work(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, /* find next thread which is visible */ do{ + unsigned long branch; if(mn_get_revsort(msgmap) && thrd->prevthd) thrd = fetch_thread(stream, thrd->prevthd); - else if(!mn_get_revsort(msgmap) && thrd->nextthd) - thrd = fetch_thread(stream, thrd->nextthd); + /*branch = get_branch(stream,thrd)*/ + else if(!mn_get_revsort(msgmap) && thrd->branch) + thrd = fetch_thread(stream, thrd->branch); else thrd = NULL; } while(thrd @@ -2027,13 +2055,10 @@ format_index_index_line(INDEXDATA_S *idata) */ ice = copy_ice(ice); + thrd = fetch_thread(idata->stream, idata->rawno); /* is this a collapsed thread index line? */ - if(!idata->bogus && THREADING()){ - thrd = fetch_thread(idata->stream, idata->rawno); - collapsed = thrd && thrd->next - && get_lflag(idata->stream, NULL, - idata->rawno, MN_COLL); - } + if(!idata->bogus && THREADING()) + collapsed = thrd && thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno); /* calculate contents of the required fields */ for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++) @@ -2075,10 +2100,15 @@ format_index_index_line(INDEXDATA_S *idata) if(to_us == ' ') to_us = '+'; + if(to_us == '+' + && F_ON(F_MARK_FOR_GROUP,ps_global) && + (addr->next || addr != fetch_to(idata))) + to_us = '.'; + break; } - if(to_us != '+' && resent_to_us(idata)){ + if(to_us == ' ' && resent_to_us(idata)){ ice->to_us = 1; if(to_us == ' ') to_us = '+'; @@ -2132,7 +2162,7 @@ format_index_index_line(INDEXDATA_S *idata) ielem->color = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR); } } - else if(str[0] == '+' || str[0] == '-'){ + else if(str[0] == '+' || str[0] == '-' || str[0] == '.'){ if(VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; @@ -2188,6 +2218,12 @@ format_index_index_line(INDEXDATA_S *idata) for(addr = fetch_to(idata); addr; addr = addr->next) if(address_is_us(addr, ps_global)){ to_us = '+'; + + if(to_us == '+' + && F_ON(F_MARK_FOR_GROUP,ps_global) && + (addr->next || addr != fetch_to(idata))) + to_us = '.'; + break; } @@ -2283,7 +2319,7 @@ format_index_index_line(INDEXDATA_S *idata) if(pico_usingcolor()){ - if(str[0] == '+' || str[0] == '-'){ + if(str[0] == '+' || str[0] == '-' || str[0] == '.'){ if(start == 0 && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){ @@ -2465,6 +2501,24 @@ format_index_index_line(INDEXDATA_S *idata) from_str(cdesc->ctype, idata, str, sizeof(str), ice); break; + case iAddressTo: + case iAddressCc: + case iAddressRecip: + {ENVELOPE *env; + int we_clear; + env = rules_fetchenvelope(idata, &we_clear); + sprintf(str, "%-*.*s", ifield->width, ifield->width, + detoken_src((cdesc->ctype == iAddressTo + ? "_ADDRESSTO_" + : (cdesc->ctype == iAddressCc + ? "_ADRESSCC_" + : "_ADRESSRECIPS_")), FOR_INDEX, + env, NULL, NULL, NULL)); + if(we_clear) + mail_free_envelope(&env); + } + break; + case iTo: if(((field = ((addr = fetch_to(idata)) ? "To" @@ -2531,7 +2585,30 @@ format_index_index_line(INDEXDATA_S *idata) break; + case iSizeThread: + if (!THREADING()){ + goto getsize; + } else if (collapsed){ + l = count_flags_in_thread(idata->stream, thrd, F_NONE); + snprintf(str, sizeof(str), "(%lu)", l); + } + else{ + thrd = fetch_thread(idata->stream, idata->rawno); + if(!thrd) + snprintf(str, sizeof(str), "%s", "Error"); + else{ + long lengthb; + lengthb = get_length_branch(idata->stream, idata->rawno); + if (lengthb > 0L) + snprintf(str, sizeof(str), "(%lu)", lengthb); + else + snprintf(str,sizeof(str), "%s", " "); + } + } + break; + case iSize: +getsize: /* 0 ... 9999 */ if((l = fetch_size(idata)) < 10*1000L) snprintf(str, sizeof(str), "(%lu)", l); @@ -2785,7 +2862,6 @@ format_index_index_line(INDEXDATA_S *idata) if(first_text){ strncpy(str, first_text, BIGWIDTH); str[BIGWIDTH] = '\0'; - fs_give((void **) &first_text); } } @@ -3107,7 +3183,7 @@ format_thread_index_line(INDEXDATA_S *idata) tice->linecolor = new_color_pair(VAR_IND_IMP_FORE_COLOR, VAR_IND_IMP_BACK_COLOR); } - else if((to_us == '+' || to_us == '-') + else if((to_us == '+' || to_us == '-' || to_us == '.') && VAR_IND_PLUS_FORE_COLOR && VAR_IND_PLUS_BACK_COLOR){ ielem = ifield->ielem; ielem->freecolor = 1; @@ -3710,6 +3786,26 @@ fetch_firsttext(INDEXDATA_S *idata, int delete_quotes) gf_io_t pc; long partial_fetch_len = 0L; SEARCHSET *ss, **sset; + MESSAGECACHE *mc; + PINELT_S *pelt; + + /* we cache the result we get from this function, so that we do not have to + * refetch the text in case there is a change. We could cache in the envelope + * but c-client does not have a special field for that, nor we want to use the + * sparep pointer, since there could be other uses for sparep later, and even + * if we add a pointer to the ENVELOPE structure, we would be caching the same + * text twice (one in a private pointer, and the new pointer) and that would + * not make sense. Instead we will use an elt for this + */ + + if((mc = mail_elt(idata->stream, idata->rawno)) + && ((pelt = (PINELT_S *) mc->sparep) == NULL)){ + pelt = (PINELT_S *) fs_get(sizeof(PINELT_S)); + memset(pelt, 0, sizeof(PINELT_S)); + } + + if(pelt && pelt->firsttext != NULL) + return(pelt->firsttext); try_again: @@ -3803,7 +3899,17 @@ try_again: if(p > buf){ size_t l; - + ENVELOPE *env; + char *rule_result; + + if(rule_result = find_value((delete_quotes + ? "_OPENINGTEXTNQ_" : "_OPENINGTEXT_"), + buf, PROCESS_SP, idata, 4)){ + collspaces(rule_result); + strncpy(buf, rule_result, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + fs_give((void **) &rule_result); + } l = strlen(buf); l += 100; firsttext = fs_get((l+1) * sizeof(char)); @@ -3827,6 +3933,8 @@ try_again: goto try_again; } } + if(mc && pelt) + pelt->firsttext = firsttext; } } } @@ -5267,10 +5375,10 @@ subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int openi { char *subject, *origsubj, *origstr, *rawsubj, *sptr = NULL; char *p, *border, *q = NULL, *free_subj = NULL; - char *sp; + char *sp, *rule_result; size_t len; int width = -1; - int depth = 0, mult = 2; + int depth = 0, mult = 2, collapsed, i, we_clear = 0; int save; int do_subj = 0, truncated_tree = 0; PINETHRD_S *thd, *thdorig; @@ -5324,7 +5432,13 @@ subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int openi * origsubj is the original subject but it has been decoded. We need * to free it at the end of this routine. */ - + if (rule_result = find_value("_SUBJECT_", origsubj, PROCESS_SP, idata, 4)){ + if(origsubj) + fs_give((void **)&origsubj); + we_clear++; + origsubj = cpystr(rule_result); + fs_give((void **)&rule_result); + } /* * prepend_keyword will put the keyword stuff before the subject @@ -5412,10 +5526,8 @@ subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int openi if(pith_opt_condense_thread_cue) width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width, - thd && thd->next - && get_lflag(idata->stream, - NULL,idata->rawno, - MN_COLL)); + this_thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno) && + (count_thread(ps_global,idata->stream, ps_global->msgmap, idata->rawno) != 1)); /* * width is < available strsize and @@ -5765,6 +5877,9 @@ subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int openi if(free_subj) fs_give((void **) &free_subj); + + if (we_clear && origsubj) + fs_give((void **)&origsubj); } @@ -6043,11 +6158,8 @@ from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_ border = str + width; if(pith_opt_condense_thread_cue) width = (*pith_opt_condense_thread_cue)(thd, ice, &str, &strsize, width, - thd && thd->next - && get_lflag(idata->stream, - NULL,idata->rawno, - MN_COLL)); - + this_thread_is_kolapsed(ps_global, idata->stream, ps_global->msgmap, idata->rawno) && + (count_thread(ps_global,idata->stream, ps_global->msgmap, idata->rawno) != 1)); fptr = str; if(thd) @@ -6133,16 +6245,33 @@ from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_ ? "To" : (addr = fetch_cc(idata)) ? "Cc" - : NULL)) - && set_index_addr(idata, field, addr, "To: ", - strsize-1, fptr)) - break; + : NULL))){ + char *rule_result; + rule_result = find_value("_FROM_", NULL, 0, idata, 1); + if (!rule_result) + set_index_addr(idata, field, addr, "To: ", + strsize-1, fptr); + else{ + sprintf(str, "%-*.*s", strsize-1, strsize-1, + rule_result); + fs_give((void **)&rule_result); + } + break; + } if(ctype == iFromTo && (newsgroups = fetch_newsgroups(idata)) && *newsgroups){ - snprintf(fptr, strsize, "To: %-*.*s", strsize-1-4, strsize-1-4, - newsgroups); + char *rule_result; + rule_result = find_value("_FROM_", NULL, 0, idata, 1); + if (!rule_result) + sprintf(str, "To: %-*.*s", strsize-1-4, + strsize-1-4, newsgroups); + else{ + sprintf(str, "%-*.*s", strsize-1, strsize-1, + rule_result); + fs_give((void **)&rule_result); + } break; } @@ -6155,7 +6284,15 @@ from_str(IndexColType ctype, INDEXDATA_S *idata, char *str, size_t strsize, ICE_ break; case iFrom: - set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr); + { char *rule_result; + rule_result = find_value("_FROM_", NULL, 0, idata, 4); + if (!rule_result) + set_index_addr(idata, "From", fetch_from(idata), NULL, strsize-1, fptr); + else{ + sprintf(str, "%-*.*s", strsize-1, strsize-1, rule_result); + fs_give((void **)&rule_result); + } + } break; case iAddress: @@ -6452,3 +6589,64 @@ set_print_format(IELEM_S *ielem, int width, int leftadj) } } } + +void +setup_threading_display_style(void) +{ + RULE_RESULT *rule; + NAMEVAL_S *v; + int i; + + rule = get_result_rule(V_THREAD_DISP_STYLE_RULES, FOR_THREAD, NULL); + if (rule || ps_global->VAR_THREAD_DISP_STYLE){ + for(i = 0; v = thread_disp_styles(i); i++) + if(!strucmp(rule ? rule->result : ps_global->VAR_THREAD_DISP_STYLE, + rule ? (v ? v->name : "" ) : S_OR_L(v))){ + ps_global->thread_disp_style = v->value; + break; + } + if (rule){ + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + } +} + +char * +find_value(char *token, char *use_this, int flag, INDEXDATA_S *idata, int nfcn) +{ + int n = 0, i, rule_context, we_clear; + char *rule_result = NULL, **list; + ENVELOPE *env; + RULELIST *rule; + RULE_S *prule; + + env = rules_fetchenvelope(idata, &we_clear); + if(env && env->sparep) + fs_give((void **)&env->sparep); + if(we_clear) + mail_free_envelope(&env); + if(rule = get_rulelist_from_code(V_REPLACE_RULES, ps_global->rule_list)){ + list = functions_for_token(token); + while(rule_result == NULL && (prule = get_rule(rule,n++))){ + rule_context = 0; + if (prule->action->token && !strcmp(prule->action->token, token)){ + for (i = 0; i < nfcn; i++) + if(list[i+1] && !strcmp(prule->action->function, list[i+1])) + rule_context |= context_for_function(list[i+1]); + if (rule_context){ + env = rules_fetchenvelope(idata, &we_clear); + if(use_this) + env->sparep = get_sparep_for_rule(use_this, flag); + rule_result = process_rule(prule, rule_context, env); + if(env->sparep) + free_sparep_for_rule(&env->sparep); + if(we_clear) + mail_free_envelope(&env); + } + } + } + } + return rule_result; +} diff --git a/pith/mailindx.h b/pith/mailindx.h index 8eb23033..fdea552c 100644 --- a/pith/mailindx.h +++ b/pith/mailindx.h @@ -30,6 +30,9 @@ extern void (*setup_header_widths)(MAILSTREAM *); /* exported prototypes */ +SortOrder translate (char *, int); +char *find_value (char *, char *, int, INDEXDATA_S *, int); +void setup_threading_display_style (void); int msgline_hidden(MAILSTREAM *, MSGNO_S *, long, int); void adjust_cur_to_visible(MAILSTREAM *, MSGNO_S *); unsigned long line_hash(char *); diff --git a/pith/mailview.c b/pith/mailview.c index 40728aab..5df0dbdb 100644 --- a/pith/mailview.c +++ b/pith/mailview.c @@ -52,7 +52,11 @@ static char rcsid[] = "$Id: mailview.c 1266 2009-07-14 18:39:12Z hubert@u.washin #include "../pith/escapes.h" #include "../pith/keyword.h" #include "../pith/smime.h" - +#include "../pith/osdep/color.h" +#include "../pico/osdep/color.h" +#include "../pico/estruct.h" +#include "../pico/pico.h" +#include "../pico/efunc.h" #define FBUF_LEN (50) @@ -282,9 +286,17 @@ format_body(long int msgno, BODY *body, HANDLE_S **handlesp, HEADER_S *hp, int f if((flgs & FM_DISPLAY) && !(flgs & FM_NOCOLOR) && pico_usingcolor() + && ps_global->VAR_SPECIAL_TEXT_FORE_COLOR + && ps_global->VAR_SPECIAL_TEXT_BACK_COLOR){ + gf_link_filter(gf_line_test, gf_line_test_opt(color_this_text, NULL)); + } + + if((flgs & FM_DISPLAY) + && !(flgs & FM_NOCOLOR) + && pico_usingcolor() && ps_global->VAR_SIGNATURE_FORE_COLOR && ps_global->VAR_SIGNATURE_BACK_COLOR){ - gf_link_filter(gf_line_test, gf_line_test_opt(color_signature, &is_in_sig)); + gf_link_filter(gf_quote_test, gf_line_test_opt(color_signature, &is_in_sig)); } if((flgs & FM_DISPLAY) @@ -292,8 +304,10 @@ format_body(long int msgno, BODY *body, HANDLE_S **handlesp, HEADER_S *hp, int f && pico_usingcolor() && ps_global->VAR_QUOTE1_FORE_COLOR && ps_global->VAR_QUOTE1_BACK_COLOR){ - gf_link_filter(gf_line_test, gf_line_test_opt(color_a_quote, NULL)); + gf_link_filter(gf_quote_test, gf_line_test_opt(color_a_quote, NULL)); } + else + gf_link_filter(gf_quote_test,gf_line_test_opt(select_quote, NULL)); if(!(flgs & FM_NOWRAP)){ wrapflags = (flgs & FM_DISPLAY) ? (GFW_HANDLES|GFW_SOFTHYPHEN) : GFW_NONE; @@ -1098,27 +1112,88 @@ int color_signature(long int linenum, char *line, LT_INS_S **ins, void *is_in_sig) { struct variable *vars = ps_global->vars; - int *in_sig_block; + int *in_sig_block, i, j,same_qstr = 0, plb; COLOR_PAIR *col = NULL; + static char GLine[NSTRING] = {'\0'}; + static char PLine[NSTRING] = {'\0'}; + static char PPLine[NSTRING] = {'\0'}; + char NLine[NSTRING] = {'\0'}; + char rqstr[NSTRING] = {'\0'}; + char *p, *q; + static char *buf, buf2[NSTRING] = {'\0'}; + QSTRING_S *qs; + static int qstrlen = 0; if(is_in_sig == NULL) return 0; + if (linenum > 0){ + strncpy(PLine, GLine, sizeof(PLine)); + PLine[sizeof(PLine)-1] = '\0'; + } + + if(p = strchr(tmp_20k_buf, '\015')) *p = '\0'; + strncpy(NLine, tmp_20k_buf, sizeof(NLine)); + NLine[sizeof(NLine) - 1] = '\0'; + if (p) *p = '\015'; + + strncpy(GLine, line, sizeof(GLine)); + GLine[sizeof(GLine) - 1] = '\0'; + + ps_global->list_qstr = default_qstr(ps_global->prefix && *ps_global->prefix + ? (void *) ps_global->prefix : (void *) ">", 0); + plb = line_isblank(ps_global->list_qstr, PLine, GLine, PPLine, NSTRING); + qs = do_quote_match(ps_global->list_qstr, GLine, NLine, PLine, rqstr, NSTRING, plb); + if(linenum > 0) + strncpy(PPLine, PLine, NSTRING); + strncpy(buf2, rqstr, NSTRING); + i = buf2 && buf2[0] ? strlen(buf2) : 0; + free_qs(&qs); + + /* determine if buf and buf2 are the same quote string */ + if (!struncmp(buf, buf2, qstrlen)){ + for (j = qstrlen; buf2[j] && isspace((unsigned char)buf2[j]); j++); + if (!buf2[j] || buf2[j] == '|' || (buf2[j] == '*' && buf2[j+1] != '>')) + same_qstr++; + } + in_sig_block = (int *) is_in_sig; - if(!strcmp(line, SIGDASHES)) - *in_sig_block = START_SIG_BLOCK; - else if(*line == '\0') + if (*in_sig_block != OUT_SIG_BLOCK){ + if (line && *line && (strlen(line) >= qstrlen) && same_qstr) + line += qstrlen; + else if (strlen(line) < qstrlen) + line += i; + else if (!same_qstr) + *in_sig_block = OUT_SIG_BLOCK; + } + else + line += i; + + if(!strcmp(line, SIGDASHES) || !strcmp(line, "--")){ + *in_sig_block = START_SIG_BLOCK; + buf = (char *) fs_get((i + 1)*sizeof(char)); + buf = cpystr(buf2); + qstrlen = i; + } + else if(*line == '\0'){ /* * Suggested by Eduardo: allow for a blank line right after * the sigdashes. */ *in_sig_block = (*in_sig_block == START_SIG_BLOCK) ? IN_SIG_BLOCK : OUT_SIG_BLOCK; + } else *in_sig_block = (*in_sig_block != OUT_SIG_BLOCK) ? IN_SIG_BLOCK : OUT_SIG_BLOCK; + if (*in_sig_block == OUT_SIG_BLOCK){ + qstrlen = 0; /* reset back in case there's another paragraph */ + if (buf) + fs_give((void **)&buf); + } + if(*in_sig_block != OUT_SIG_BLOCK && VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR && (col = new_color_pair(VAR_SIGNATURE_FORE_COLOR, @@ -1482,18 +1557,78 @@ color_headers(long int linenum, char *line, LT_INS_S **ins, void *local) return(0); } +int +incomplete_url(char *up, int n, int delim) +{ + char *line, *line2; + int rv = 0, len; + + if(*(up + n) != '\0') + return 0; + + if(delim > 0) + return 1; + + if(F_ON(F_VIEW_LONG_URL, ps_global)){ + line = up; + if(!strncmp(line, "http://", 7)) + line += 7; + else if(!strncmp(line, "https://", 8)) + line += 8; + if(strchr(line, '/') != NULL && (line = strrchr(line, '/')) != NULL){ + line++; + line2 = strrchr(line, '.'); + rv = (strpbrk(line,"+#?=&") != NULL) + || (!line2 || line-line2 > 4); + } + } + return rv; +} + int url_hilite(long int linenum, char *line, LT_INS_S **ins, void *local) { register char *lp, *up = NULL, *urlp = NULL, *weburlp = NULL, *mailurlp = NULL; - int n, n1, n2, n3, l; + char *use_this_line, c, *begin_line, *end_line; + static int scannextline, delim = -1; + int n, n1, n2, n3, l, len; + int we_clear = 0, newhandle = 1, tie_off = 0; char buf[256], color[256]; HANDLE_S *h; URL_HILITE_S *uh; - for(lp = line; ; lp = up + n){ + uh = (URL_HILITE_S *) local; + if((uh && uh->handlesp && ((h = *(uh->handlesp)) == NULL) || h->key == 0) || + (!line || !*line) || linenum == 0) + scannextline = 0; /* initialize scannextline */ + + if(scannextline != 0){ + up = rfc1738_scan(line, &n1); + + /* if we found a url in the current line, but it is not at the beginning of + * the next line, or if there is no url in this line, we check if the url + * in the previous line continues in this line. + */ + + if(line != up){ + if(*uh->handlesp == NULL) + h = new_handle(uh->handlesp); + for(h = *uh->handlesp; h->next; h = h->next); /* get last handle */ + len = h->h.url.path ? strlen(h->h.url.path) : 0; + use_this_line = (char *) fs_get((len + strlen(line) + 1)*sizeof(char)); + sprintf(use_this_line,"%s%s", (h->h.url.path ? h->h.url.path : ""), line); + we_clear++; + newhandle = 0; + } + else + use_this_line = line; + } + else + use_this_line = line; + + for(lp = use_this_line; ; lp = up + n){ /* scan for all of them so we can choose the first */ if(F_ON(F_VIEW_SEL_URL,ps_global)) urlp = rfc1738_scan(lp, &n1); @@ -1503,6 +1638,10 @@ url_hilite(long int linenum, char *line, LT_INS_S **ins, void *local) mailurlp = mail_addr_scan(lp, &n3); if(urlp || weburlp || mailurlp){ + if(scannextline == 0){ + newhandle++; + delim = -1; + } up = urlp ? urlp : weburlp ? weburlp : mailurlp; if(up == urlp && weburlp && weburlp < up) @@ -1511,7 +1650,16 @@ url_hilite(long int linenum, char *line, LT_INS_S **ins, void *local) up = mailurlp; if(up == urlp){ + if(delim < 0) + delim = up > use_this_line && *(up - 1) == '<'; n = n1; + if(incomplete_url(up,n, delim)) + scannextline++; + else{ + if(scannextline) + tie_off++; + scannextline = 0; + } weburlp = mailurlp = NULL; } else if(up == weburlp){ @@ -1528,36 +1676,58 @@ url_hilite(long int linenum, char *line, LT_INS_S **ins, void *local) uh = (URL_HILITE_S *) local; - h = new_handle(uh->handlesp); - h->type = URL; - h->h.url.path = (char *) fs_get((n + 10) * sizeof(char)); - snprintf(h->h.url.path, n+10, "%s%.*s", + if(tie_off){ + tie_off = 0; /* do only once */ + begin_line = line; + end_line = line + n - strlen(h->h.url.path); + fs_give((void **)&h->h.url.path); + c = *(use_this_line + n); + *(use_this_line+n) = '\0'; + h->h.url.path = cpystr(use_this_line); + *(use_this_line+n) = c; + } + else{ + if(newhandle){ + h = new_handle(uh->handlesp); + h->type = URL; + } + begin_line = newhandle ? (we_clear ? line + strlen(line) - strlen(up) + : up) : line; + end_line = newhandle ? begin_line + n : line + strlen(line); + if(scannextline && h->h.url.path) + fs_give((void **)&h->h.url.path); + h->h.url.path = (char *) fs_get((n + 10) * sizeof(char)); + snprintf(h->h.url.path, n+10, "%s%.*s", weburlp ? "http://" : (mailurlp ? "mailto:" : ""), n, up); - h->h.url.path[n+10-1] = '\0'; + h->h.url.path[n+10-1] = '\0'; + } if(handle_start_color(color, sizeof(color), &l, uh->hdr_color)) - ins = gf_line_test_new_ins(ins, up, color, l); + ins = gf_line_test_new_ins(ins, begin_line, color, l); else if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)) - ins = gf_line_test_new_ins(ins, up, url_embed(TAG_BOLDON), 2); + ins = gf_line_test_new_ins(ins, begin_line, url_embed(TAG_BOLDON), 2); buf[0] = TAG_EMBED; buf[1] = TAG_HANDLE; snprintf(&buf[3], sizeof(buf)-3, "%d", h->key); buf[sizeof(buf)-1] = '\0'; buf[2] = strlen(&buf[3]); - ins = gf_line_test_new_ins(ins, up, buf, (int) buf[2] + 3); + ins = gf_line_test_new_ins(ins, begin_line, buf, (int) buf[2] + 3); /* in case it was the current selection */ - ins = gf_line_test_new_ins(ins, up + n, url_embed(TAG_INVOFF), 2); + ins = gf_line_test_new_ins(ins, end_line, url_embed(TAG_INVOFF), 2); if(scroll_handle_end_color(color, sizeof(color), &l, uh->hdr_color)) - ins = gf_line_test_new_ins(ins, up + n, color, l); + ins = gf_line_test_new_ins(ins, end_line, color, l); else - ins = gf_line_test_new_ins(ins, up + n, url_embed(TAG_BOLDOFF), 2); + ins = gf_line_test_new_ins(ins, end_line, url_embed(TAG_BOLDOFF), 2); urlp = weburlp = mailurlp = NULL; } + if(we_clear) + fs_give((void **)&use_this_line); + return(0); } @@ -1678,6 +1848,77 @@ pad_to_right_edge(long int linenum, char *line, LT_INS_S **ins, void *local) } +/* This filter gives a quote string of a line. It sends its reply back to the + calling filter in the tmp_20k_buf variable. This filter replies with + the full quote string including tailing spaces if any. It is the + responsibility of the calling filter to figure out if thos spaces are + useful for that filter or if they should be removed before doing any + useful work. For example, color_a_quote does not require the trailing + spaces, but gf_wrap does. + */ +int +select_quote(long linenum, char *line, LT_INS_S **ins, void *local) +{ + int i, plb, *code; + char rqstr[NSTRING] = {'\0'}, buf[NSTRING] = {'\0'}; + char GLine[NSTRING] = {'\0'}, PLine[NSTRING] = {'\0'}; + char PPLine[NSTRING] = {'\0'}, NLine[NSTRING] = {'\0'}; + static char GLine1[NSTRING] = {'\0'}; + static char PLine1[NSTRING] = {'\0'}; + static char PPLine1[NSTRING] = {'\0'}; + static char GLine2[NSTRING] = {'\0'}; + static char PLine2[NSTRING] = {'\0'}; + static char PPLine2[NSTRING] = {'\0'}; + QSTRING_S *qs; + int buflen = NSTRING < SIZEOF_20KBUF ? NSTRING - 1: SIZEOF_20KBUF - 1; + int who, raw; + + code = (int *)local; + who = code ? (*code & COLORAQUO) : 0; /* may I ask who is calling? */ + raw = code ? (*code & RAWSTRING) : 0; /* return raw string */ + strncpy(GLine, (who ? GLine1 : GLine2), buflen); + strncpy(PLine, (who ? PLine1 : PLine2), buflen); + strncpy(PPLine, (who ? PPLine1 : PPLine2), buflen); + + if (linenum > 0) + strncpy(PLine, GLine, buflen); + + strncpy(NLine, tmp_20k_buf, buflen); + + if (line) + strncpy(GLine, line, buflen); + else + GLine[0] = '\0'; + + ps_global->list_qstr = default_qstr(ps_global->prefix && *ps_global->prefix + ? (void *) ps_global->prefix : (void *) ">", 0); + plb = line_isblank(ps_global->list_qstr, PLine, GLine, PPLine, NSTRING); + + qs = do_quote_match(ps_global->list_qstr, GLine, NLine, PLine, rqstr, NSTRING, plb); + if (raw) + strncpy(buf, rqstr, NSTRING); + else + flatten_qstring(qs, buf, NSTRING); + if(qs) + record_quote_string(qs); + free_qs(&qs); + + /* do not paint an extra level for a line with a >From string at the + * begining of it + */ + if (buf[0]){ + i = strlen(buf); + if (strlen(line) >= i + 6 && !strncmp(line+i-1,">From ", 6)) + buf[i - 1] = '\0'; + } + strncpy(tmp_20k_buf, buf, buflen); + if (linenum > 0) + strncpy((who ? PPLine1 : PPLine2), PLine, buflen); + strncpy((who ? GLine1 : GLine2), GLine, buflen); + strncpy((who ? PLine1 : PLine2), PLine, buflen); + return 1; +} + #define UES_LEN 12 #define UES_MAX 32 @@ -2503,6 +2744,190 @@ hdr_color(char *fieldname, char *value, SPEC_COLOR_S *speccolor) return(color_pair); } +void +interval_free(IVAL_S **ival) +{ + if (!(*ival)) + return; + + if ((*ival)->next) + interval_free(&((*ival)->next)); + + fs_give((void **)(ival)); +} + +IVAL_S * +compute_interval (char *string, int endm) +{ + IVAL_S *ival = NULL; + regmatch_t pmatch; + + if(ps_global->paterror == 0 && + regexec(&ps_global->colorpat, string + endm, 1, &pmatch, 0) == 0){ + ival = (IVAL_S *) fs_get(sizeof(IVAL_S)); + ival->start = endm + pmatch.rm_so; + ival->end = endm + pmatch.rm_eo; + ival->next = compute_interval(string, ival->end); + } + return ival; +} + +void +regex_pattern(char **plist) +{ + int i = 0, j = 0, len = 0; + char *pattern = NULL; + regex_t preg; + + if(ps_global->paterror == 0) + regfree(&ps_global->colorpat); + + if(plist && *plist && *plist){ + for (i = 0; plist[i] && plist[i][0]; i++) + len += strlen(plist[i]) + 1; + pattern = (char *) fs_get(len * sizeof(char)); + *pattern = '\0'; + for (j = 0; j < i; j++){ + strcat(pattern, plist[j]); + strcat(pattern, (j < i - 1) ? "|" : ""); + } + if ((ps_global->paterror = regcomp(&preg, pattern, REG_EXTENDED)) != 0) + regfree(&preg); + else + ps_global->colorpat = preg; + } + if(pattern) + fs_give((void **)&pattern); +} + +LT_INS_S ** +insert_color_special_text(LT_INS_S **ins, char **p, IVAL_S *ival, int last_end, + COLOR_PAIR *col) +{ + struct variable *vars = ps_global->vars; + + if (ival){ + *p += ival->start - last_end; + ins = gf_line_test_new_ins(ins, *p, color_embed(col->fg, col->bg), + (2 * RGBLEN) + 4); + *p += ival->end - ival->start; + ins = gf_line_test_new_ins(ins, *p, color_embed(VAR_NORM_FORE_COLOR, + VAR_NORM_BACK_COLOR), (2 * RGBLEN) + 4); + ins = insert_color_special_text(ins, p, ival->next, ival->end, col); + } + return ins; +} + +int +length_color(char *p, int begin_color) +{ + int len = 0, done = begin_color ? 0 : -1; + char *orig = p; + + while (*p && done <= 0){ + switch(*p++){ + case TAG_HANDLE : + p += *p + 1; + done++; + break; + + case TAG_FGCOLOR : + case TAG_BGCOLOR : + p += RGBLEN; + if (!begin_color) + done++; + break; + + default : + break; + } + } + len = p - orig; + return len; +} + +int +any_color_in_string(char *p) +{ + int rv = 0; + char *orig = p; + while (*p && !rv) + if (*p++ == TAG_EMBED) + rv = p - orig; + return rv; +} + +void +remove_spaces_ival(IVAL_S **ivalp, char *p) +{ + IVAL_S *ival; + int i; + if (!ivalp || !*ivalp) + return; + ival = *ivalp; + for (i = 0; isspace((unsigned char) p[ival->start + i]); i++); + if (ival->start + i < ival->end) /* do not do this if match only spaces */ + ival->start += i; + else + return; + for (i = 0; isspace((unsigned char) p[ival->end - i - 1]); i++); + ival->end -= i; + if (ival->next) + remove_spaces_ival(&(ival->next), p); +} + +int +color_this_text(long linenum, char *line, LT_INS_S **ins, void *local) +{ + struct variable *vars = ps_global->vars; + COLOR_PAIR *col = NULL; + char *p; + int i = 0; + static char *pattern = NULL; + +/* select_quote(linenum, line, ins, (void *) &i); + for (i = 0; tmp_20k_buf[i] != '\0'; i++); */ + p = line + i; + + if(VAR_SPECIAL_TEXT_FORE_COLOR && VAR_SPECIAL_TEXT_BACK_COLOR + && (col = new_color_pair(VAR_SPECIAL_TEXT_FORE_COLOR, + VAR_SPECIAL_TEXT_BACK_COLOR)) + && !pico_is_good_colorpair(col)) + free_color_pair(&col); + + if(ps_global->VAR_SPECIAL_TEXT && *ps_global->VAR_SPECIAL_TEXT + && **ps_global->VAR_SPECIAL_TEXT && col){ + IVAL_S *ival; + int done = 0, begin_color = 0; + + while (!done){ + if (i = any_color_in_string(p)){ + begin_color = (begin_color + 1) % 2; + if (begin_color){ + p[i - 1] = '\0'; + ival = compute_interval(p, 0); + remove_spaces_ival(&ival, p); + p[i - 1] = TAG_EMBED; + ins = insert_color_special_text(ins, &p, ival, 0, col); + } + for (;*p++ != TAG_EMBED; ); + p += length_color(p, begin_color); + } + else{ + ival = compute_interval(p, 0); + remove_spaces_ival(&ival, p); + ins = insert_color_special_text(ins, &p, ival, 0, col); + done++; + } + interval_free(&ival); + if (!*p) + done++; + } + free_color_pair(&col); + } + + return 0; +} /* * The argument fieldname is something like "Subject:..." or "Subject". diff --git a/pith/mailview.h b/pith/mailview.h index a89f3f95..5d2fe171 100644 --- a/pith/mailview.h +++ b/pith/mailview.h @@ -30,6 +30,12 @@ #include "../pith/color.h" +typedef struct IVAL { + int start; + int end; + struct IVAL *next; +} IVAL_S; + /* format_message flags */ #define FM_DISPLAY 0x0001 /* result is headed for display */ #define FM_NEW_MESS 0x0002 /* a new message so zero out attachment descrip */ @@ -126,6 +132,15 @@ char *format_body(long int, BODY *, HANDLE_S **, HEADER_S *, int, int, gf_io_t); int url_hilite(long, char *, LT_INS_S **, void *); int handle_start_color(char *, size_t, int *, int); int handle_end_color(char *, size_t, int *); +IVAL_S *compute_interval(char *, int); +void remove_spaces_ival(IVAL_S **, char *); +void interval_free(IVAL_S **); +void regex_pattern(char **); +LT_INS_S **insert_color_special_text(LT_INS_S **, char **, IVAL_S *, + int, COLOR_PAIR *); +int any_color_in_string(char *); +int length_color(char *, int); +int color_this_text(long, char *, LT_INS_S **, void *); /* * BUG: BELOW IS UNIX/PC ONLY since config'd browser means nothing to webpine @@ -142,6 +157,7 @@ COLOR_PAIR *hdr_color(char *, char *, SPEC_COLOR_S *); char *display_parameters(PARAMETER *); char *pine_fetch_header(MAILSTREAM *, long, char *, char **, long); int color_signature(long, char *, LT_INS_S **, void *); +int select_quote(long, char *, LT_INS_S **, void *); int scroll_handle_start_color(char *, size_t, int *); int scroll_handle_end_color(char *, size_t, int *, int); int width_at_this_position(unsigned char *, unsigned long); diff --git a/pith/makefile.wnt b/pith/makefile.wnt index d9316dba..b9464cd4 100644 --- a/pith/makefile.wnt +++ b/pith/makefile.wnt @@ -44,7 +44,7 @@ HFILES= ../include/system.h ../include/general.h \ init.h keyword.h ldap.h list.h mailcap.h mailcmd.h mailindx.h maillist.h \ mailpart.h mailview.h margin.h mimedesc.h mimetype.h msgno.h newmail.h news.h \ options.h pattern.h pineelt.h pipe.h readfile.h remote.h remtype.h repltype.h reply.h \ - rfc2231.h save.h savetype.h search.h send.h sequence.h signal.h sort.h sorttype.h \ + rfc2231.h rules.h save.h savetype.h search.h send.h sequence.h signal.h sort.h sorttype.h \ state.h status.h store.h stream.h string.h strlst.h takeaddr.h tempfile.h text.h \ thread.h url.h user.h util.h @@ -53,7 +53,7 @@ OFILES= ablookup.obj abdlc.obj addrbook.obj addrstring.obj adrbklib.obj bldaddr. filter.obj flag.obj folder.obj handle.obj help.obj helptext.obj hist.obj icache.obj imap.obj init.obj \ keyword.obj ldap.obj list.obj mailcap.obj mailcmd.obj mailindx.obj maillist.obj mailview.obj \ margin.obj mimedesc.obj mimetype.obj msgno.obj newmail.obj news.obj pattern.obj pipe.obj \ - readfile.obj remote.obj reply.obj rfc2231.obj save.obj search.obj sequence.obj send.obj sort.obj state.obj \ + readfile.obj remote.obj reply.obj rfc2231.obj rules.obj save.obj search.obj sequence.obj send.obj sort.obj state.obj \ status.obj store.obj stream.obj string.obj strlst.obj takeaddr.obj tempfile.obj text.obj \ thread.obj adjtime.obj url.obj util.obj diff --git a/pith/msgno.c b/pith/msgno.c index 465a42e0..e72ee0d3 100644 --- a/pith/msgno.c +++ b/pith/msgno.c @@ -933,6 +933,9 @@ free_pine_elt(void **sparep) if((*peltp)->pthrd) fs_give((void **) &(*peltp)->pthrd); + if((*peltp)->firsttext) + fs_give((void **) &(*peltp)->firsttext); + if((*peltp)->ice) free_ice(&(*peltp)->ice); diff --git a/pith/osdep/color.c b/pith/osdep/color.c index faf3c675..cad5502e 100644 --- a/pith/osdep/color.c +++ b/pith/osdep/color.c @@ -31,7 +31,7 @@ static char rcsid[] = "$Id: color.c 761 2007-10-23 22:35:18Z hubert@u.washington #include <system.h> #include "./color.h" - +#include "./collate.h" /* @@ -91,3 +91,1257 @@ pico_set_colorp(COLOR_PAIR *col, int flags) { return(pico_set_colors(col ? col->fg : NULL, col ? col->bg : NULL, flags)); } + + + /* + * Extended Justification support also does not belong here + * but otherwise webpine will not build, so we move everything + * here. Hopefully this will be the permanent place for these + * routines. These routines used to be in pico/word.c + */ +#define NSTRING 256 +#include "../../include/general.h" + +/* Support of indentation of paragraphs */ +#define is_indent_char(c) (((c) == '.' || (c) == '}' || (c) == RPAREN || \ + (c) == '*' || (c) == '+' || is_a_digit(c) || \ + ISspace(c) || (c) == '-' || \ + (c) == ']') ? 1 : 0) +#define allowed_after_digit(c,word,k) ((((c) == '.' && \ + allowed_after_period(next((word),(k)))) ||\ + (c) == RPAREN || (c) == '}' || (c) == ']' ||\ + ISspace(c) || is_a_digit(c) || \ + ((c) == '-' ) && \ + allowed_after_dash(next((word),(k)))) \ + ? 1 : 0) +#define allowed_after_period(c) (((c) == RPAREN || (c) == '}' || (c) == ']' ||\ + ISspace(c) || (c) == '-' || \ + is_a_digit(c)) ? 1 : 0) +#define allowed_after_parenth(c) (ISspace(c) ? 1 : 0) +#define allowed_after_space(c) (ISspace(c) ? 1 : 0) +#define allowed_after_braces(c) (ISspace(c) ? 1 : 0) +#define allowed_after_star(c) ((ISspace(c) || (c) == RPAREN ||\ + (c) == ']' || (c) == '}') ? 1 : 0) +#define allowed_after_dash(c) ((ISspace(c) || is_a_digit(c)) ? 1 : 0) +#define EOLchar(c) (((c) == '.' || (c) == ':' || (c) == '?' ||\ + (c) == '!') ? 1 : 0) + + +/* Extended justification support */ +#define is_cquote(c) ((c) == '>' || (c) == '|' || (c) == ']' || (c) == ':') +#define is_cword(c) ((((c) >= 'a') && ((c) <= 'z')) || \ + (((c) >= 'A') && ((c) <= 'Z')) || \ + (((c) >= '0') && ((c) <= '9')) || \ + ((c) == ' ') || ((c) == '?') || \ + ((c) == '@') || ((c) == '.') || \ + ((c) == '!') || ((c) == '\'') || \ + ((c) == ',') || ((c) == '\"') ? 1 : 0) +#define isaquote(c) ((c) == '\"' || (c) == '\'') +#define is8bit(c) ((((int) (c)) & 0x80) ? 1 : 0) +#define iscontrol(c) (iscntrl(((int) (c)) & 0x7f) ? 1 : 0) +#define forbidden(c) (((c) == '\"') || ((c) == '\'') || ((c) == '$') ||\ + ((c) == ',') || ((c) == '.') || ((c) == '-') ||\ + ((c) == LPAREN) || ((c) == '/')|| ((c) == '`') ||\ + ((c) == '{') || ((c) == '\\') || (iscontrol((c))) ||\ + (((c) >= '0') && ((c) <= '9')) || ((c) == '?')) +#define is_cletter(c) ((((c) >= 'a') && ((c) <= 'z'))) ||\ + ((((c) >= 'A') && ((c) <= 'Z'))||\ + is8bit(c)) +#define is_cnumber(c) ((c) >= '0' && (c) <= '9') +#define allwd_after_word(c) (((c) == ' ') || ((c) == '>') || is_cletter(c)) +#define allwd_after_qsword(c) (((c) != '\\') && ((c) != RPAREN)) +#define before(word,i) (((i) > 0) ? (word)[(i) - 1] : 0) +#define next(w,i) ((((w)[(i)]) != 0) ? ((w)[(i) + 1]) : 0) +#define now(w,i) ((w)[(i)]) +#define is_qsword(c) (((c) == ':') || ((c) == RPAREN) ? 1 : 0) +#define is_colon(c) (((c) == ':') ? 1 : 0) +#define is_rarrow(c) (((c) == '>') ? 1 : 0) +#define is_tilde(c) (((c) == '~') ? 1 : 0) +#define is_dash(c) (((c) == '-') ? 1 : 0) +#define is_pound(c) (((c) == '#') ? 1 : 0) +#define is_a_digit(c) ((((c) >= '0') && ((c) <= '9')) ? 1 : 0) +#define is_allowed(c) (is_cquote(c) || is_cword(c) || is_dash(c) || \ + is_pound(c)) +#define qs_allowed(a) (((a)->qstype != qsGdb) && ((a)->qstype != qsProg)) + +/* Internal justification functions */ +QSTRING_S *is_quote(char **, char *, int); +QSTRING_S *qs_normal_part(QSTRING_S *); +QSTRING_S *qs_remove_trailing_spaces(QSTRING_S *); +QSTRING_S *trim_qs_from_cl(QSTRING_S *, QSTRING_S *, QSTRING_S *); +QSTRING_S *fix_qstring(QSTRING_S *, QSTRING_S *, QSTRING_S *); +QSTRING_S *fix_qstring_allowed(QSTRING_S *, QSTRING_S *, QSTRING_S *); +QSTRING_S *qs_add(char **, char *, QStrType, int, int, int, int); +QSTRING_S *remove_qsword(QSTRING_S *); +QSTRING_S *do_raw_quote_match(char **, char *, char *, char *, QSTRING_S **, QSTRING_S **); +void free_qs(QSTRING_S **); +int word_is_prog(char *); +int qstring_is_normal(QSTRING_S *); +int exists_good_part(QSTRING_S *); +int strcmp_qs(char *, char *); +int count_levels_qstring(QSTRING_S *); +int same_qstring(QSTRING_S *, QSTRING_S *); +int isaword(char *,int ,int); +int isamailbox(char *,int ,int); +int double_check_qstr(char *); + +int +word_is_prog(char *word) +{ + static char *list1[] = {"#include", + "#define", + "#ifdef", + "#ifndef", + "#elif", + "#if", + NULL}; + static char *list2[] = {"#else", + "#endif", + NULL}; + int i, j = strlen(word), k, rv = 0; + + for(i = 0; rv == 0 && list1[i] && (k = strlen(list1[i])) && k < j; i++) + if(!strncmp(list1[i], word, k) && ISspace(word[k])) + rv++; + + if(rv) + return rv; + + for(i = 0; rv == 0 && list2[i] && (k = strlen(list2[i])) && k <= j; i++) + if(!strncmp(list2[i], word, k) && (!word[k] || ISspace(word[k]))) + rv++; + + return rv; +} + +/* + * This function creates a qstring pointer with the information that + * is_quote handles to it. + * Parameters: + * qs - User supplied quote string + * word - The line of text that the user is trying to read/justify + * beginw - Where we need to start copying from + * endw - Where we end copying + * offset - Any offset in endw that we need to account for + * typeqs - type of the string to be created + * neednext - boolean, indicating if we need to compute the next field + * of leave it NULL + * + * It is a mistake to call this function if beginw >= endw + offset. + * Please note the equality sign in the above inequality (this is because + * we always assume that qstring->value != ""). + */ +QSTRING_S * +qs_add(char **qs, char word[NSTRING], QStrType typeqs, int beginw, int endw, + int offset, int neednext) +{ + QSTRING_S *qstring, *nextqs; + int i; + + qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S)); + memset (qstring, 0, sizeof(QSTRING_S)); + qstring->qstype = qsNormal; + + if (beginw == 0){ + beginw = endw + offset; + qstring->qstype = typeqs; + } + + nextqs = neednext ? is_quote(qs, word+beginw, 1) : NULL; + + qstring->value = (char *) malloc((beginw+1)*sizeof(char)); + strncpy(qstring->value, word, beginw); + qstring->value[beginw] = '\0'; + + qstring->next = nextqs; + + return qstring; +} + +int +qstring_is_normal(QSTRING_S *cl) +{ + for (;cl && (cl->qstype == qsNormal); cl = cl->next); + return cl ? 0 : 1; +} + +/* + * Given a quote string, this function returns the part that is the leading + * normal part of it. (the normal part is the part that is tagged qsNormal, + * that is to say, the one that is not controversial at all (like qsString + * for example). + */ +QSTRING_S * +qs_normal_part(QSTRING_S *cl) +{ + + if (!cl) /* nothing in, nothing out */ + return cl; + + if (cl->qstype != qsNormal) + free_qs(&cl); + + if (cl) + cl->next = qs_normal_part(cl->next); + + return cl; +} + +/* + * this function removes trailing spaces from a quote string, but leaves the + * last one if there are trailing spaces + */ +QSTRING_S * +qs_remove_trailing_spaces(QSTRING_S *cl) +{ + QSTRING_S *rl = cl; + if (!cl) /* nothing in, nothing out */ + return cl; + + if (cl->next) + cl->next = qs_remove_trailing_spaces(cl->next); + else{ + if (value_is_space(cl->value)) + free_qs(&cl); + else{ + int i, l; + i = l = strlen(cl->value) - 1; + while (cl->value && cl->value[i] + && ISspace(cl->value[i])) + i--; + i += (i < l) ? 2 : 1; + cl->value[i] = '\0'; + } + } + return cl; +} + +/* + * This function returns if two strings are the same quote string. + * The call is not symmetric. cl must preceed the line nl. This function + * should be called for comparing the last part of cl and nl. + */ +int +strcmp_qs(char *valuecl, char *valuenl) +{ + int j; + + for (j = 0; valuecl[j] && (valuecl[j] == valuenl[j]); j++); + return !strcmp(valuecl, valuenl) + || (valuenl[j] && value_is_space(valuenl+j) + && value_is_space(valuecl+j) + && strlenis(valuecl+j) >= strlenis(valuenl+j)) + || (!valuenl[j] && value_is_space(valuecl+j)); +} + +int +count_levels_qstring(QSTRING_S *cl) +{ + int count; + for (count = 0; cl ; count++, cl = cl->next); + + return count; +} + +int +value_is_space(char *value) +{ + for (; value && *value && ISspace(*value); value++); + + return value && *value ? 0 : 1; +} + +void +free_qs(QSTRING_S **cl) +{ + if (!(*cl)) + return; + + if ((*cl)->next) + free_qs(&((*cl)->next)); + + (*cl)->next = (QSTRING_S *) NULL; + + if ((*cl)->value) + free((void *)(*cl)->value); + (*cl)->value = (char *) NULL; + free((void *)(*cl)); + *cl = (QSTRING_S *) NULL; +} + +/* + * This function returns the number of agreements between + * cl and nl. The call is not symmetric. cl must be the line + * preceding nl. + */ +int +same_qstring(QSTRING_S *cl, QSTRING_S *nl) +{ + int same = 0, done = 0; + + for (;cl && nl && !done; cl = cl->next, nl = nl->next) + if (cl->qstype == nl->qstype + && (!strcmp(cl->value, nl->value) + || (!cl->next && strcmp_qs(cl->value, nl->value)))) + same++; + else + done++; + return same; +} + +QSTRING_S * +trim_qs_from_cl(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl) +{ + QSTRING_S *cqstring = pl ? pl : nl; + QSTRING_S *tl = pl ? pl : nl; + int p, c; + + if (qstring_is_normal(tl)) + return tl; + + p = same_qstring(pl ? pl : cl, pl ? cl : nl); + + for (c = 1; c < p; c++, cl = cl->next, tl = tl->next); + + /* + * cl->next and tl->next differ, it may be because cl->next does not + * exist or tl->next does not exist or simply both exist but are + * different. In this last case, it may be that cl->next->value is made + * of spaces. If this is the case, tl advances once more. + */ + + if (tl->next){ + if (cl && cl->next && value_is_space(cl->next->value)) + tl = tl->next; + if (tl->next) + free_qs(&(tl->next)); + } + + if (!p) + free_qs(&cqstring); + + return cqstring; +} + +/* This function trims cl so that it returns a real quote string based + * on information gathered from the previous and next lines. pl and cl are + * also trimmed, but that is done in another function, not here. + */ +QSTRING_S * +fix_qstring(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl) +{ + QSTRING_S *cqstring = cl, *nqstring = nl, *pqstring = pl; + int c, n; + + if (qstring_is_normal(cl)) + return cl; + + c = count_levels_qstring(cl); + n = same_qstring(cl,nl); + + if (!n){ /* no next line or no agreement with next line */ + int p = same_qstring(pl, cl); /* number of agreements between pl and cl */ + QSTRING_S *tl; /* test line */ + + /* + * Here p <= c, so either p < c or p == c. If p == c, we are done, + * and return cl. If not, there are two cases, either p == 0 or + * 0 < p < c. In the first case, we do not have enough evidence + * to return anything other than the normal part of cl, in the second + * case we can only return p levels of cl. + */ + + if (p == c) + tl = cqstring; + else{ + if (p){ + for (c = 1; c < p; c++) + cl = cl->next; + free_qs(&(cl->next)); + tl = cqstring; + } + else{ + int done = 0; + QSTRING_S *al = cl; /* another line */ + /* + * Ok, we really don't have enough evidence to return anything, + * different from the normal part of cl, but it could be possible + * that we may want to accept the not-normal part, so we better + * make an extra test to determine what needs to be freed + */ + while (pl && cl && cl->qstype == pl->qstype + && !strucmp(cl->value, pl->value)){ + cl = cl->next; + pl = pl->next; + } + if (pl && cl && cl->qstype == pl->qstype + && strcmp_qs(pl->value, cl->value)) + cl = cl->next; /* next level differs only in spaces */ + while (!done){ + while (cl && cl->qstype == qsNormal) + cl = cl->next; + if (cl){ + if ((cl->qstype == qsString) + && (cl->value[strlen(cl->value) - 1] == '>')) + cl = cl->next; + else done++; + } + else done++; + } + if (al == cl){ + free_qs(&(cl)); + tl = cl; + } + else { + while (al && (al->next != cl)) + al = al->next; + cl = al; + if (cl && cl->next) + free_qs(&(cl->next)); + tl = cqstring; + } + } + } + return tl; + } + if (n + 1 < c){ /* if there are not enough agreements */ + int p = same_qstring(pl, cl); /* number of agreement between pl and cl */ + QSTRING_S *tl; /* test line */ + /* + * There's no way we can use cl in this case, but we can use + * part of cl, this is if pl does not have more agreements + * with cl. + */ + if (p == c) + tl = cqstring; + else{ + int m = p < n ? n : p; + for (c = 1; c < m; c++){ + pl = pl ? pl->next : (QSTRING_S *) NULL; + nl = nl ? nl->next : (QSTRING_S *) NULL; + cl = cl->next; + } + if (p == n && pl && pl->next && nl && nl->next + && ((cl->next->qstype == pl->next->qstype) + || (cl->next->qstype == nl->next->qstype)) + && (strcmp_qs(cl->next->value, pl->next->value) + || strcmp_qs(pl->next->value, cl->next->value) + || strcmp_qs(cl->next->value, nl->next->value) + || strcmp_qs(nl->next->value, cl->next->value))) + cl = cl->next; /* next level differs only in spaces */ + if (cl->next) + free_qs(&(cl->next)); + tl = cqstring; + } + return tl; + } + if (n + 1 == c){ + int p = same_qstring(pl, cl); + QSTRING_S *tl; /* test line */ + + /* + * p <= c, so p <= n+1, which means p < n + 1 or p == n + 1. + * If p < n + 1, then p <= n. + * so we have three possibilities: + * p == n + 1 or p == n or p < n. + * In the first case we copy p == n + 1 == c levels, in the second + * and third case we copy n levels, and check if we can copy the + * n + 1 == c level. + */ + if (p == n + 1) /* p == c, in the above sense of c */ + tl = cl; /* use cl, this is enough evidence */ + else{ + for (c = 1; c < n; c++) + cl = cl->next; + /* + * Here c == n, we only have one more level of cl, and at least one + * more level of nl + */ + if (cl->next->qstype == qsNormal) + cl = cl->next; + if (cl->next) + free_qs(&(cl->next)); + tl = cqstring; + } + return tl; + } + if (n == c) /* Yeah!!! */ + return cqstring; +} + +QSTRING_S * +fix_qstring_allowed(QSTRING_S *cl, QSTRING_S *nl, QSTRING_S *pl) +{ + if(!cl) + return (QSTRING_S *) NULL; + + if (qs_allowed(cl)) + cl->next = fix_qstring_allowed(cl->next, (nl ? nl->next : NULL), + (pl ? pl->next : NULL)); + else + if((nl && cl->qstype == nl->qstype) || (pl && cl->qstype == pl->qstype) + || (!nl && !pl)) + free_qs(&cl); + return cl; +} + +/* + * This function flattens the quote string returned to us by is_quote. A + * crash in this function implies a bug elsewhere. + */ +void +flatten_qstring(QSTRING_S *qs, char *buff, int bufflen) +{ + int i, j; + if(!buff || bufflen <= 0) + return; + + for (i = 0; qs; qs = qs->next) + for (j = 0; i < bufflen - 1 + && (qs->value[j]) && (buff[i++] = qs->value[j]); j++); + buff[i] = '\0'; +} + +extern int list_len; + + +int +double_check_qstr(char *q) +{ + if(!q || !*q) + return 0; + + return (*q == '#') ? 1 : 0; +} + +/* + * Given a string, we return the position where the function thinks that + * the quote string is over, if you are ever thinking of fixing something, + * you got to the right place. Memory freed by caller. Experience shows + * that it only makes sense to initialize memory when we need it, not at + * the start of this function. + */ +QSTRING_S * +is_quote (char **qs,char *word, int been_here) +{ + int i = 0, j, nxt, prev, finished = 0, offset; + unsigned char c; + QSTRING_S *qstring = (QSTRING_S *) NULL; + + if (word == NULL || word[0] == '\0') + return (QSTRING_S *) NULL; + + while (!finished){ + /* + * Before we apply our rules, let's advance past the quote string + * given by the user, this will avoid not recognition of the + * user's indent string and application of the arbitrary rules + * below. Notice that this step may bring bugs into this + * procedure, but these bugs will only appear if the indent string + * is really really strange and the text to be justified + * cooperates a lot too, so in general this will not be a problem. + * If you are concerned about this bug, simply remove the + * following lines after this comment and before the "switch" + * command below and use a more normal quote string!. + */ + for(j = 0; j < list_len; j++){ + if(!double_check_qstr(qs[j])){ + i += advance_quote_string(qs[j], word, i); + if (!word[i]) /* went too far? */ + return qs_add(qs, word, qsNormal, 0, i, 0, 0); + } + else + break; + } + + switch (c = (unsigned char) now(word,i)){ + case NBSP: + case TAB : + case ' ' : { QSTRING_S *nextqs, *d; + + for (; ISspace(word[i]); i++); /* FIX ME */ + nextqs = is_quote(qs,word+i, 1); + /* + * Merge qstring and nextqs, since this is an artificial + * separation, unless nextqs is of different type. + * What this means in practice is that if + * qs->qstype == qsNormal and qs->next != NULL, then + * qs->next->qstype != qsNormal. + * + * Can't use qs_add to merge because it could lead + * to an infinite loop (e.g a line "^ ^"). + */ + i += nextqs && nextqs->qstype == qsNormal + ? strlen(nextqs->value) : 0; + qstring = (QSTRING_S *) malloc (sizeof(QSTRING_S)); + memset (qstring, 0, sizeof(QSTRING_S)); + qstring->value = (char *) malloc((i+1)*sizeof(char)); + strncpy(qstring->value, word, i); + qstring->value[i] = '\0'; + qstring->qstype = qsNormal; + if(nextqs && nextqs->qstype == qsNormal){ + d = nextqs->next; + nextqs->next = NULL; + qstring->next = d; + free_qs(&nextqs); + } + else + qstring->next = nextqs; + + return qstring; + } + break; + case RPAREN: /* parenthesis ')' */ + if ((i != 0) || ((i == 0) && been_here)) + i++; + else + if (i == 0) + return qs_add(qs, word, qsChar, i, i, 1, 1); + else + finished++; + break; + + case ':': /* colon */ + case '~': nxt = next(word,i); + if ((is_tilde(c) && (nxt == '/')) + || (is_colon(c) && !is_cquote(nxt) + && !is_cword(nxt) && nxt != RPAREN)) + finished++; + else if (is_cquote(c) + || is_cquote(nxt) + || (c != '~' && nxt == RPAREN) + || (i != 0 && ISspace(nxt)) + || is_cquote(prev = before(word,i)) + || (ISspace(prev) && !is_tilde(c)) + || (is_tilde(c) && nxt != '/')) + i++; + else if (i == 0 && been_here) + return qs_add(qs, word, qsChar, i, i, 1, 1); + else + finished++; + break; + + case '<' : + case '=' : + case '-' : offset = is_cquote(nxt = next(word,i)) ? 2 + : (nxt == c && is_cquote(next(word,i+1))) ? 3 : -1; + + if (offset > 0) + return qs_add(qs, word, qsString, i, i, offset, 1); + else + finished++; + break; + + case '[' : + case '+' : /* accept +>, *> */ + case '*' : if (is_rarrow(nxt = next(word, i)) || /* stars */ + (ISspace(nxt) && is_rarrow(next(word,i+1)))) + i++; + else + finished++; + break; + + case '^' : + case '!' : + case '%' : if (next(word,i) != c) + return qs_add(qs, word, qsChar, i, i+1, 0, 1); + else + finished++; + break; + + case '_' : if(ISspace(next(word, i))) + return qs_add(qs, word, qsChar, i, i+1, 0, 1); + else + finished++; + break; + + case '#' : { QStrType qstype = qsChar; + if((nxt = next(word, i)) != c){ + if(isdigit((int) nxt)) + qstype = qsGdb; + else + if(word_is_prog(word)) + qstype = qsProg; + return qs_add(qs, word, qstype, i, i+1, 0, 1); + } + else + finished++; + break; + } + + default: + if (is_cquote(c)) + i++; + else if (is_cletter(c)){ + for (j = i; (is_cletter(nxt = next(word,j)) || is_cnumber(nxt)) + && !(ISspace(nxt));j++); + /* + * The whole reason why we are splitting the quote + * string is so that we will be able to accept quote + * strings that are strange in some way. Here we got to + * a point in which a quote string might exist, but it + * could be strange, so we need to create a "next" field + * for the quote string to warn us that something + * strange is coming. We need to confirm if this is a + * good choice later. For now we will let it pass. + */ + if (isaword(word,i,j) || isamailbox(word,i,j)){ + int offset; + QStrType qstype; + + offset = (is_cquote(c = next(word,j)) + || (c == RPAREN)) ? 2 + : ((ISspace(c) + && is_cquote(next(word,j+1))) ? 3 : -1); + + qstype = (is_cquote(c) || (c == RPAREN)) + ? (is_qsword(c) ? qsWord : qsString) + : ((ISspace(c) && is_cquote(next(word,j+1))) + ? (is_qsword(next(word,j+1)) + ? qsWord : qsString) + : qsString); + + /* + * qsWords are valid quote strings only when + * they are followed by text. + */ + if (offset > 0 && qstype == qsWord && + !allwd_after_qsword(now(word,j + offset))) + offset = -1; + + if (offset > 0) + return qs_add(qs, word, qstype, i, j, offset, 1); + } + finished++; + } + else{ + if(i > 0) + return qs_add(qs, word, qsNormal, 0, i, 0, 1); + else if(!forbidden(c)) + return qs_add(qs, word, qsChar, 0, 1, 0, 1); + else /* chao pescao */ + finished++; + } + break; + } /* End Switch */ + } /* End while */ + if (i > 0) + qstring = qs_add(qs, word, qsNormal, 0, i, 0, 0); + return qstring; +} + +int +isaword(char word[NSTRING], int i, int j) +{ + return i <= j && is_cletter(word[i]) ? + (i < j ? isaword(word,i+1,j) : 1) : 0; +} + +int +isamailbox(char word[NSTRING], int i, int j) +{ + return i <= j && (is_cletter(word[i]) || is_a_digit(word[i]) + || word[i] == '.') + ? (i < j ? isamailbox(word,i+1,j) : 1) : 0; +} + +/* + This routine removes the last part that is qsword or qschar that is not + followed by a normal part. This means that if a qsword or qschar is + followed by a qsnormal (or qsstring), we accept the qsword (or qschar) + as part of a quote string. + */ +QSTRING_S * +remove_qsword(QSTRING_S *cl) +{ + QSTRING_S *np = cl; + QSTRING_S *cp = np; /* this variable trails cl */ + + while(1){ + while (cl && cl->qstype == qsNormal) + cl = cl->next; + + if (cl){ + if (((cl->qstype == qsWord) || (cl->qstype == qsChar)) + && !exists_good_part(cl)){ + if (np == cl) /* qsword or qschar at the beginning */ + free_qs(&cp); + else{ + while (np->next != cl) + np = np->next; + free_qs(&(np->next)); + } + break; + } + else + cl = cl->next; + } + else + break; + } + return cp; +} + +int +exists_good_part (QSTRING_S *cl) +{ + return (cl ? (((cl->qstype != qsWord) && (cl->qstype != qsChar) + && qs_allowed(cl) && !value_is_space(cl->value)) + ? 1 : exists_good_part(cl->next)) + : 0); +} + +int +line_isblank(char **q, char *GLine, char *NLine, char *PLine, int buflen) +{ + int n = 0; + QSTRING_S *cl; + char qstr[NSTRING]; + + cl = do_raw_quote_match(q, GLine, NLine, PLine, NULL, NULL); + + flatten_qstring(cl, qstr, NSTRING); + + free_qs(&cl); + + for(n = strlen(qstr); n < buflen && GLine[n]; n++) + if(!ISspace((unsigned char) GLine[n])) + return(FALSE); + + return(TRUE); +} + +QSTRING_S * +do_raw_quote_match(char **q, char *GLine, char *NLine, char *PLine, QSTRING_S **nlp, QSTRING_S **plp) +{ + QSTRING_S *cl, *nl = NULL, *pl = NULL; + char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING]; + int emptypl = 0, emptynl = 0; + + if (!(cl = is_quote(q, GLine, 0))) /* if nothing in, nothing out */ + return cl; + + nl = is_quote(q, NLine, 0); /* Next Line */ + if (nlp) *nlp = nl; + pl = is_quote(q, PLine, 0); /* Previous Line */ + if (plp) *plp = pl; + /* + * If there's nothing in the preceeding or following line + * there is not enough information to accept it or discard it. In this + * case it's likely to be an isolated line, so we better accept it + * if it does not look like a word. + */ + flatten_qstring(pl, pbuf, NSTRING); + emptypl = (!PLine || !PLine[0] || + (pl && value_is_space(pbuf)) && !PLine[strlen(pbuf)]) ? 1 : 0; + if (emptypl){ + flatten_qstring(nl, nbuf, NSTRING); + emptynl = (!NLine || !NLine[0] || + (nl && value_is_space(nbuf) && !NLine[strlen(nbuf)])) ? 1 : 0; + if (emptynl){ + cl = remove_qsword(cl); + if((cl = fix_qstring_allowed(cl, NULL, NULL)) != NULL) + cl = qs_remove_trailing_spaces(cl); + free_qs(&nl); + free_qs(&pl); + if(nlp) *nlp = NULL; + if(plp) *plp = NULL; + + return cl; + } + } + + /* + * If either cl, nl or pl contain suspicious characters that may make + * them (or not) be quote strings, we need to fix them, so that the + * next pass will be done correctly. + */ + + cl = fix_qstring(cl, nl, pl); + nl = trim_qs_from_cl(cl, nl, NULL); + pl = trim_qs_from_cl(cl, NULL, pl); + if((cl = fix_qstring_allowed(cl, nl, pl)) != NULL){ + nl = trim_qs_from_cl(cl, nl, NULL); + pl = trim_qs_from_cl(cl, NULL, pl); + } + else{ + free_qs(&nl); + free_qs(&pl); + } + if(nlp) + *nlp = nl; + else + free_qs(&nl); + if(plp) + *plp = pl; + else + free_qs(&pl); + return cl; +} + +QSTRING_S * +do_quote_match(char **q, char *GLine, char *NLine, char *PLine, char *rqstr, +int rqstrlen, int plb) +{ + QSTRING_S *cl, *nl = NULL, *pl = NULL; + int c, n, p,i, j, NewP, NewC, NewN, clength, same = 0; + char nbuf[NSTRING], pbuf[NSTRING], buf[NSTRING]; + + if(rqstr) + *rqstr = '\0'; + + /* if nothing in, nothing out */ + cl = do_raw_quote_match(q, GLine, NLine, PLine, &nl, &pl); + if(cl == NULL){ + free_qs(&nl); + free_qs(&pl); + return cl; + } + + flatten_qstring(cl, rqstr, rqstrlen); + flatten_qstring(cl, buf, NSTRING); + flatten_qstring(nl, nbuf, NSTRING); + flatten_qstring(pl, pbuf, NSTRING); + + /* + * Once upon a time, is_quote used to return the length of the quote + * string that it had found. One day, not long ago, black hand came + * and changed all that, and made is_quote return a quote string + * divided in several fields, making the algorithm much more + * complicated. Fortunately black hand left a few comments in the + * source code to make it more understandable. Because of this change + * we need to compute the lengths of the quote strings separately + */ + c = buf && buf[0] ? strlen(buf) : 0; + n = nbuf && nbuf[0] ? strlen(nbuf) : 0; + p = pbuf && pbuf[0] ? strlen(pbuf) : 0; + /* + * When quote strings contain only blank spaces (ascii code 32) the + * above count is equal to the length of the quote string, but if + * there are TABS, the length of the quote string as seen by the user + * is different than the number that was just computed. Because of + * this we demand a recount (hmm.. unless you are in Florida, where + * recounts are forbidden) + */ + NewP = strlenis(pbuf); + NewC = strlenis(buf); + NewN = strlenis(nbuf); + + /* + * For paragraphs with spaces in the first line, but no space in the + * quote string of the second line, we make sure we choose the quote + * string without a space at the end of it. + */ + if ((NLine && !NLine[0]) + && ((PLine && !PLine[0]) + || (((same = same_qstring(pl, cl)) != 0) + && (same != count_levels_qstring(cl))))) + cl = qs_remove_trailing_spaces(cl); + else + if (NewC > NewN){ + int agree = 0; + for (j = 0; (j < n) && (GLine[j] == NLine[j]); j++); + clength = j; + /* clength is the common length in which Gline and Nline agree */ + /* j < n means that they do not agree fully */ + /* GLine = " \tText" + NLine = " Text" */ + if(j == n) + agree++; + if (clength < n){ /* see if buf and nbuf are padded with spaces and tabs */ + for (i = clength; i < n && ISspace(NLine[i]); i++); + if (i == n){/* padded NLine until the end of spaces? */ + for (i = clength; i < c && ISspace(GLine[i]); i++); + if (i == c) /* Padded CLine until the end of spaces? */ + agree++; + } + } + if (agree){ + for (j = clength; j < c && ISspace(GLine[j]); j++); + if (j == c){ + /* + * If we get here, it means that the current line has the same + * quote string (visually) than the next line, but both of them + * are padded with different amount of TABS or spaces at the end. + * The current line (GLine) has more spaces/TABs than the next + * line. This is the typical situation that is found at the + * begining of a paragraph. We need to check this, however, by + * checking the previous line. This avoids that we confuse + * ourselves with being in the last line of a paragraph. + * Example when it should not free_qs(cl) + * " Text in Paragraph 1" (PLine) + * " Text in Paragraph 1" (GLine) + * " Other Paragraph Number 2" (NLine) + * + * Example when it should free_qs(cl): + * ":) " (PLine) p = 3, j = 3 + * ":) Text" (GLine) c = 5 + * ":) More text" (NLine) n = 3 + * + * Example when it should free_qs(cl): + * ":) " (PLine) p = 3, j = 3 + * ":) > > > Text" (GLine) c = 11 + * ":) > > > More text" (NLine) n = 9 + * + * Example when it should free_qs(cl): + * ":) :) " (PLine) p = 6, j = 3 + * ":) > > > Text" (GLine) c = 11 + * ":) > > > More text" (NLine) n = 9 + * + * Example when it should free_qs(cl): + * ":) > > > " (PLine) p = 13, j = 11 + * ":) > > > Text" (GLine) c = 11 + * ":) > > > More text" (NLine) n = 9 + * + * The following example is very interesting. The "Other Text" + * line below should free the quote string an make it equal to the + * quote string of the line below it, but any algorithm trying + * to advance past that line should make it stop there, so + * we need one more check, to check the raw quote string and the + * processed quote string at the same time. + * FREE qs in this example. + * " Some Text" (PLine) p = 3, j = 0 + * "\tOther Text" (GLine) c = 1 + * " More Text" (NLine) n = 3 + * + */ + for (j = 0; (j < p) && (GLine[j] == PLine[j]); j++); + if ((p != c || j != p) && NLine[n]) + if(!get_indent_raw_line(q, PLine, nbuf, NSTRING, p, plb) + || NewP + strlenis(nbuf) != NewC){ + free_qs(&cl); + free_qs(&pl); + return nl; + } + } + } + } + + free_qs(&nl); + free_qs(&pl); + + return cl; +} + +/* + * Given a line, an initial position, and a quote string, we advance the + * current line past the quote string, including arbitraty spaces + * contained in the line, except that it removes trailing spaces. We do + * not handle TABs, if any, contained in the quote string. At least not + * yet. + * + * Arguments: q - quote string + * l - a line to process + * i - position in the line to start processing. i = 0 is the + * begining of that line. + */ +int +advance_quote_string(char *q, char l[NSTRING], int i) +{ + int n = 0, j = 0, is = 0, es = 0; + int k, m, p, adv; + char qs[NSTRING] = {'\0'}; + if(!q || !*q) + return(0); + for (p = strlen(q); (p > 0) && (q[p - 1] == ' '); p--, es++); + if (!p){ /* string contains only spaces */ + for (k = 0; ISspace(l[i + k]); k++); + k -= k % es; + return k; + } + for (is = 0; ISspace(q[is]); is++); /* count initial spaces */ + for (m = 0 ; is + m < p ; m++) + qs[m] = q[is + m]; /* qs = quote string without any space at the end */ + /* advance as many spaces as there are at the begining */ + for (k = 0; ISspace(l[i + j]); k++, j++); + /* now find the visible string in the line */ + for (m = 0; qs[m] && l[i + j] == qs[m]; m++, j++); + if (!qs[m]){ /* no match */ + /* + * So far we have advanced at least "is" spaces, plus the visible + * string "qs". Now we need to advance the trailing number of + * spaces "es". If we can do that, we have found the quote string. + */ + for (p = 0; ISspace(l[i + j + p]); p++); + adv = advance_quote_string(q, l, i + j + ((p < es) ? p : es)); + n = ((p < es) ? 0 : es) + k + m + adv; + } + return n; +} + +/* + * This function returns the effective length in screen of the quote + * string. If the string contains a TAB character, it is added here, if + * not, the length returned is the length of the string + */ +int strlenis(char *qstr) +{ + int i, rv = 0; + for (i = 0; qstr && qstr[i]; i++) + rv += ((qstr[i] == TAB) ? (~rv & 0x07) + 1 : 1); + return rv; +} + +int +is_indent (char word[NSTRING], int plb) +{ + int i = 0, finished = 0, c, nxt, j, k, digit = 0, bdigits = -1, alpha = 0; + + if (!word || !word[0]) + return i; + + for (i = 0, j = 0; ISspace(word[i]); i++, j++); + while ((i < NSTRING - 2) && !finished){ + switch (c = now(word,i)){ + case NBSP: + case TAB : + case ' ' : for (; ISspace(word[i]); i++); + if (!is_indent_char(now(word,i))) + finished++; + break; + + case '+' : + case '.' : + case ']' : + case '*' : + case '}' : + case '-' : + case RPAREN: + nxt = next(word,i); + if ((c == '.' && allowed_after_period(nxt) && alpha) + || (c == '*' && allowed_after_star(nxt)) + || (c == '}' && allowed_after_braces(nxt)) + || (c == '-' && allowed_after_dash(nxt)) + || (c == '+' && allowed_after_dash(nxt)) + || (c == RPAREN && allowed_after_parenth(nxt)) + || (c == ']' && allowed_after_parenth(nxt))) + i++; + else + finished++; + break; + + default : if (is_a_digit(c) && plb){ + if (bdigits < 0) + bdigits = i; /* first digit */ + for (k = i; is_a_digit(now(word,k)); k++); + if (k - bdigits > 2){ /* more than 2 digits? */ + i = bdigits; /* too many! */ + finished++; + } + else{ + if(allowed_after_digit(now(word,k),word,k)){ + alpha++; + i = k; + } + else{ + i = bdigits; + finished++; + } + } + } + else + finished++; + break; + + } + } + if (i == j) + i = 0; /* there must be something more than spaces in an indent string */ + return i; +} + +int +get_indent_raw_line(char **q, char *GLine, char *buf, int buflen, int k, int plb) +{ + int i, j; + char testline[1024]; + + if(k > 0){ + for(j = 0; GLine[j] != '\0'; j++){ + testline[j] = GLine[j]; + testline[j+1] = '\0'; + if(strlenis(testline) >= strlenis(buf)) + break; + } + k = ++j; /* reset k */ + } + i = is_indent(GLine+k, plb); + + for (j = 0; j < i && j < buflen && (buf[j] = GLine[j + k]); j++); + buf[j] = '\0'; + + return i; +} + +/* support for remembering quote strings across messages */ +char **allowed_qstr = NULL; +int list_len = 0; + +void +free_allowed_qstr(void) +{ + int i; + char **q = allowed_qstr; + + if(q == NULL) + return; + + for(i = 0; i < list_len; i++) + fs_give((void **)&q[i]); + + fs_give((void **)q); + list_len = 0; +} + +void +add_allowed_qstr(void *q, int type) +{ + int i; + + if(allowed_qstr == NULL){ + allowed_qstr = malloc(sizeof(char *)); + list_len = 0; + } + + if(type == 0){ + allowed_qstr[list_len] = malloc((1+strlen((char *)q))*sizeof(char)); + strcpy(allowed_qstr[list_len], (char *)q); + } + else + allowed_qstr[list_len] = (char *) ucs4_to_utf8_cpystr((UCS *)q); + + fs_resize((void **)&allowed_qstr, (++list_len + 1)*sizeof(char *)); + allowed_qstr[list_len] = NULL; +} + +void +record_quote_string (QSTRING_S *qs) +{ + int i, j, k; + + for(; qs && qs->value; qs = qs->next){ + j = 0; + for (; ;){ + k = j; + for(i = 0; i < list_len; i++){ + j += advance_quote_string(allowed_qstr[i], qs->value, j); + for(; ISspace(qs->value[j]); j++); + } + if(k == j) + break; + } + if(qs->value[j] != '\0') + add_allowed_qstr((void *)(qs->value + j), 0); + } +} + +/* type utf8: code 0; ucs4: code 1. */ +char ** +default_qstr(void *q, int type) +{ + if(allowed_qstr == NULL) + add_allowed_qstr(q, type); + + return allowed_qstr; +} + diff --git a/pith/osdep/color.h b/pith/osdep/color.h index 32c86242..929389a2 100644 --- a/pith/osdep/color.h +++ b/pith/osdep/color.h @@ -17,6 +17,24 @@ #ifndef PITH_OSDEP_COLOR_INCLUDED #define PITH_OSDEP_COLOR_INCLUDED +/* + * struct that will help us determine what the quote string of a line + * is. The "next" field indicates the presence of a possible continuation. + * The idea is that if a continuation fails, we free it and check for the + * remaining structure left + */ + +typedef enum {qsNormal, qsString, qsWord, qsChar, qsGdb, qsProg, qsText} QStrType; + +typedef struct QSTRING { + char *value; /* possible quote string */ + QStrType qstype; /* type of quote string */ + struct QSTRING *next; /* possible continuation */ +} QSTRING_S; + +#define UCH(c) ((unsigned char) (c)) +#define NBSP UCH('\240') +#define ISspace(c) (UCH(c) == ' ' || UCH(c) == TAB || UCH(c) == NBSP) #define RGBLEN 11 #define MAXCOLORLEN 11 /* longest string a color can be */ @@ -93,6 +111,11 @@ char *pico_get_last_fg_color(void); char *pico_get_last_bg_color(void); char *color_to_canonical_name(char *); int pico_count_in_color_table(void); - +int is_indent(char *, int); +int get_indent_raw_line (char **, char *, char *, int, int, int); +int line_isblank(char **, char *, char *, char *, int); +int strlenis(char *); +int value_is_space(char *); +int advance_quote_string(char *, char *, int); #endif /* PITH_OSDEP_COLOR_INCLUDED */ diff --git a/pith/pattern.c b/pith/pattern.c index 84a32c41..9d09462a 100644 --- a/pith/pattern.c +++ b/pith/pattern.c @@ -1756,7 +1756,7 @@ parse_action_slash(char *str, ACTION_S *action) SortOrder def_sort; int def_sort_rev; - if(decode_sort(p, &def_sort, &def_sort_rev) != -1){ + if(decode_sort(p, &def_sort, &def_sort_rev, 0) != -1){ action->sort_is_set = 1; action->sortorder = def_sort; action->revsort = (def_sort_rev ? 1 : 0); @@ -5483,6 +5483,15 @@ match_pattern_folder_specific(PATTERN_S *folders, MAILSTREAM *stream, int flags) break; case '#': +#ifndef _WINDOWS + if(!struncmp(patfolder, "#md/", 4) + || !struncmp(patfolder, "#mc/", 4)){ + maildir_file_path(patfolder, tmp1, sizeof(tmp1)); + if(!strcmp(patfolder, stream->mailbox)) + match++; + break; + } +#endif if(!strcmp(patfolder, stream->mailbox)) match++; @@ -7903,7 +7912,7 @@ move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr, int we_cancel = 0, width; CONTEXT_S *save_context = NULL; char buf[MAX_SCREEN_COLS+1], sbuf[MAX_SCREEN_COLS+1]; - char *save_ref = NULL; + char *save_ref = NULL, *save_dstfldr = NULL, *save_dstfldr2 = NULL; #define FILTMSG_MAX 30 if(!stream) @@ -7937,6 +7946,16 @@ move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr, if(F_OFF(F_QUELL_FILTER_MSGS, ps_global)) we_cancel = busy_cue(buf, NULL, 0); +#ifndef _WINDOWS + if(!struncmp(dstfldr, "#md/", 4) || !struncmp(dstfldr, "#mc/", 4)){ + char tmp1[MAILTMPLEN]; + maildir_file_path(dstfldr, tmp1, sizeof(tmp1)); + save_dstfldr2 = dstfldr; + save_dstfldr = cpystr(tmp1); + dstfldr = save_dstfldr; + } +#endif + if(!is_absolute_path(dstfldr) && !(save_context = default_save_context(ps_global->context_list))) save_context = ps_global->context_list; @@ -8000,6 +8019,11 @@ move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr, if(we_cancel) cancel_busy_cue(buf[0] ? 0 : -1); + if(save_dstfldr){ + fs_give((void **)&save_dstfldr); + dstfldr = save_dstfldr2; + } + return(buf[0] != '\0'); } diff --git a/pith/pine.hlp b/pith/pine.hlp index 82872f19..6fbe15ac 100644 --- a/pith/pine.hlp +++ b/pith/pine.hlp @@ -89,6 +89,7 @@ Where "variable" is one of either: ALPINE_VERSION ALPINE_REVISION ALPINE_COMPILE_DATE + ALPINE_PATCHLEVEL ALPINE_TODAYS_DATE C_CLIENT_VERSION _LOCAL_FULLNAME_ @@ -159,6 +160,14 @@ Version <!--#echo var="ALPINE_VERSION"-->(<!--#echo var="ALPINE_REVISION"-->) </DIV> <P> +This version was modified from its original source code. More information +about some of the patches applied to this version can be found <A HREF="h_patches">here</A>. +<!--chtml if pinemode="running"--> +The patch level of this version, including creation date of the patch is: +<!--#echo var=ALPINE_PATCHLEVEL-->. +<!--chtml endif--> + +<P> Alpine is an "Alternatively Licensed Program for Internet News and Email" produced until 2008 by the University of Washington. It is intended to be an easy-to-use program for @@ -198,7 +207,7 @@ message, as specified by original sender. Bugs that have been addressed include: <P> <UL> - <LI> Proper quote of shell characters in urls. + <LI> Do not use a shell to open a browser. <LI> Configure script did not test for crypto or pam libraries. <LI> Fix Cygwin separator to "/". <LI> Crash when a non-compliant SMTP server closes a connection without a QUIT command. @@ -3183,9 +3192,11 @@ There are also additional details on <li><a href="h_config_alt_role_menu">FEATURE: <!--#echo var="FEAT_alternate-role-menu"--></a> <li><a href="h_config_force_low_speed">FEATURE: <!--#echo var="FEAT_assume-slow-link"--></a> <li><a href="h_config_auto_read_msgs">FEATURE: <!--#echo var="FEAT_auto-move-read-msgs"--></a> +<li><a href="h_config_auto_read_msgs_rules">FEATURE: <!--#echo var="FEAT_auto-move-read-msgs-using-rules"--></a> <li><a href="h_config_auto_open_unread">FEATURE: <!--#echo var="FEAT_auto-open-next-unread"--></a> <li><a href="h_config_auto_unselect">FEATURE: <!--#echo var="FEAT_auto-unselect-after-apply"--></a> <li><a href="h_config_auto_unzoom">FEATURE: <!--#echo var="FEAT_auto-unzoom-after-apply"--></a> +<li><a href="h_config_circular_tab">FEATURE: <!--#echo var="FEAT_enable-circular-tab"--></a> <li><a href="h_config_auto_zoom">FEATURE: <!--#echo var="FEAT_auto-zoom-after-select"--></a> <li><a href="h_config_use_boring_spinner">FEATURE: <!--#echo var="FEAT_busy-cue-spinner-only"--></a> <li><a href="h_config_check_mail_onquit">FEATURE: <!--#echo var="FEAT_check-newmail-when-quitting"--></a> @@ -3285,6 +3296,7 @@ There are also additional details on <li><a href="h_config_full_auto_expunge">FEATURE: <!--#echo var="FEAT_expunge-without-confirm-everywhere"--></a> <li><a href="h_config_no_fcc_attach">FEATURE: <!--#echo var="FEAT_fcc-without-attachments"--></a> <li><a href="h_config_force_arrow">FEATURE: <!--#echo var="FEAT_force-arrow-cursor"--></a> +<li><a href="h_config_ignore_size">FEATURE: <!--#echo var="FEAT_ignore-size-changes"--></a> <li><a href="h_config_forward_as_attachment">FEATURE: <!--#echo var="FEAT_forward-as-attachment"--></a> <li><a href="h_config_preserve_field">FEATURE: <!--#echo var="FEAT_preserve-original-fields"--></a> <li><a href="h_config_quell_empty_dirs">FEATURE: <!--#echo var="FEAT_quell-empty-directories"--></a> @@ -3298,6 +3310,7 @@ There are also additional details on <li><a href="h_config_add_ldap">FEATURE: <!--#echo var="FEAT_ldap-result-to-addrbook-add"--></a> <li><a href="h_config_maildrops_preserve_state">FEATURE: <!--#echo var="FEAT_maildrops-preserve-state"--></a> <li><a href="h_config_mark_fcc_seen">FEATURE: <!--#echo var="FEAT_mark-fcc-seen"--></a> +<li><a href="h_config_mark_for_group">FEATURE: <!--#echo var="FEAT_mark-for-me-in-group"--></a> <li><a href="h_config_mark_for_cc">FEATURE: <!--#echo var="FEAT_mark-for-cc"--></a> <li><a href="h_config_mulnews_as_typed">FEATURE: <!--#echo var="FEAT_mult-newsrc-hostnames-as-typed"--></a> <li><a href="h_config_news_uses_recent">FEATURE: <!--#echo var="FEAT_news-approximates-new-status"--></a> @@ -3500,6 +3513,7 @@ There are also additional details on <li><a href="h_config_image_viewer">OPTION: <!--#echo var="VAR_image-viewer"--></a> <li><a href="h_config_inbox_path">OPTION: <!--#echo var="VAR_inbox-path"--></a> <li><a href="h_config_archived_folders">OPTION: <!--#echo var="VAR_incoming-archive-folders"--></a> +<li><a href="h_config_sleep">OPTION: <!--#echo var="VAR_sleep-interval-length"--></a> <li><a href="h_config_incoming_interv">OPTION: <!--#echo var="VAR_incoming-check-interval"--></a> <li><a href="h_config_incoming_second_interv">OPTION: <!--#echo var="VAR_incoming-check-interval-secondary"--></a> <li><a href="h_config_incoming_list">OPTION: <!--#echo var="VAR_incoming-check-list"--></a> @@ -3552,6 +3566,7 @@ There are also additional details on <li><a href="h_config_print_cat">OPTION: <!--#echo var="VAR_personal-print-category"--></a> <li><a href="h_config_print_command">OPTION: <!--#echo var="VAR_personal-print-command"--></a> <li><a href="h_config_post_char_set">OPTION: <!--#echo var="VAR_posting-character-set"--></a> +<li><a href="h_config_special_text_to_color">OPTION: <!--#echo var="VAR_h_config_special_text_to_color"--></a> <li><a href="h_config_postponed_folder">OPTION: <!--#echo var="VAR_postponed-folder"--></a> <li><a href="h_config_print_font_char_set">OPTION: Print-Font-Char-Set</a> <li><a href="h_config_print_font_name">OPTION: Print-Font-Name</a> @@ -3580,9 +3595,11 @@ There are also additional details on <li><a href="h_config_sending_filter">OPTION: <!--#echo var="VAR_sending-filters"--></a> <li><a href="h_config_sendmail_path">OPTION: <!--#echo var="VAR_sendmail-path"--></a> <li><a href="h_config_signature_color">OPTION: Signature Color</a> +<li><a href="h_config_special_text_color">OPTION: Special Text Color</a> <li><a href="h_config_signature_file">OPTION: <!--#echo var="VAR_signature-file"--></a> <li><a href="h_config_smtp_server">OPTION: <!--#echo var="VAR_smtp-server"--></a> <li><a href="h_config_sort_key">OPTION: <!--#echo var="VAR_sort-key"--></a> +<li><a href="h_config_thread_sort_key">OPTION: <!--#echo var="VAR_thread-sort-key"--></a> <li><a href="h_config_speller">OPTION: <!--#echo var="VAR_speller"--></a> <li><a href="h_config_sshcmd">OPTION: <!--#echo var="VAR_ssh-command"--></a> <li><a href="h_config_ssh_open_timeo">OPTION: <!--#echo var="VAR_ssh-open-timeout"--></a> @@ -4442,6 +4459,10 @@ thread: message in the thread was sent to you as a cc:. This symbol will only show up if the feature "<A HREF="h_config_mark_for_cc"><!--#echo var="FEAT_mark-for-cc"--></A>" is turned on (which is the default). + <LI> "." for messages that were sent to you as part of a group message, regardless + of if all addresses in the To: field are yours or not. This symbol will only show up if + the feature + "<A HREF="h_config_mark_for_group"><!--#echo var="FEAT_mark-for-me-in-group"--></A>" is turned on (which is the default). <LI> "X" for selected. You have selected at least one message in the thread by using the "select" command. (Some systems may optionally allow selected messages to be denoted by the index line being displayed in bold @@ -4601,6 +4622,10 @@ message: message was sent to you as a cc:. This symbol will only show up if the feature "<A HREF="h_config_mark_for_cc"><!--#echo var="FEAT_mark-for-cc"--></A>" is turned on (which is the default). + <LI> "." for messages that were sent to you as part of a group message, regardless + of if all addresses in the To: field are yours or not. This symbol will only show up if + the feature + "<A HREF="h_config_mark_for_group"><!--#echo var="FEAT_mark-for-me-in-group"--></A>" is turned on (which is the default). <LI> "X" for selected. You have selected the message by using the "select" command. (Some systems may optionally allow selected messages to be denoted by the index line being displayed in bold @@ -5515,6 +5540,163 @@ the names of the carbon copy addresses of the message. <End of help on this topic> </BODY> </HTML> +======= h_thread_index_sort_arrival ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Arrival</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Arrival</H1> + +The <EM>Arrival</EM> sort option arranges threads according to the last +time that a message was added to it. In this order the last thread +contains the most recent message in the folder. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_date ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Date</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Date</H1> + +The <EM>Date</EM> sort option in the THREAD INDEX screen sorts +threads by the date in which messages were sent. The thread containing the +last message in this order is displayed last. +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_subj ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Subject</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Subject</H1> + +The <EM>Subject</EM> sort option has not been defined yet. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_ordsubj ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: OrderedSubject</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: OrderedSubject</H1> + +The <EM>OrderedSubject</EM> sort option in the THREAD INDEX screen is +the same as sorting by <A HREF="h_thread_index_sort_subj">Subject</A>. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_thread ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Thread</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Thread</H1> + +The <EM>Thread</EM> sort option in the THREAD INDEX screen sorts all +messages by the proposed algorithm by Crispin and Murchison. In this +method of sorting once threads have been isolated they are sorted by the +date of their parents, or if that is missing, the first message in that +thread. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_from ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: From</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: From</H1> + +The <EM>From</EM> sort option has not been defined yet. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_size ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Size</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Size</H1> + +The <EM>Size</EM> sort option sorts threads by their size (the number +of messages in the thread). This could be used to find conversations +where no reply has been sent by any of the participants in the thread +(e.g. those whose length is equal to one). Longer threads appear +below shorter ones. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_score ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Score</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Score</H1> + +The <EM>Score</EM> sort option means that threads are sorted according to +the maximum score of a message in that thread. A thread all of whose +messages contain a smaller score than a message in some other thread is +placed in an earlier place in the list of messages for that folder; that +is, threads with the highest scores appear at the bottom of the index +list. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_to ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: To</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: To</H1> + +The <EM>To</EM> sort option has not been defined yet. + +<P> +<End of help on this topic> +</BODY> +</HTML> +======= h_thread_index_sort_cc ======= +<HTML> +<HEAD> +<TITLE>SORT OPTION: Cc</TITLE> +</HEAD> +<BODY> +<H1>SORT OPTION: Cc</H1> + +The <EM>Cc</EM> sort option has not been defined yet. + +<P> +<End of help on this topic> +</BODY> +</HTML> ======= h_index_cmd_whereis ======= <HTML> <HEAD> @@ -6738,6 +6920,46 @@ hold down the "Control" key on your keyboard. The second "^" "type the character ^". <P> +This version of Alpine contains an enhanced algorithm for justification, +which allows you to justify text that contains more complicated quote +strings. This algorithm is based on pragmatics, rather than on a theory, +and seems to work well with most messages. Below you will find technical +information on how this algorithm works. + +<P> +When justifying, Alpine goes through each line of the text and tries to +determine for each line what the quote string of that line is. The quote +string you provided is always recognized. Among other characters +recognized is ">". + +<P> +Some other constructions of quote strings are recognized only if they +appear enough in the text. For example "Peter :" is only +recognized if it appears in two consecutive lines. + +<P> +Additionaly, Alpine recognizes indent-strings and justifies text in a +paragraph to the right of indent-string, padding with spaces if necessary. +An indent string is one which you use to delimit elements of a list. For +example, if you were to write a list of groceries, one may write: + +<UL> +<LI> Fruit +<LI> Bread +<LI> Eggs +</UL> + +<P> +In this case the character "*" is the indent-string. Aline +recognizes numbers (0, 1, 2.5, etc) also as indent-strings, and certain +combinations of spaces, periods, and parenthesis. In any case, numbers are +recognized <B>ONLY</B> if the line preceeding the given line is empty or +ends in one of the characters "." or ":". +In addition to the explanation of what constitutes a paragraph above, a +new paragraph is recognized when an indent-string is found in it (and +validated according to the above stated rules). + +<P> <End of help on this topic> </BODY> </HTML> @@ -18300,6 +18522,7 @@ This set of special tokens may be used in the <A HREF="h_config_index_format">"<!--#echo var="VAR_index-format"-->"</A> option, in the <A HREF="h_config_reply_intro">"<!--#echo var="VAR_reply-leadin"-->"</A> option, in signature files, +in the <A HREF="h_config_reply_leadin_rules">"new-rules" option</A>, in template files used in <A HREF="h_rules_roles">"roles"</A>, and in the folder name that is the target of a Filter Rule. @@ -18312,7 +18535,7 @@ and in the target of Filter Rules. <P> <P> -<H1><EM>Tokens Available for all Cases (except Filter Rules)</EM></H1> +<H1><EM>Tokens Available for all Cases (except Filter Rules or in some cases for new-rules)</EM></H1> <DL> <DT>SUBJECT</DT> @@ -18345,6 +18568,15 @@ email address, never the personal name. For example, "mailbox@domain". </DD> +<DT>ADDRESSTO</DT> +<DD> +This is similar to the "TO" token, only it is always the +email address of all people listed in the TO: field of the messages. Addresses +are separated by a blank space. Example, "mailbox@domain" when +the e-mail message contains only one person in the To: field, or +"peter@flintstones.com president@world.com". +</DD> + <DT>MAILBOX</DT> <DD> This is the same as the "ADDRESS" except that the @@ -18392,6 +18624,15 @@ are unavailable) of the persons specified in the message's "Cc:" header field. </DD> +<DT>ADDRESSCC</DT> +<DD> +This is similar to the "CC" token, only it is always the +email address of all people listed in the Cc: field of the messages. Addresses +are separated by a blank space. Example: "mailbox@domain" when +the e-mail message contains only one person in the Cc: field, or +"peter@flintstones.com president@world.com". +</DD> + <DT>RECIPS</DT> <DD> This token represents the personal names (or email addresses if the names @@ -18400,6 +18641,14 @@ message's "To:" header field and the message's "Cc:" header field. </DD> +<DT>ADDRESSRECIPS</DT> +<DD> +This token represent the e-mail addresses of the people in the To: and +Cc: fields, exactly in that order separated by a space. It is almost obtained +by concatenating the ADDRESSTO and ADDRESSCC tokens. +</DD> + + <DT>NEWSANDRECIPS</DT> <DD> This token represents the newsgroups from the @@ -18745,7 +18994,11 @@ aspects of the message's state. The first character is either blank, a '*' for message marked Important, or a '+' indicating a message addressed directly to you (as opposed to your having received it via a -mailing list, for example). +mailing list, for example). The symbol '.' will be used +for messages that were sent to you as part of a group message, regardless +of if all addresses in the To: field are yours or not. This symbol will only show up if +the feature +"<A HREF="h_config_mark_for_group"><!--#echo var="FEAT_mark-for-me-in-group"--></A>" is turned on (which is the default). When the feature "<A HREF="h_config_mark_for_cc"><!--#echo var="FEAT_mark-for-cc"--></A>" is set, if the first character would have been @@ -18881,6 +19134,14 @@ The progression of sizes used looks like: <P> </DD> +<DT>SIZETHREAD</DT> +<DD> +This token represents the total size of the thread for a collapsed thread +or the size of the branch for an expanded thread. The field is omitted for +messages that are not top of threads nor branches and it defaults to +the SIZE token when your folders is not sorted by thread. +</DD> + <DT>SIZENARROW</DT> <DD> This token represents the total size, in bytes, of the message. @@ -19496,6 +19757,78 @@ This is an end of line marker. </DL> <P> +<H1><EM>Tokens Available Only for New-Rules</EM></H1> + +<DL> +<DT>FOLDER</DT> +<DD> +Name of the folder where the rule will be applied +</DD> +</DL> + +<DL> +<DT>COLLECTION</DT> +<DD> +Name of the collection list where the rule will be applied. +</DD> +</DL> + +<DL> +<DT>ROLE</DT> +<DD> +Name of the Role used to reply a message. +</DD> +</DL> + +<DL> +<DT>BCC</DT> +<DD> +Not implemented yet, but it will be implemented in future versions. It will +be used for <A HREF="h_config_compose_rules">compose</A> +<A HREF="h_config_reply_rules">reply</A> +<A HREF="h_config_forward_rules">forward</A> +rules. +</DD> +</DL> + +<DL> +<DT>LCC</DT> +<DD> +This is the value of the Lcc: field at the moment that you start the composition. +</DD> +</DL> + +<DL> +<DT>FORWARDFROM</DT> +<DD> +This corresponds to the personal name (or address if there's no personal +name) of the person who sent the message that you are forwarding. +</DD> +</DL> + +<DL> +<DT>FORWARDADDRESS</DT> +<DD> +This is the address of the person that sent the message that you +are forwarding. +</DD> +</DL> + + + + +<DL> +<DT>FLAG</DT> +<DD> +A string containing the value of all the flags associated to a specific +message. The possible values of allowed flags are "*" for Important, "N" +for recent or new, "U" for unseen or unread, "R" for seen or read, "A" for +answered and "D" for deleted. See an example of its use in the +<A HREF="h_config_new_rules">new rules</A> explanation and example help. +</DD> +</DL> + +<P> <H1><EM>Token Available Only for Templates and Signatures</EM></H1> <DL> @@ -20420,6 +20753,53 @@ give up and consider it a failed connection. <End of help on this topic> </BODY> </HTML> +====== h_config_sleep ====== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_sleep-interval-length"--> (UNIX Alpine only)</TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_sleep-interval-length"--> (UNIX Alpine only)</H1> + +<P> +When you open an attachment, Alpine goes through a list of viewers either +in your .mailcap file, or some other mailcap file in your system. The +normal behavior is that Alpine opens a local copy of the attachment, which +is removed from the system <B>after</B> the viewer has completed +displaying it. For example, if you open an attachment on a viewer and +later close the viewer, then control will return to the system and the +copy of the attachment will be removed from the system. This is the normal +behavior and has been accepted for years as the correct behavior. + +<P> +However, if an instance of the viewer is already open, the viewer may +return control to the system <B>before</B> it reads the copy of the +attachment. Given that Alpine removes the copy of the attachment after +control is returned to the system, this may cause Alpine to remove the +copy of the attachment <B>before</B> it is actually opened by the viewer. + +<P> +Since Alpine has no control over when a viewer returns from opening a file, +and viewers should not return before they read the file, Alpine has no control +over when the viewer will read the file and avoid the problem described above. + +<P> +The value of this option is the number of seconds that Alpine will wait +between the time that the viewer returns control to the system and the +when it will remove it from the system. You will not notice this +delay, since this will happen in the background. The default value is +0 which means that no delay will occur, and it is a value which may trigger +the problem described above with some viewers. By modifying the value of +this option you can set Alpine to wait longer. A reasonable small value is 5, +which works with all viewers tested to date. The maximum value is 120 +(2 minutes). +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_incoming_interv ====== <HTML> <HEAD> @@ -21484,6 +21864,102 @@ your account's home directory). <End of help on this topic> </BODY> </HTML> +====== h_config_maildir_location ====== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_maildir-location"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_maildir-location"--></H1> + +<P> +This option should be used only if you have a Maildir folder which you +want to use as your INBOX. If this is not your case (or don't know what +this is), you can safely ignore this option. + +<P> +This option overrides the default directory Pine uses to find the location of +your INBOX, in case this is in Maildir format. The default value of this +option is "Maildir", but in some systems, this directory could have been +renamed (e.g. to ".maildir"). If this is your case use this option to change +the default. + +<P> +The value of this option is prefixed with the "~/" string to determine the +full path to your INBOX. + +<P> +You should probably <A HREF="h_config_maildir">read</A> a few tips that +teach you how to configure your maildir for optimal performance. This +version also has <A HREF="h_config_courier_list">support</A> for the +Courier style file system when a maildir collection is accessed locally. + +<P><UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL> +<P> +<End of help on this topic> +</BODY> +</HTML> +====== h_config_maildir ===== +<HTML> +<HEAD> +<TITLE>Maildir Support</TITLE> +</HEAD> +<BODY> +<H1>Maildir Support</H1> + +This version of Alpine has been enhanced with Maildir support. This text is +intended to be a reference on its support. +<P> + +A Maildir folder is a directory that contains three directories called +cur, tmp and new. A program that delivers mail (e.g. postfix) will put new +mail in the new directory. A program that reads mail will look for for old +messages in the cur directory, while it will look for new mail in the new +directory. +<P> + +In order to use maildir support it is better to set your inbox-path to the +value "#md/inbox" (without quotes). This assumes that your mail +delivery agent is delivering new mail to ~/Maildir/new. If the directory +where new mail is being delivered is not called "Maildir", you can set the +name of the subdirectory of home where it is being delivered in the <A +HREF="h_config_maildir_location"><!--#echo var="VAR_maildir-location"--></A> configuration +variable. Most of the time you will not have to worry about the +<!--#echo var="VAR_maildirlocation"--> variable, because it will probably be set by your +administrator in the pine.conf configuration file. +<P> + +One of the advantages of the Maildir support of this version of Alpine is +that you do not have to stop using folders in another styles (mbox, mbx, +etc.). This is desirable since the usage of a specific mail storage system +is a personal decision. Folders in the maildir format that are part of the +Mail collection will be recognized without any extra configuration of your +part. If your mail/ collection is located under the mail/ directory, then +creating a new maildir folder in this collection is done by pressing "A" +and entering the string "#driver.md/mail/newfolder". Observe that adding a +new folder as "newfolder" may not create such folder in maildir format. + +<P> +If you would like to have all folders created in the maildir format by +default, you do so by adding a Maildir Collection. In order to convert +your current mail/ collection into a maildir collection, edit the +collection and change the path variable from "mail/" to +"#md/mail". In a maildir collection folders of any other format +are ignored. + +<P> Finally, This version also has +<A HREF="h_config_courier_list">support</A> for the Courier style file system +when a maildir collection is accessed locally. + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_literal_sig ===== <HTML> <HEAD> @@ -22246,6 +22722,45 @@ command, then it will not be re-sorted until the next time it is opened. <End of help on this topic> </BODY> </HTML> +====== h_config_thread_sort_key ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_thread-sort-key--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_thread-sort-key--></TITLE></H1> + +This option determines the order in which threads will be displayed. You +can choose from the options listed below. Each folder is sorted in one of +the sort orders displayed below first, then the thread containing the last +message of that sorted list is put at the end of the index. All messages +of that thread are "removed" from the sorted list and the +process is repeated with the remaining messages in that list. + +<P> +<UL> + <LI> <A HREF="h_thread_index_sort_arrival">Arrival</A> + <LI> <A HREF="h_thread_index_sort_date">Date</A> +<!-- <LI> <A HREF="h_thread_index_sort_subj">Subject</A> + <LI> <A HREF="h_thread_index_sort_ordsubj">OrderedSubj</A>--> + <LI> <A HREF="h_thread_index_sort_thread">Thread</A> +<!-- <LI> <A HREF="h_thread_index_sort_from">From</A> --> + <LI> <A HREF="h_thread_index_sort_size">Size</A> + <LI> <A HREF="h_thread_index_sort_score">Score</A> +<!-- <LI> <A HREF="h_thread_index_sort_to">To</A> + <LI> <A HREF="h_thread_index_sort_cc">Cc</A>--> +</UL> + +<P> Each type of sort may also be reversed. Normal default is by +"Thread". + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_other_startup ===== <HTML> <HEAD> @@ -22516,6 +23031,898 @@ character sets Alpine knows about by using the "T" ToCharsets command. <End of help on this topic> </BODY> </HTML> +====== h_config_procid ===== +<HTML> +<HEAD> +<TITLE>Token: PROCID</TITLE> +</HEAD> +<BODY> +<H1>TOKEN: PROCID explained</H1> + +<P> +The PROCID token is a way in which the user and the program can differentiate +between different parts of a program. It allows the user to tell the +program when to use a specific rule, and only use it at that specific +moment. + +<P> The normal way in which this is done is by adding a new configuration +variable. The idea behind the PROCID token is that instead of adding a new +configuration variable (which means the user has to go through more +configuration variables just to tune the program to his liking), we reuse +an old variable and let the user look inside that variable for the desired +behavior, which is actually set by setting the PROCID token. + +<P> +Consider the following examples for forward-rules: + +<P> +_ROLE_ == {work} => _SUBJECT_ := _COPY_{[tag] _SUBJECT_} + +<P> +and + +<P> +_ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ <_FORWARDADDRESS_>} + +<P> +both are triggered by the same condition. Since both are configured in the +same variable, only one of them will be executed all the time (whichever +is first). Therefore in order to differentiate, we add a _PROCID_ token. +So, for example, the first example above will be executed only when we are +determining the subject. In this case, the following rule will accomplish +this task + +<P> +_PROCID_ == {fwd-subject} && _ROLE_ == {work} => _SUBJECT_ := _COPY_{[tag] _SUBJECT_} + +<P> +In this case, this rule will be tested fully only when we are determining +the subject line of a forwarded message, not otherwise. + +<P> +It is wise to add the _PROCID_ token as the first condition in a rule, so +that other conditions will not be tested in a long list of rules. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_compose_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_compose-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_compose-rules"--></H1> + +<P> At this time, this option is used to generate values for signature +files that is not possible to do with the use of +<A HREF="h_rules_roles">roles</A>. + +<P> For example, you can have a rule like:<BR> +_TO_ >> {Peter Flinstones} => _SIGNATURE_{~/.petersignature} + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_forward_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_forward-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_forward-rules"--></H1> + +<P> This option has several uses. This feature uses the PROCID function +to identify different features of forwarding. You can read more about PROCID +by following <A HREF="h_config_procid">this link</A>. + +<P> If you want to edit the subject of a forwarded message, use the +PROCID <I>fwd-subject</I>. For example you could have a rule like + +<P> +_ROLE_ == {admin} && _SUBJECT_ !> {[tag] } => _COPY_{[tag] _SUBJECT_} + +<P> Another way in which this option can be used, is to trim the values of +some fields. For this application the PROCID is <I>fwd-lcc</I>. For +example it can be used in the following way: + +<P> +_ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ <_FORWARDADDRESS_>} + +<P> Other functions that can be used in this option are _EXEC_ and _REXTRIM_. + +<P> You can also use the _EXEC_ function. The documentation for this function +is in the +<A HREF="h_config_resub_rules"><!--#echo var="VAR_reply-subject-rules"--></A> +help text. + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_index_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_index-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_index-rules"--></H1> + +<P> This option is used to supersede the value of the option <A +HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> for specific folders. In +this form you can have different index-formats for different folders. For +example an entry here may be: + +<P> +_FOLDER_ == {INBOX} => _INDEX_{IMAPSTATUS DATE FROM(33%) SIZE SUBJECT(67%)} + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_pretty_command ===== +<HTML> +<HEAD> +<TITLE>Pretty-Command Explained</TITLE> +</HEAD> +<BODY> +<H1>Pretty Command Explained</H1> + +<P> This text explains how to encode keys so that they will be recognized +by Alpine in the _PKEY_ token. Most direct keystrokes are recognized in the +same way. For example, the key ~ is recognized by the same character. The +issue is how control, or functions keys are recognized. The internal code +is most times easy to find out. If the key you want to use is not already +recognized by Alpine simply press it. Alpine will print its code. For example, +the return key is not recognized in this screen, so if you press it, you +will see the following message. + +<P> [Command "RETURN" not defined for this screen. Use ? for help] + +<P> from here you can guess that the code for the return command is +RETURN. You can try other commands, like Control-C, the TAB key, F4, etc. +to see their codes. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_key_macro_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_key-definition-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_key-definition-rules"--></H1> + +<P> This option can be used to define macros, that is, to define a key that +when pressed executes a group of predetermined keystrokes. Since Alpine is +a menu driven program, sometimes the same key may have different meanings +in different screens, so a global redefinition of a key although possible +is not advisable. + +<P> <B>Always use the _SCREEN_ token as defined below.</B>. You have been +warned! + +<P> In each screen, every time you press a recognized key a command is +activated. In order to understand this feature, think of commands instead +of keystrokes. For example, you can think of the sort by thread command. +This command is associated to the keystrokes $ and h. You may want to +associate this command to a specific keystroke, like ~, so every time you +press the ~ key, Alpine understand the $ and h keystrokes, which activates +the sort by thread command. + +<P> Therefore, in order to use this option you must think of three +components. The screen where you will use the macro, the keystroke you +want to use and the set of keystrokes used by Alpine to accomplish the task +you want to accomplish. We will talk about these three components in what +follows. + +<P> First you must decide in which screen the macro will be used. This +feature is currently only available for the screen where your messages +are listed in index form (<A HREF="h_mail_index">MESSAGE INDEX</A>), +the screen where your message is displayed +(<A HREF="h_mail_view">MESSAGE TEXT</A>) and the screen where the list of +folders is displayed (<A HREF="h_folder_maint">FOLDER LIST</A>). The +internal names of these screens for this patch are "index", +"text" and +"folder" respectively. Please note that the internal names are +all in lowercase are are case sensitive. + +<P> In order to define the screen, you use the _SCREEN_ token, so for +example, you can write _SCREEN_ == {index}. + +<P> Second you must think of which key you will use to activate the macro. +Here you can use any key of your choice. The token you use to designate a +key is the _PKEY_ token (PKEY stands for "pressed key"). For +example you could use _PKEY_ == {~}, to designate the "~" +character to activate the command. Some keystrokes (like control, or +function keys) are encoded in special ways. You should read the +<A HREF="h_config_pretty_command">full explanation</A> on how to find +out the encoding for each keystroke. + +<P> Last, you must think of the list of keys you will use to accomplish +the task you want Alpine to perform. Say for example you want to have the +folder sorted by thread. That means you want Aline to execute the keys +"$" and "h". You use the _COMMAND_ function to specify +this. The syntax in this case is _COMMAND_{$,h}. + +<P> Observe that in the above example the different inputs are separated +by commas. This is the standard way in which the +<A HREF="h_config_init_cmd_list"><!--#echo var="VAR_initial-keystroke-list"--></A> command works from +the command line. Due to restrictions in the way Alpine works, a comma is a +special character, which when added to a configuration option like this +will cause the configuration to split into several lines in the +configuration screen. This has the effect of producing several +configuration options, all of which are incorrect. This is undesirable +because what you want is to have it all in one line. In order to force the +configuration into one line you must quote the comma. The best way to +accomplish this is by quoting the full definition of the rule. For +example. + +<P> +"_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{$,h}" + +<P> Another way to accomplish the same effect is by quoting the command and +not using quotes for the full command, nor commas to separate the +keystrokes in the command, for example + +<P> +_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{"$h"} + +<P> For more information on how to define the argument of the _COMMAND_ +token see the help of +<A HREF="h_config_init_cmd_list"><!--#echo var="VAR_initial-keystroke-list"--></A>. + +<P> Because the $ command can also be used as the first character in the +definition of an environemnt variable, no expansion of environment variables +is done when parsing this variable. The $ character does not need quoting +and quoting it will make Alpine fail to produce the correct result. + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_replace_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_replace-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_replace-rules"--></H1> + +<P> This option is used to have Alpine print different values for specific +tokens in the <A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>. For example you +can replace strings like "To: newsgroup" by your name. + +<P> Here are examples of possible rules:<BR> +_FOLDER_ != {sent-mail} && _NICK_ != {} => _FROM_ := _REPLACE_{_FROM_ (_NICK_)} + +<P> or if you receive messages with tags that contain arbitrary numbers, and +you want them removed from the index (but not from the subject), use a rule +like the following<BR> +_FOLDER_ == {INBOX} => _SUBJECT_ := _REXTRIM_{\[some-tag-here #[0-9].*\]} + +<P> You can also use this configuration option to remove specific strings of +the index display screen, so that you can trim unnecessary information in +your index, like the reply leadin string in the OPENINGTEXTNQ token of the index. +<BR> +_FOLDER_ == {mailing-list} => _OPENINGTEXTNQ_ := _REXTRIM_{On.*wrote: } + +<P> or if you receive messages with tags that contain arbitrary numbers, and +you want them removed from the index (but not from the subject), use a rule +like the following<BR> + +<P> You can also use the _EXEC_ function. The documentation for this function +is in the +<A HREF="h_config_resub_rules"><!--#echo var="VAR_reply-subject-rules"--></A> +help text. + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_reply_leadin_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_reply-leadin-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_reply-leadin-rules"--></H1> + +<P> This option is used to have Alpine generate a different +<A HREF="h_config_reply_intro"><!--#echo var="VAR_reply-leadin"--></A> string dependent either on +the person you are replying to, or the folder where the message is being +replied is in, or both. + +<P> Here there are examples of how this can be used. One can use the definition +below to post to newsgroups and the pine-info mailing list, say: +<P> +_FOLDER_ << {pine-info;_NEWS_} => _REPLY_{*** _FROM_ _ADDRESS_("_FROM_" "" "(_ADDRESS_) ")wrote in_NEWS_("" " the" "") _FOLDER_ _NEWS_("" "list " "")_SMARTDATE_("Today" "today" "on _LONGDATE_"):} + +<P> Here there is an example that one can use to change the reply indent string +to reply people that speak spanish. +<P> +_FROM_{Condorito;Quico} => _REPLY_{*** _FROM_ (_ADDRESS_) escribió _SMARTDATE_("Today" "hoy" "en _LONGDATE_"):} + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_resub_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_reply-subject-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_reply-subject-rules"--></H1> + +<P> This option is used to have Alpine generate a different subject when +replying rather than the one Alpine would generate automatically. + +<P> Here there are a couple of examples about how to use this +configuration option: + +<P> In order to have messages with empty subject to be replied with the message +"your message" use the rule<BR> +<center>_SUBJECT_ == {} => _RESUB_{Re: your message}</center> + +<P> If you want to trim some parts of the subject when you reply use the +rule<BR> +<center>_SUBJECT_ >> {[one];two} => _SUBJECT_ := _TRIM_{[;];two}</center> + +<P>this rule removes the brackets "[" and "]" whenever the string "[one]" +appears in it, it also removes the word "two" from it. + +<P>Another example where you may want to use this rule is when you +correspond with people that change the reply string from "Re:" +to "AW:" or "Sv:". In this case a rule like<BR> +<center>_SUBJECT_ >> {Sv: ;AW: } => _SUBJECT_ := _TRIM_{Sv: ;AW: }</center> +<P> +would eliminate undesired strings in replies. + +<P> Another interesting use of this option is the use of the _EXEC_ function. +This function takes as an argument a program or a script. This program +must take as the input a file, and write its output to that file. For example, +below is a sample of a script that removes the letter "a" of a file. + +<PRE> +#!/bin/sh +sed 's/a//g' $1 > /tmp/mytest +mv /tmp/mytest $1 +</PRE> + +<P> +As you can see this script took "$1" as input file, the sed program +wrote its output to /tmp/mytest, and then the move program moved the file +/tmp/mytest to the input file "$1". This is the kind of behavior +that your program is expected to have. + +<P> +The content of the input file ("$1" above) is the value of a token +like _SUBJECT_. In order to indicate this, we use the notation + +<P> +_SUBJECT_ := _EXEC_{/path/to/script} + +<P> for the action. So for example + +<P> +_FOLDER_ := {sent-mail} => _SUBJECT_ := _EXEC_{/path/to/script} + +<P> is a valid rule. + +<P> You can also use this configuration option to customize reply subjects +according to the sender of the message. + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> +</BODY> +</HTML> +====== h_config_sort_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_sort-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_sort-rules"--></H1> + +<P> This option is used to have Alpine sort different folders in different orders +and thus override the value already set in the +<A HREF="h_config_sort_key"><!--#echo var="VAR_sort-key"--></A> configuration option. + +<P> Here's an example of the way it can be used. In this case all incoming +folders are mailing lists, except for INBOX, so we sort INBOX by arrival +(which is the default type of sort), but we want all the rest of mailing +lists and newsgroups to be sorted by thread. + +<P> +_COLLECTION_ >> {Incoming-Folders;News} && _FOLDER_ != {INBOX} => _SORT_{tHread} + +<P> Another example could be<BR> +_FOLDER_ == {Mailing List} => _SORT_{Reverse tHread} + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> + +</BODY> +</HTML> +====== h_config_save_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_save-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_save-rules"--></H1> + +<P> This option is used to specify which folder should be used to save a +message depending either on the folder the message is in, who the message +is from, or text that the message contains in specific headers (Cc:, +Subject:, etc). + +<P> If this option is set and the +<A HREF="h_config_auto_read_msgs"><!--#echo var="FEAT_auto-move-read-msgs"--></A> configuration +option is also enabled then these definitions will be used to move messages +from your INBOX when exiting Alpine. + +<P>Here there are some examples<BR> +_FLAG_ >> {D} -> Trash<BR> +_FROM_ == {U2} -> Bono<BR> +_FOLDER_ == {comp.mail.pine} -> pine-stuff<BR> +_NICK_ != {} -> _NICK_/_NICK_<BR> +_DATEISO_ >> {02-10;02-11} -> archive-oct-nov-2002 + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> + +</BODY> +</HTML> +====== h_config_reply_indent_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_reply-indent-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_reply-indent-rules"--></H1> + +<P> This option is used to specify which reply-indent-string is to be used +when replying to an e-mail. If none of the rules are successful, the result in +the variable <a href="h_config_reply_indent_string"><!--#echo var="VAR_reply-indent-string"--></a> +is used. + +<P> The associated function to this configuration option is called "RESTR" (for +REply STRing). Some examples of its use are:<BR> +_FROM_ == {Your Boss} => _RESTR_{"> "}<BR> +_FROM_ == {My Wife} => _RESTR_{":* "}<BR> +_FROM_ == {Perter Flinstone;Wilma Flinstone} => _RESTR_{"_INIT_ > "}<BR> + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> + +</BODY> +</HTML> +====== h_config_smtp_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_smtp-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_smtp-rules"--></H1> + +<P> This option is used to specify which SMTP server should be used when +sending a message, if this rule is not defined, or the execution of the rule +results in no server selected, then Alpine will look for +the value from the role that is being used to compose the message. If no smtp +server is defined in that role or you are not using a role, then Alpine will get +the name of the server from the +<A HREF="h_config_smtp_server">"<!--#echo var="VAR_smtp-server"-->"</A> configuration +option according to the rules used in that variable. + +<P> The function associated to this configuration option is _SMTP_, an example +of the use of this function is<BR> +_ADDRESSTO_ == {peter@bedrock.com} => _SMTP_{smtp.bedrock.com} + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> + +</BODY> +</HTML> +====== h_config_startup_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_startup-rules"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_startup-rules"--></H1> + +<P> This option is used when a folder is being opened. You can use it to specify its <A +HREF="h_config_inc_startup"><!--#echo var="VAR_incoming-startup-rule"--></A> and override +Alpine's global value set for all folders. + +<P> An example of the usage of this option is:<BR> +_FOLDER_ == {Lynx;pine-info;_NEWS_} => _STARTUP_{first-unseen} + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P><End of help on this topic> + +</BODY> +</HTML> +====== h_config_new_rules ===== +<HTML> +<HEAD> +<TITLE>OPTION: New Rules Explained</TITLE> +</HEAD> +<BODY> +<H1>OPTION: New Rules Explained</H1> + +This is a quite powerful option. Here you can define rules that override +the values of any other option you have set in Alpine. + +<P> + For example, you can set your folders to be sorted in a certain way when +you open them (say by Arrival). You may want, however, your newsgroups to +be sorted by thread. The set of "rules" options allows you to +configure this and many other options, including the index-format for +specific folders, the way the subject is displayed in the index screen or +the reply-leadin-string, to name a few. + +<P> + Every rule has three parts: a condition, a separator and an action. The +action is what will happen if the condition of the rule is satisified. + +<P> + Here is an example: + +<P> + _FROM_ == {Fred Flinstone} => _SAVE_{Fred} + +<P> + Here the separator is "=>". Whatever is to the left of the separator +is the condition (that is _FROM_ == {Fred Flinstone}) and to the right is +the action (_SAVE_{Fred}). The condition means that the rule will be +applied only if the message that you are reading is from "Fred +Flinstone", and the action will be that you will be offered to save +it in the folder "Fred", whenever you press the letter +"S" to save a message. + +<P> + The separator is always "=>", with one exception to be seen +later. But for the most part this will be the only one you will ever need. + +<P> + Now let us see how to do it. There are 13 functions already defined for +you. These are: _EXEC_, _INDEX_, _REPLACE_, _REPLY_, _RESUB_, _SAVE_, +_SIGNATURE_, _SORT_, _STARTUP_, _TRIM_, _REXTRIM_, _THREADSTYLE and +_THREADINDEX_. The parameter of a function has to be enclosed between +"{" and "}", so for example you can specify +_SAVE_{saved-messages} as a valid sentence. + +<P> + Later in the document you will find examples. Here is a short +description of what each function does: + +<P> +<UL> +<LI> _EXEC_ : This function takes as an argument a program. This program +gets as the input a file and must rewrite its output to that file, which +is then taken as the value to replace from the contents of that file. You +can use this function with +<A HREF="h_config_resub_rules"><!--#echo var="VAR_reply-subject-rules"--></A>, +<A HREF="h_config_replace_rules"><!--#echo var="VAR_replace-rules"--></A> and +<A HREF="h_config_forward_rules"><!--#echo var="VAR_forward-rules"--></A>. +See the help of those options for examples of how to use this function +and configure these rules. +<BR> <BR> +<LI> _INDEX_ : This function takes as an argument an index-format, and +makes that the index-format for the specified folder. +<BR> <BR> +<LI> _REPLACE_ : This function replaces the subject/from of the given e-mail by +another subject/from only when displaying the index. +<BR> <BR> +<LI> _REPLY_ : This function takes as an argument a definition of a +reply-leadin-string and makes this the reply-leading-string of the +specified folder or person. +<BR> <BR> +<LI> _RESTR_ : This function takes as an argument the value of the +reply-indent-string to be used to answer the message being replied to. +<BR> <BR> +<LI> _RESUB_ : This function replaces the subject of the given e-mail by +another subject only when replying to a message. +<BR> <BR> +<LI> _SAVE_ : The save function takes as an argument the name of a +possibly non existing folder, whenever you want to save a message, that +folder will be offered for you to save. +<BR> <BR> +<LI> _SIGNATURE_ : This function takes as an argument a signature file and +uses that file as the signature for the message you are about to +compose/reply/forward. +<BR> <BR> +<LI> _SMTP_ : This function takes as an argument the definition of a +SMTP server. +<BR> <BR> +<LI> _SORT_ : This function takes as an argument a Sort Style, and sorts a +specified folder in that sort order. +<BR> <BR> +<LI> _TRIM_ : This function takes as an argument a list of strings that +you want removed from another string. At this time this only works for +_FROM_ and _SUBJECT_. +<BR> <BR> +<LI> _REXTRIM_ : Same as _TRIM_ but its argument is one and +only one extended regular expression. +<BR> <BR> +<LI> _STARTUP_ : This function takes as an argument an +incoming-startup-rule, and open an specified folder using that rule. +<BR> <BR> +<LI> _THREADSTYLE_ : This function takes as an argument a +threading-display-style and uses it to display threads in a folder. +<BR> <BR> +<LI> _THREADINDEX_ : This function takes as an argument a +threading-index-style and uses it to display threads in a folder. +</UL> + +<P> +You must me wondering how to define the person/folder over who to apply +the action. This is done in the condition. When you specify a rule, the +rule is only executed if the condition is satisfied. In another words for +the rule: + +<P> + _FROM_ == {Fred Flinstone} => _SAVE_{Fred} + +<P> it will only be applied if the from is "Fred Flinstone". If +the From is "Wilma Flinstone" the rule will be skipped. + +<P> In order to test a condition you can use the following tokens (in +alphabetical order): _ADDRESS_, _CC_, _FOLDER_, _FROM_,_NICK_, _ROLE, +_SENDER_, _SUBJECT_ and _TO_. The token will always be tested against what +it is between "{" and "}" in the condition, this part +of the condition is called the "condition set". The definition +of each token can be found <A HREF="h_index_tokens">here</A>. + +<P> A special testing token called _PROCID_ can be used to differentiate +inside a rule, between two rules that are triggered by the same condition. +A full explanation of the _PROCID_ token can be found in +<A HREF="h_config_procid">this link</A>. + +<P> There are two more tokens related to the option +<A HREF="h_config_key_macro_rules">key-definition-rules</A>. Those tokens +are only specific to that option, and hence are not explained here. + +<P> You can also test in different ways, you can use the following +"test operands": <<, !<, >>, !>, == and !=. +All of them are two strings long. Here is the meaning of them: + +<P> +<UL> +<LI> << : It tests if the value of the token is contained in +the condition set. Here for example if the condition set were equal to +"Freddy", then the condition: _NICK_ << {Freddy}, would be true if +the value of _NICK_ were "Fred", "red" or "Freddy". You are just looking +for substrings here. +<LI> >> : It tests if the value of the token contains the value of +the condition set. Here for example if the condittion set were equal to +"Fred", then the condition: _FROM_ >> {Fred}, would be true if +the value of _FROM_ were "Fred Flinstone" or "Fred P. Flinstone" or "Freddy". +<LI> == : It tests if the value of the token is exactly equal to the value +of the set condition. For example _NICK_ == {Fred} will be false if the value +of _NICK_ is "Freddy" or "red". +<LI> !< : This is true only when << is false and viceversa. +<LI> !> : This is true only when >> is false and viceversa. +<LI> != : This is true only when == is false and viceversa. +</UL> + +<P> + Now let us say that you want the same action to be applied to more than +one person or folder, say you want "folder1" and "folder2" to be sorted by +Ordered Subject upon entering. Then you can list them all of them in the +condition part separting them by a ";". Here is the way to do it. + +<P> + _FOLDER_ << {folder1; folder2} => _SORT_{OrderedSubj} + +<P> + Here is the first subtelty about these definitions. Notice that the +following rule: + +<P> + _FOLDER_ == {folder1; folder2} => _SORT_{Reverse OrderedSubj} + +<P> works only for "folder1" but not for "folder2". This is because the +comparison of the name of the folder is done with whatever is in between +"{", ";" or "}", so in the above rule you would be testing <BR> +"folder2" == " folder2". The extra space makes the difference. +The reason why the first rule does not fail is because +"folder2" << " folder2" is actually +true. If something ever fails this may be something to look into. + +<P> + Here are a few examples of what we have talked about before. + +<P> +_NICK_ == {lisa;kika} => _SAVE_{_NICK_/_NICK_} <BR> +This means that if the nick is lisa, it will +save the message in the folder "lisa/lisa", and if the nick +is "kika", it will save the message in the folder "kika/kika" + +<P> +_FOLDER_ == {Lynx} -> lynx <BR> +This, is an abreviation of the following rule:<BR> +_FOLDER_ == {Lynx} => _SAVE_{lynx} <BR> +(note the change in separator from "=>" to "->"). In the future +I will use that abreviation. + +<P> _FOLDER_ << {comp.mail.pine; pine-info; pine-alpha} -> pine <BR> +Any message in the folders "comp.mail.pine", "pine-info" or "pine-alpha" +will be saved to the folder "pine". + +<P> _FROM_ << {Pine Master} -> pine <BR> +Any message whose From field contains +"Pine Master" will be saved in the folder pine. + +<P> _FOLDER_ << {Lynx; pine-info; comp.mail.pine} => +_INDEX_{IMAPSTATUS MSGNO DATE FROMORTO(33%) SUBJECT(66%)} <BR> Use a +different index-format for the folders "Lynx", "pine-info" and +"comp.mail.pine", where the size is not present. + +<P> _FOLDER_ == {Lynx;pine-info} => _REPLY_{*** _FROM_ (_ADDRESS_) +wrote in the _FOLDER_ list _SMARTDATE_("Today" "today" "on +_LONGDATE_"):}<BR> If a message is in one of the incoming folders "Lynx" +or "pine-info", create a reply-leadin-string that acknowledges that. Note +the absence of "," in the function _SMARTDATE_. For example answering to a +message in the pine-info list would look like: + +<P> +*** Steve Hubert (hubert@cac.washington.edu) wrote in the pine-info list today: + +<P> +However replying for a message in the Lynx list would look: + +<P> +*** mattack@area.com (mattack@area.com) wrote in the Lynx list today: + +<P> +If you write in more than one language you can use this feature to create +Reply-leadin-strings in different languages. + +<P> Note that at least for people you can create particular +reply-leadin-string using the role features, but it does not work as this +one does. This seems to be the right way to do it. + +<P> _FOLDER_ << {Lynx; comp.mail.pine; pine_info; pine-alpha} => +_SORT_{OrderedSubj}<BR> This means upon opening, sort the folders "Lynx", +"comp.mail.pine", etc in ordered subject. All the others use the default +sort order. You can not sort in reverse in this form. The possible +arguments of this function are listed in the definition of the +default-sort-rule (Arrival, scorE, siZe, etc). + +<P> The last examples use the function _TRIM_ which has a special form. +This function can only be used in the index list. + +<P> _FOLDER_ << {Lynx} => _SUBJECT_ := _TRIM_{lynx-dev }<BR> In +the folder "Lynx" eliminate from the subject the string "lynx-dev " (with +the space at the end). For example a message whose subject is "Re: +lynx-dev unvisited Visited Links", would be shown in the index with +subject: "Re: unvisited Visited Links", making the subject shorter and +giving the same information. + +<P> _FROM_ >> {Name (Comment)} => _FROM_ := +_TRIM_{ (Comment)}<BR> Remove the part " (Comment)" +from the _FROM_, so when displaying in the index the real From "Name" +will appear. + +<P> _SUBJECT_ == {} => _RESUB_{Re: your mail without subject} +If there is no subject in the message, use the subject "Re: your mail +wiyhout subject" as a subject for the reply message. + +<P> You can add more complexity to your rules by checking more than one +conditions before a rule is executed. For example: Assume that you want to +answer every email that contains the string "bug report", with the subject +"Re: About your bug report", you could make + +<P> +_SUBJECT_ == {bug report} => _RESUB_{Re: About your _SUBJECT_} + +<P> The problem with this construction is that if the person emails you +back, then the next time you answer the message the subject will be: "Re: +About your Re: About your bug report", so it grew. You may want to avoid +this growth by using the following rule: + +<P> +_SUBJECT_ >> {bug report} && _SUBJECT_ !> {Re: } => _RESUB_{Re: About your _SUBJECT_}<BR> + +<P> +which will only add the string "Re: About your" only the first time the +message is replied. + +<P> + Say your personal name is "Fred Flinstones", and assume that you don't +like to see "To: comp.mail.pine" in every post you make to this newsgroup, +but instead would like to see it as everyone else sees it. <BR> +_FOLDER_ == {comp.mail.pine} && _FROM_ == {Fred Flinstones} => _FROM_ := _REPLACE_{_FROM_} + +<P> + You can also list your index by nick, in the following way:<BR> +_NICK_ != {} => _FROM_ := _REPLACE_{_NICK_} + +<P> + If you want to open the folder "pine-info" in the first non-read message +use the rule:<BR> +_FOLDER_ == {pine-info} => _STARTUP_{first-unseen} + +<P> + If you want to move your deleted messages to a folder, called "Trash", use +the following rule:<BR> +_FLAG_ >> {D} -> Trash + +<P> +The reason why the above test is not "_FLAG_ == {D}" is because that would mean +that this is the only flag set in the message. It's better to test by containment in this case. + +<P> If you want to use a specific signature when you are in a specific collection +use the following rule:<BR> +_COLLECTION_ == {Mail} => _SIGNATURE_{/full/path/to/.signature} + +<P> Finally about the question of which rule will be executed. Only the +first rule that matches will be executed. It is important to notice though +that "saving" rules do not compete with "sorting" rules. So the first +"saving" rule that matches will be executed in the case of saving and so +on. + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_char_set ===== <HTML> <HEAD> @@ -22827,6 +24234,43 @@ That won't work because spell works in a different way. <End of help on this topic> </BODY> </HTML> +====== h_config_special_text_to_color ===== +<HTML> +<HEAD> +<TITLE>OPTION: <!--#echo var="VAR_special-text-color"--></TITLE> +</HEAD> +<BODY> +<H1>OPTION: <!--#echo var="VAR_special-text-color"--></H1> + +Use this option to enter patterns (text or regular expressions) that +Alpine will highlight in the body of the text that is not part of a handle +(an internal or external link that Alpine paints in a different color). + +<P> +Enter each pattern in a different line. Pine will internally merge these +patterns (by adding a "|" character), or you can add them all in one line +by separating them by a "|" character. There is only a <A +HREF="h_regex_text">set</A> of regular expressions that are matched. + +<P> +Pine will use the colors defined in the +<A HREF="h_config_special_text_color">Special Text Color</A> variable. +to paint any match. + +<P> +If the Special Text Color is not set, setting this variable will not +cause that special text to be indicated in any special way. It will look +like any normal text. You must set those colors in order to make Pine +paint the screen differently when it finds the patterns specified in this +variable. + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_display_filters ===== <HTML> <HEAD> @@ -22991,6 +24435,12 @@ specified. Command Modifying Tokens: <DL> +<DT>_ADDRESS_</DT> +<DD>When the command is executed, this token is replaced +with the address of the person sending the message in the format +mailbox@host. +</DD> + <DT>_RECIPIENTS_</DT> <DD>When the command is executed, this token is replaced with the space delimited list of recipients of the @@ -26103,6 +27553,76 @@ the From field is used to show the relationships instead of the Subject field. <End of help on this topic> </BODY> </HTML> +====== h_config_thread_display_style_rule ===== +<HTML> +<HEAD> +<TITLE>OPTION: Threading-Display-Style-Rule</TITLE> +</HEAD> +<BODY> +<H1>OPTION: Threading-Display-Style-Rule</H1> + +This option is very similar to <A HREF="h_config_thread_disp_style"> +<!--#echo var="VAR_threading-display-style"--></A>, but it is a rule which specifies the +display styles for a thread that you want displayed in a specific +folder or collection. +<P> +The token to be used in this function is _THREADSTYLE_. Here there is +an example of its use +<P> +_FOLDER_ == {pine-info} => _THREADSTYLE_{mutt-like} +<P> +The values that can be given for the _THREADSTYLE_ function are the +values of the threading-display-style function, which can be found +listed in the <A HREF="h_config_thread_disp_style">threading-display-style</A> +configuration option. + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> +====== h_config_thread_index_style_rule ===== +<HTML> +<HEAD> +<TITLE>OPTION: Threading-Index-Style-Rule</TITLE> +</HEAD> +<BODY> +<H1>OPTION: Threading-Index-Style-Rule</H1> + +This option is very similar to <A HREF="h_config_thread_index_style"> +<!--#echo var="VAR_threading-index-style"--></A>, but it is a rule which specifies the +index styles for a thread that you want displayed in a specific +folder or collection. +<P> +The token to be used in this function is _THREADINDEX_. Here there is +an example of its use +<P> +_FOLDER_ == {pine-info} => _THREADINDEX_{regular-index-with-expanded-threads} +<P> +The values that can be given for the _THREADINDEX_ function are the +values of the threading-index-display function, which can be found +listed in the <A HREF="h_config_thread_index_style"><!--#echo var="VAR_threading-index-style"--></A> +configuration option. + +<P> This configuration option is just one of many that allow you to +override the value of some global configurations within Alpine. There is a +help text explaining how to define all of them, which you can read by +following this <A HREF="h_config_new_rules">link</A>. + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_pruning_rule ===== <HTML> <HEAD> @@ -28009,6 +29529,22 @@ See also <A HREF="h_config_allow_chg_from">"<!--#echo var="FEAT_allow-chang <End of help on this topic> </BODY> </HTML> +====== h_config_use_domain ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_return-path-uses-domain-name"--> </TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_return-path-uses-domain-name"--></H1> + +If you enable this configuration option Pine will use your domain name and your +username in that domain name to construct your Return-Path header, if not Pine +will use the address that you have set in the From: field to construct it. + +<P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_use_sender_not_x ===== <HTML> <HEAD> @@ -28605,6 +30141,71 @@ of flowed text. <End of help on this topic> </BODY> </HTML> +====== h_config_alt_reply_menu ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_alternate-reply-menu"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_alternate-reply-menu"--></H1> + +This feature controls the menu that is displayed when Reply is selected. +If set, a list of options will be presented, with each option representing +the type of composition that could be used. This feature is most useful +for users who want to avoid being prompted with each option separately, or +would like to override some defaults set in your configuration for the +message that you are replying (e.g. you may have set the option to strip +signatures, but for the message you are answering you would like not to do +that) + +<P> +The way this feature works is as follows. Initially you get the question +if you want to include the message, and in the menu you will see several +options, each option is accompanied by some text explaining what will +happen if you press the associated command. For example, if you read the +text "S Strip Sig", it means that if you press the letter +"S" the signature will be stripped off the message you are +replying. Observer that the menu will change to +"S No Strip", which means that if you press "S", the +signature will not be stripped off from the message. Your choices are +activated when you press RETURN. + +<P> +Another way to remember what Pine will do, is that what will be done is +exactly the opposite of what you read in the menu. + +<P> +The possible options are: + +<OL> +<LI> A: This determines if Pine will include or not the attachments sent to +you in the message that you are replying. By default Pine will use the value +of the configuration option +<A HREF="h_config_attach_in_reply"><!--#echo var="FEAT_include-attachments-in-reply"--></A>, but +you can use this option to override such behavior in a per message basis. + +<LI> F: To decide if you want to send flowed text or not. This option appears +unless you have quelled sending flowed text. + +<LI> H: This option determines if the headers of a message are to be +included in the body of the message that is being replied. By default Pine +will use the value of the configuration option +<A HREF="h_config_include_header"><!--#echo var="FEAT_include-header-in-reply"--></A>, but +you can use this option to override such behavior in a per message basis. + +<LI> R: To set a role, if you do not want Pine to set one automatically for you +or would like to set one when you can not select any. + +<LI> S: To strip the signature from a message, only available is the feature + <a href="h_config_sigdashes"><!--#echo var="FEAT_enable-sigdashes"--></a> or the +<a href="h_config_strip_sigdashes"><!--#echo var="FEAT_strip-from-sigdashes-on-reply"--></a> option are +enabled. + +</OL> +<P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_del_from_dot ===== <HTML> <HEAD> @@ -29128,6 +30729,38 @@ Ctrl-B key can be used to select the previous web hostnames in the same way. <End of help on this topic> </BODY> </HTML> +====== h_config_enable_long_url ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_enable-msg-view-long-url"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_enable-msg-view-long-url"--></H1> + +This feature modifies the behavior of Alpine's MESSAGE TEXT screen. When this feature +is set alpine will attempt to recognize long urls (those that spread over several +lines in the text) for the HTTP protocol, even when they have not been enclosed between +delimiters "<" and ">". + +<P>The normal behavior in Alpine is that if a URL is preceeded by the "<" +character and this URL was not finished before the end of the line, then a +continuation of the URL is searched in the following line(s). Normally, this type of +URLs will be ended by the ">" character, and if it is not, there is a +possibility of including erroneous text into the URL. + +<P>Enabling this feature will make Alpine search for a continuation of certain URLs in +lines following its location. This will be of great help most times, but in some cases +the algorithm will catch some text into the URL that is not part of the URL. + +<P>If you find that Alpine failed to recognize correctly a URL simply edit the URL before +passing it to your browser. + +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_enable_view_addresses ===== <HTML> <HEAD> @@ -29162,6 +30795,27 @@ Ctrl-B key can be used to select the previous web hostnames in the same way. <End of help on this topic> </BODY> </HTML> +====== h_config_circular_tab ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_enable-circular-tab"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_enable-circular-tab"--></H1> + +<P> +This Feature is like +<A HREF="h_config_auto_open_unread">"<!--#echo var="FEAT_auto-open-next-unread"-->"</A>, +in the sense that you can use TAB to browse through all of your Incoming +Folders checking for new mail. Once it gets to the last folder of the +collection it goes back to check again until it returns to the original +folder where it started. +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_enable_view_arrows ===== <HTML> <HEAD> @@ -29435,6 +31089,49 @@ than across the columns as is the default. <End of help on this topic> </BODY> </HTML> +====== h_config_courier_list ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_courier-folder-list"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_courier-folder-list"--></H1> + +In a maildir collection, a folder could be used as a directory to store +folders. In the Courier server if you create a folder, then a directory +with the same name is created. If you use this patch to access a +collection created by the Courier server, then the display of such +collection will look confusing. The best way to access a maildir +collection created by the Courier server is by using the "#mc/" +prefix instead of the "#md/" prefix. If you use this alternate +prefix, then this feature applies to you, otherwise you can safely ignore +the text that follows. +<P> +Depending on if you have enabled the option +<a href="h_config_separate_fold_dir_view"><!--#echo var="FEAT_separate-folder-and-directory-entries"--></a> +a folder may be listed as "folder[.]", or as two entries in the +list by "folder" and "folder.". +<P> +If this option is disabled, Pine will list local folders that are in Courier +style format, as "folder", and those that are also directories as +"folder[.]". This makes the default display cleaner. +<P> +If this feature is enabled then creating folders in a maildir collection +will create a directory with the same name. If this feature is disabled, then +a folder is considered a directory only if it contains subfolders, so you can +not create a directory with the same name as an exisiting folder unless +you create a subfolder of that folder first (e.g. if you have a folder +called "foo" simply add "foo.bar" directly. This will +create the directory "foo" and the subfolder "bar" of it). +<P> +Observe that this feature works only for maildir collections that are accessed +locally. If a collection is accessed remotely then this feature has no value, +as the report is created in a server, and Pine only reports what received +from the server in this case. +<P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_verbose_post ===== <HTML> <HEAD> @@ -29589,6 +31286,29 @@ them as deleted in the INBOX. Messages in the INBOX marked with an <End of help on this topic> </BODY> </HTML> +====== h_config_auto_read_msgs_rules ===== +<HTML> +<HEAD> +<TITLE>FEATURE: auto-move-read-msgs-using-rules</TITLE> +</HEAD> +<BODY> +<H1>FEATURE: auto-move-read-msgs-using-rules</H1> +This feature controls an aspect of Alpine's behavior upon quitting. If set, +and the +<A HREF="h_config_read_message_folder">"<!--#echo var="VAR_read-message-folder"-->"</A> +option is also set, then Alpine will automatically transfer all read +messages to the designated folder using the rules that you have defined in +your +<A HREF="h_config_save_rules">"<!--#echo var="VAR_save-rules"-->"</A> and mark +them as deleted in the INBOX. Messages in the INBOX marked with an +"N" (meaning New, or unseen) are not affected. +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_auto_fcc_only ===== <HTML> <HEAD> @@ -30039,6 +31759,23 @@ Reply Use, Forward Use, and Compose Use. <End of help on this topic> </BODY> </HTML> +====== h_config_enhanced_thread ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_enhanced-fancy-thread-support"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_enhanced-fancy-thread-support"--></H1> + +If this option is set certain commands in Pine will operate in loose +threads too. For example, the command ^R marks a thread deleted, but if +this feature is set, it will remove all threads that share the same missing +parent with this thread. + +<P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_news_cross_deletes ===== <HTML> <HEAD> @@ -30517,6 +32254,40 @@ but that is not implemented. <End of help on this topic> </BODY> </HTML> +====== h_config_ignore_size ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_ignore-size-changes"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_ignore-size-changes"--></H1> + +When you have an account residing in an IMAP server, Alpine gets the size of +each message from the server. However, when Alpine saves a message residing +in an IMAP server, Alpine computes the size of the message independently. If +these two numbers do not match for a message, Alpine asks you if you still +want to take the risk of saving the message, since data corruption or loss +of data could result of this save. + +<P> +Sometimes the root of this problem is that the server is defective, and +there will not be loss of information when saving such message. Enabling +this feature will make Aline ignore such error and continue saving the +message. If you can determine that this is the case, enable this feature +so that the saving operation will succeed. An example of a defective server +is the Gmail IMAP server. Another example is some versions of the Exchange +server. + +<P> +It is recommended that this feature be disabled most of the time and only +enabled when you find a server which you can determine that has the above +mentioned defect, but be disabled again after making this operation +succeed. + +<P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_force_low_speed ===== <HTML> <HEAD> @@ -31204,6 +32975,30 @@ to see the available Editing and Navigation commands. <End of help on this topic> </BODY> </HTML> +====== h_config_special_text_color ===== +<HTML> +<HEAD> +<TITLE>OPTION: Special Text Color</TITLE> +</HEAD> +<BODY> +<H1>OPTION: Special Text Color</H1> + +Sets the color Pine uses for coloring any text in the body of the message +that is not part of a handle (and internal or external link that Pine +paints in a different color). By default, this variable is not defined, +which means that text that matches the pattern is not painted in any +particular way. This variable must be set in a special form if you +want text to be painted. + +<P> +<A HREF="h_color_setup">Descriptions of the available commands</A> +<P> +Look <A HREF="h_edit_nav_cmds">here</A> +to see the available Editing and Navigation commands. +<P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_index_arrow_color ===== <HTML> <HEAD> @@ -33013,6 +34808,28 @@ messages. <End of help on this topic> </BODY> </HTML> +====== h_config_mark_for_group ===== +<HTML> +<HEAD> +<TITLE>FEATURE: <!--#echo var="FEAT_mark-for-me-in-group"--></TITLE> +</HEAD> +<BODY> +<H1>FEATURE: <!--#echo var="FEAT_mark-for-me-in-group"--></H1> + +This feature affects Alpine's MESSAGE INDEX display. +By default, a '+' is displayed in the first column if the +message is addressed directly to you. +When this feature is set and the message is addressed to you as part of a group message +(that is, your address appears in the To: field, but there is more than one recipient), then a +'.' character is displayed instead. + +<P> +<UL> +<LI><A HREF="h_finding_help">Finding more information and requesting help</A> +</UL><P> +<End of help on this topic> +</BODY> +</HTML> ====== h_config_mark_for_cc ===== <HTML> <HEAD> @@ -33022,7 +34839,7 @@ messages. <H1>FEATURE: <!--#echo var="FEAT_mark-for-cc"--></H1> This feature affects Alpine's MESSAGE INDEX display. -By default, a '+' is displayed in the first column if the +By default, a '+' or a '.' is displayed in the first column if the message is addressed directly to you. When this feature is set and the message is not addressed to you, then a '-' character is displayed if the message is instead Cc'd directly diff --git a/pith/pineelt.h b/pith/pineelt.h index e44ae37a..d88f5fb9 100644 --- a/pith/pineelt.h +++ b/pith/pineelt.h @@ -40,6 +40,7 @@ typedef struct pine_elt { PINETHRD_S *pthrd; PARTEX_S *exceptions; ICE_S *ice; + char *firsttext; /* per-message pine state bits */ unsigned int hidden:1; unsigned int excluded:1; diff --git a/pith/reply.c b/pith/reply.c index 3445097f..348ebf26 100644 --- a/pith/reply.c +++ b/pith/reply.c @@ -46,6 +46,8 @@ static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washingto #include "../pith/ablookup.h" #include "../pith/mailcmd.h" #include "../pith/margin.h" +#include "../pith/copyaddr.h" +#include "../pith/rules.h" /* @@ -814,8 +816,27 @@ char * reply_quote_str(ENVELOPE *env) { char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1]; + char reply_string[MAX_PREFIX+1]; + + { RULE_RESULT *rule; + rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE , env); + if (rule){ + strncpy(reply_string,rule->result,sizeof(reply_string)); + reply_string[sizeof(reply_string)-1] = '\0'; + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + else + if ((ps_global->VAR_REPLY_STRING) && (ps_global->VAR_REPLY_STRING[0])){ + strncpy(reply_string,ps_global->VAR_REPLY_STRING, sizeof(reply_string)-1); + reply_string[sizeof(reply_string)-1] = '\0'; + } + else + strncpy(reply_string,"> ",sizeof("> ")); + } - strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1); + strncpy(buf, reply_string, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; /* set up the prefix to quote included text */ @@ -867,10 +888,29 @@ reply_quote_str(ENVELOPE *env) int reply_quote_str_contains_tokens(void) { - return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] && - (strstr(ps_global->VAR_REPLY_STRING, from_token) || - strstr(ps_global->VAR_REPLY_STRING, nick_token) || - strstr(ps_global->VAR_REPLY_STRING, init_token))); + char *reply_string; + + reply_string = (char *) malloc( 80*sizeof(char)); + { RULE_RESULT *rule; + rule = get_result_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE, NULL); + if (rule){ + reply_string = cpystr(rule->result); + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + else + if ((ps_global->VAR_REPLY_STRING) && (ps_global->VAR_REPLY_STRING[0])){ + strncpy(reply_string,ps_global->VAR_REPLY_STRING, sizeof(reply_string)-1); + reply_string[sizeof(reply_string)-1] = '\0'; + } + else + reply_string = cpystr("> "); + } + return(reply_string && reply_string[0] && + (strstr(reply_string, from_token) || + strstr(reply_string, nick_token) || + strstr(reply_string, init_token))); } @@ -972,7 +1012,7 @@ reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, if(!orig_body || orig_body->type == TYPETEXT || reply_raw_body - || F_OFF(F_ATTACHMENTS_IN_REPLY, ps_global)){ + || !ps_global->reply.attach){ char *charset = NULL; /*------ Simple text-only message ----*/ @@ -980,7 +1020,7 @@ reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, body->type = TYPETEXT; body->contents.text.data = msgtext; reply_delimiter(env, role, pc); - if(F_ON(F_INCLUDE_HEADER, ps_global)) + if(ps_global->reply.inchdr) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); @@ -1038,7 +1078,7 @@ reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, if(reply_body_text(orig_body, &tmp_body)){ reply_delimiter(env, role, pc); - if(F_ON(F_INCLUDE_HEADER, ps_global)) + if(ps_global->reply.inchdr) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); @@ -1076,7 +1116,7 @@ reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, body->nested.part->body.subtype = cpystr("Plain"); } reply_delimiter(env, role, pc); - if(F_ON(F_INCLUDE_HEADER, ps_global)) + if(ps_global->reply.inchdr) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); @@ -1099,7 +1139,7 @@ reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, int partnum; reply_delimiter(env, role, pc); - if(F_ON(F_INCLUDE_HEADER, ps_global)) + if(ps_global->reply.inchdr) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); @@ -1334,6 +1374,10 @@ get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen) buf[0] = '\0'; switch(type){ + case iFfrom: + addr = env && env->sparep ? env->sparep : NULL; + break; + case iFrom: addr = env ? env->from : NULL; break; @@ -1719,21 +1763,150 @@ get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size break; + case iProcid: + if(ps_global->procid){ + strncpy(buf, ps_global->procid, maxlen); + buf[maxlen] = '\0'; + } + break; + + case iRole: + if (ps_global->role){ + strncpy(buf, ps_global->role, maxlen); + buf[maxlen] = '\0'; + } + break; + + case iRoleNick: + if(role && role->nick){ + strncpy(buf, role->nick, maxlen); + buf[maxlen] = '\0'; + } + break; + + case iPkey: + if(ps_global->pressed_key){ + strcpy(buf, ps_global->pressed_key); + buf[maxlen] = '\0'; + } + break; + + case iScreen: + if(ps_global->screen_name){ + strncpy(buf, ps_global->screen_name, maxlen); + buf[maxlen] = '\0'; + } + break; + + case iFfrom: case iFrom: case iTo: case iCc: case iSender: case iRecips: case iInit: + if (env) get_addr_data(env, type, buf, maxlen); break; - case iRoleNick: - if(role && role->nick){ - strncpy(buf, role->nick, maxlen); - buf[maxlen] = '\0'; - } - break; + case iFolder: + if(ps_global->cur_folder){ + strncpy(buf,ps_global->cur_folder, maxlen); + buf[maxlen] = '\0'; + } + break; + + case iCollection: + if(ps_global->context_current->nickname){ + strncpy(buf,ps_global->context_current->nickname, maxlen); + buf[maxlen] = '\0'; + } + break; + + case iFlag: + {MAILSTREAM *stream = ps_global->mail_stream; + MSGNO_S *msgmap = NULL; + long msgno; + MESSAGECACHE *mc; + strncpy(buf, "_FLAG_", maxlen); /* default value */ + if (stream){ + msgmap = sp_msgmap(stream); + msgno = mn_m2raw(msgmap, rules_cursor_pos(stream)); + if (msgno > 0L) mc = stream ? mail_elt(stream, msgno) : NULL; + if (mc) + sprintf(buf,"%s%s%s%s",mc->flagged ? "*" : "", + mc->recent ? (mc->seen ? "R" : "N") : (mc->seen) ? "R" : "U", + mc->answered ? "A" : "", + mc->deleted ? "D" : "" ); + } + buf[maxlen] = '\0'; + } + break; + + case iNick: + { + ADDRESS *tmp_adr = NULL; + if (env){ + tmp_adr = env->from ? copyaddr(env->from) + : env->sender ? copyaddr(env->sender) : NULL; + get_nickname_from_addr(tmp_adr,buf,maxlen); + mail_free_address(&tmp_adr); + } + } + break; + + case iAddressCc: + case iAddressRecip: + case iAddressTo: + case iFadd: + { + int plen = 0; /* partial length */ + ADDRESS *sparep2 = (type == iAddressTo || type == iAddressRecip) + ? ((env && env->to) + ? copyaddrlist(env->to) + : NULL) + : (type == iAddressCc) + ? ((env && env->cc) + ? copyaddrlist(env->cc) + : NULL) + : ((env && env->sparep) + ? copyaddr((ADDRESS *)env->sparep) + : NULL); + ADDRESS *sparep; + + if (type == iAddressRecip){ + ADDRESS *last_to = NULL; + + for(last_to = sparep2;last_to && last_to->next; last_to= last_to->next); + + /* Make the end of To list point to cc list */ + if(last_to) + last_to->next = (env && env->cc ? copyaddrlist(env->cc) : NULL); + + } + sparep = sparep2; + for(; sparep ; sparep = sparep->next) + if(sparep && sparep->mailbox && sparep->mailbox[0] && + (plen ? plen + 1 : plen) + strlen(sparep->mailbox) <= maxlen){ + if (plen == 0) + strcpy(buf, sparep->mailbox); + else{ + strcat(buf, " "); + strcat(buf, sparep->mailbox); + } + if(sparep->host && + sparep->host[0] && + sparep->host[0] != '.' && + strlen(buf) + strlen(sparep->host) + 1 <= maxlen){ + strcat(buf, "@"); + strcat(buf, sparep->host); + } + plen = strlen(buf); + } + mail_free_address(&sparep2); + } + + break; case iNewLine: if(maxlen >= strlen(NEWLINE)){ @@ -1762,6 +1935,11 @@ get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size break; + case iLcc: /* fake it, there are not enough spare pointers */ + if (env && env->date) + sprintf(buf,"%s",env->date); + break; + case iNews: case iCurNews: get_news_data(env, type, buf, maxlen); @@ -1811,6 +1989,14 @@ get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size break; + case iOpeningText: + case iOpeningTextNQ: + if(env && env->sparep){ + strncpy(buf, ((SPAREP_S *)env->sparep)->value, maxlen); + buf[maxlen] = '\0'; + } + break; + case iSubject: if(env && env->subject){ size_t n, len; @@ -1869,7 +2055,18 @@ reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc) if(!env) return; - strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM); + { RULE_RESULT *rule; + rule = get_result_rule(V_REPLY_LEADIN_RULES, FOR_REPLY_INTRO, env); + if(rule){ + strncpy(buf, rule->result, MAX_DELIM); + if (rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + else + strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM); + } + buf[MAX_DELIM] = '\0'; /* preserve exact default behavior from before */ if(!strcmp(buf, DEFAULT_REPLY_INTRO)){ @@ -2128,6 +2325,7 @@ forward_subject(ENVELOPE *env, int flags) { size_t l; char *p, buftmp[MAILTMPLEN]; + RULE_RESULT *rule; if(!env) return(NULL); @@ -2135,9 +2333,20 @@ forward_subject(ENVELOPE *env, int flags) dprint((9, "checking subject: \"%s\"\n", env->subject ? env->subject : "NULL")); - if(env->subject && env->subject[0]){ /* add (fwd)? */ - snprintf(buftmp, sizeof(buftmp), "%s", env->subject); - buftmp[sizeof(buftmp)-1] = '\0'; + buftmp[0] = '\0'; + ps_global->procid = cpystr("fwd-subject"); + if (rule = get_result_rule(V_FORWARD_RULES,FOR_COMPOSE, env)){ + sprintf(buftmp, "%.200s", rule->result); + if(rule->result) + fs_give((void **)&rule->result); + fs_give((void **)&rule); + } + else if(env->subject) + sprintf(buftmp, "%.200s", env->subject); + buftmp[sizeof(buftmp)-1] = '\0'; + fs_give((void **)&ps_global->procid); + + if(buftmp[0]){ /* add (fwd)? */ /* decode any 8bit (copy to the temp buffer if decoding doesn't) */ if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp) @@ -2638,9 +2847,12 @@ get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body, * tied our hands, alter the prefix to continue flowed * formatting... */ - if(flow_res) + if(flow_res && !ps_global->reply.no_send_flowed) wrapflags |= GFW_FLOW_RESULT; + filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(select_quote, NULL); + filters[filtcnt].filter = gf_wrap; /* * The 80 will cause longer lines than what is likely @@ -2674,9 +2886,9 @@ get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body, * We also want to fold "> " quotes so we get the * attributions correct. */ - if(flow_res && prefix && !strucmp(prefix, "> ")) + if(flow_res && !ps_global->reply.no_send_flowed && prefix && !strucmp(prefix, "> ")) *(prefix_p = prefix + 1) = '\0'; - + ps_global->reply.no_send_flowed = 0; /* reset for next call */ if(!(wrapflags & GFW_FLOWED) && flow_res){ filters[filtcnt].filter = gf_line_test; @@ -2709,9 +2921,7 @@ get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body, } if(prefix){ - if(ps_global->full_header != 2 - && (F_ON(F_ENABLE_SIGDASHES, ps_global) - || F_ON(F_ENABLE_STRIP_SIGDASHES, ps_global))){ + if(ps_global->reply.strip){ dashdata = 0; filters[filtcnt].filter = gf_line_test; filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata); @@ -2736,7 +2946,7 @@ get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body, dq.do_color = 0; dq.delete_all = 1; - filters[filtcnt].filter = gf_line_test; + filters[filtcnt].filter = gf_quote_test; filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); } diff --git a/pith/save.c b/pith/save.c index 957e163b..22cfa4a6 100644 --- a/pith/save.c +++ b/pith/save.c @@ -954,7 +954,7 @@ save(struct pine *state, MAILSTREAM *stream, CONTEXT_S *context, char *folder, *date = '\0'; rv = save_fetch_append(stream, mn_m2raw(msgmap, i), - NULL, save_stream, save_folder, context, + NULL, save_stream, folder, context, mc ? mc->rfc822_size : 0L, flags, date, so); if(flags) @@ -1157,6 +1157,7 @@ long save_fetch_append_cb(MAILSTREAM *stream, void *data, char **flags, snprintf(buf, sizeof(buf), "Message to save shrank: source msg # %ld may be saved incorrectly", mn_raw2m(pkg->msgmap, raw)); + if(F_OFF(F_IGNORE_SIZE, ps_global)) q_status_message(SM_ORDER, 0, 3, buf); } else{ diff --git a/pith/send.c b/pith/send.c index a0c60439..3cca3365 100644 --- a/pith/send.c +++ b/pith/send.c @@ -44,6 +44,7 @@ static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington #include "../pith/ablookup.h" #include "../pith/sort.h" #include "../pith/smime.h" +#include "../pith/rules.h" #include "../c-client/smtp.h" #include "../c-client/nntp.h" @@ -53,7 +54,7 @@ static char rcsid[] = "$Id: send.c 1204 2009-02-02 19:54:23Z hubert@u.washington /* name::type::canedit::writehdr::localcopy::rcptto */ PINEFIELD pf_template[] = { {"X-Auth-Received", FreeText, 0, 1, 1, 0}, /* N_AUTHRCVD */ - {"From", Address, 0, 1, 1, 0}, + {"From", Address, 1, 1, 1, 0}, {"Reply-To", Address, 0, 1, 1, 0}, {TONAME, Address, 1, 1, 1, 1}, {CCNAME, Address, 1, 1, 1, 1}, @@ -257,6 +258,13 @@ postponed_stream(MAILSTREAM **streamp, char *mbox, char *type, int checknmsgs) if(exists & FEX_ISFILE){ context_apply(tmp, p_cntxt, mbox, sizeof(tmp)); +#ifndef _WINDOWS + if (!struncmp(tmp, "#md/",4) || !struncmp(tmp, "#mc/", 4)){ + char tmp2[MAILTMPLEN]; + maildir_file_path(tmp, tmp2, sizeof(tmp2)); + strcpy(tmp, tmp2); + } +#endif if(!(IS_REMOTE(tmp) || is_absolute_path(tmp))){ /* * The mbox is relative to the home directory. @@ -1229,7 +1237,7 @@ pine_new_env(ENVELOPE *outgoing, char **fccp, char ***tobufpp, PINEFIELD *custom *p = *(p+4); pf->type = pf_template[i].type; - pf->canedit = pf_template[i].canedit; + pf->canedit = (i == N_FROM) ? CAN_EDIT(ps_global) : pf_template[i].canedit; pf->rcptto = pf_template[i].rcptto; pf->writehdr = pf_template[i].writehdr; pf->localcopy = pf_template[i].localcopy; @@ -1738,9 +1746,9 @@ call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_serve char error_buf[200], *error_mess = NULL, *postcmd; ADDRESS *a; ENVELOPE *fake_env = NULL; - int addr_error_count, we_cancel = 0; + int addr_error_count, we_cancel = 0, choice, num_rules = 0, added_rules = -1; long smtp_opts = 0L; - char *verbose_file = NULL; + char *verbose_file = NULL, **smtp_list; BODY *bp = NULL; PINEFIELD *pf; BODY *origBody = body; @@ -1893,20 +1901,49 @@ call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_serve * OK, who posts what? We tried an mta_handoff above, but there * was either none specified or we decided not to use it. So, * if there's an smtp-server defined anywhere, + * First we check for rules and make a list using the rules. */ - if(alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0]){ - /*---------- SMTP ----------*/ - dprint((4, "call_mailer: via TCP (%s)\n", - alt_smtp_servers[0])); - TIME_STAMP("smtp-open start (tcp)", 1); - sending_stream = smtp_open(alt_smtp_servers, smtp_opts); + if(ps_global->VAR_SMTP_RULES && ps_global->VAR_SMTP_RULES[0] + && ps_global->VAR_SMTP_RULES[0][0]) + while (ps_global->VAR_SMTP_RULES[num_rules]) num_rules++; + + if(num_rules){ + int i, j; + + added_rules = 0; + smtp_list = (char **) fs_get ((num_rules + 1)*sizeof(char*)); + for (i = 0, j = 0; i < num_rules; i++){ + RULELIST *rule = get_rulelist_from_code(V_SMTP_RULES, + ps_global->rule_list); + RULE_S *prule = get_rule(rule, i); + if(prule){ + char *rule_result = process_rule(prule, FOR_COMPOSE, header->env); + if(rule_result && *rule_result){ + smtp_list[j++] = cpystr(rule_result); + added_rules++; + } + } + } + } + + if (added_rules < 0){ + smtp_list = (char **) fs_get (sizeof(char*)); + added_rules = 0; } - else if(ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0] - && ps_global->VAR_SMTP_SERVER[0][0]){ - /*---------- SMTP ----------*/ - dprint((4, "call_mailer: via TCP\n")); - TIME_STAMP("smtp-open start (tcp)", 1); - sending_stream = smtp_open(ps_global->VAR_SMTP_SERVER, smtp_opts); + smtp_list[added_rules] = NULL; + + choice = smtp_list && smtp_list[0] && smtp_list[0][0] ? 3 : + (alt_smtp_servers && alt_smtp_servers[0] && alt_smtp_servers[0][0] ? 2 : + (ps_global->VAR_SMTP_SERVER && ps_global->VAR_SMTP_SERVER[0] + && ps_global->VAR_SMTP_SERVER[0][0] ? 1 : -1)); + + if(choice > 0){ + /*---------- SMTP ----------*/ + dprint((4, "call_mailer: via TCP (%s)\n",smtp_list[0])); + TIME_STAMP("smtp-open start (tcp)", 1); + sending_stream = smtp_open(choice == 3 ? smtp_list + : (choice == 2 ? alt_smtp_servers + : ps_global->VAR_SMTP_SERVER), smtp_opts); } else if((postcmd = smtp_command(ps_global->c_client_error, sizeof(ps_global->c_client_error))) != NULL){ char *cmdlist[2]; @@ -2142,6 +2179,8 @@ call_mailer(METAENV *header, struct mail_bodystruct *body, char **alt_smtp_serve if(error_mess){ q_status_message(SM_ORDER | SM_DING, 4, 7, error_mess); dprint((1, "call_mailer ERROR: %s\n", error_mess)); + if (ps_global->send_immediately) + printf("%s\n",error_mess); } return(-1); diff --git a/pith/send.h b/pith/send.h index 69d763fc..ee3245e5 100644 --- a/pith/send.h +++ b/pith/send.h @@ -159,6 +159,8 @@ struct local_message_copy { unsigned text_written:1; }; +#define CAN_EDIT(x) (!((x)->never_allow_changing_from) && \ + F_ON(F_ALLOW_CHANGING_FROM, (x))) #define TONAME "To" #define CCNAME "cc" diff --git a/pith/sort.c b/pith/sort.c index 68a9c10c..4d75012f 100644 --- a/pith/sort.c +++ b/pith/sort.c @@ -30,7 +30,7 @@ static char rcsid[] = "$Id: sort.c 1142 2008-08-13 17:22:21Z hubert@u.washington #include "../pith/signal.h" #include "../pith/busy.h" #include "../pith/icache.h" - +#include "../pith/rules.h" /* * global place to store mail_sort and mail_thread results @@ -91,7 +91,7 @@ Args: msgmap -- ----*/ void sort_folder(MAILSTREAM *stream, MSGNO_S *msgmap, SortOrder new_sort, - int new_rev, unsigned int flags) + int new_rev, unsigned int flags, int first) { long raw_current, i, j; unsigned long *sort = NULL; @@ -101,6 +101,15 @@ sort_folder(MAILSTREAM *stream, MSGNO_S *msgmap, SortOrder new_sort, int current_rev; MESSAGECACHE *mc; + if (first){ + if (new_sort == SortThread) + find_msgmap(stream, msgmap, flags, + ps_global->thread_cur_sort, new_rev); + else + sort_folder(stream, msgmap, new_sort, new_rev, flags, 0); + return; + } + dprint((2, "Sorting by %s%s\n", sort_name(new_sort), new_rev ? "/reverse" : "")); @@ -530,20 +539,20 @@ percent_sorted(void) * argument also means arrival/reverse. */ int -decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev) +decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev, int thread) { char *sep; char *fix_this = NULL; - int x, reverse; + int x = 0, reverse; if(!sort_spec || !*sort_spec){ - *def_sort = SortArrival; + *def_sort = thread ? SortThread : SortArrival; *def_sort_rev = 0; return(0); } if(struncmp(sort_spec, "reverse", strlen(sort_spec)) == 0){ - *def_sort = SortArrival; + *def_sort = thread ? SortThread : SortArrival; *def_sort_rev = 1; return(0); } @@ -572,7 +581,7 @@ decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev) if(ps_global->sort_types[x] == EndofList) return(-1); - *def_sort = ps_global->sort_types[x]; + *def_sort = ps_global->sort_types[x]; *def_sort_rev = reverse; return(0); } @@ -686,10 +695,26 @@ reset_sort_order(unsigned int flags) PAT_S *pat; SortOrder the_sort_order; int sort_is_rev; - + char *rule_result; + SortOrder new_sort = EndofList; + int is_rev; + + rule_result = get_rule_result(FOR_SORT, ps_global->cur_folder, V_SORT_RULES); + if (rule_result && *rule_result){ + new_sort = (SortOrder) translate(rule_result, 1); + is_rev = (SortOrder) translate(rule_result, 0) == EndofList ? 0 : 1; + fs_give((void **)&rule_result); + } + if (new_sort != EndofList){ + the_sort_order = new_sort; + sort_is_rev = is_rev; + } + else{ /* set default order */ the_sort_order = ps_global->def_sort; - sort_is_rev = ps_global->def_sort_rev; + sort_is_rev = the_sort_order == SortThread + ? (ps_global->thread_def_sort_rev + ps_global->def_sort_rev) % 2 + : ps_global->def_sort_rev; if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){ @@ -702,9 +727,52 @@ reset_sort_order(unsigned int flags) && pat->action->sort_is_set){ the_sort_order = pat->action->sortorder; sort_is_rev = pat->action->revsort; + sort_is_rev = the_sort_order == SortThread + ? (ps_global->thread_def_sort_rev + pat->action->revsort) % 2 + : pat->action->revsort; } } + } + if(the_sort_order == SortThread && !(flags & SRT_MAN)) + ps_global->thread_cur_sort = ps_global->thread_def_sort; sort_folder(ps_global->mail_stream, ps_global->msgmap, - the_sort_order, sort_is_rev, flags); + the_sort_order, sort_is_rev, flags, 1); } + +SortOrder translate(char *order, int is_rev) +{ + int rev = 0; + if (!strncmp(order,"tHread", 6) + || (rev = !strncmp(order,"Reverse tHread", 14))) + return is_rev || rev ? SortThread : EndofList; + if (!strncmp(order,"OrderedSubj", 11) + || (rev = !strncmp(order,"Reverse OrderedSubj", 19))) + return is_rev || rev ? SortSubject2 : EndofList; + if (!strncmp(order,"Subject", 7) + || (rev = !strncmp(order,"Reverse SortSubject", 15))) + return is_rev || rev ? SortSubject : EndofList; + if (!strncmp(order,"Arrival", 7) + || (rev = !strncmp(order,"Reverse Arrival", 15))) + return is_rev || rev ? SortArrival : EndofList; + if (!strncmp(order,"From", 4) + || (rev = !strncmp(order,"Reverse From", 12))) + return is_rev || rev ? SortFrom : EndofList; + if (!strncmp(order,"To", 2) + || (rev = !strncmp(order,"Reverse To", 10))) + return is_rev || rev ? SortTo : EndofList; + if (!strncmp(order,"Cc", 2) + || (rev = !strncmp(order,"Reverse Cc", 10))) + return is_rev || rev ? SortCc : EndofList; + if (!strncmp(order,"Date", 4) + || (rev = !strncmp(order,"Reverse Date", 12))) + return is_rev || rev ? SortDate : EndofList; + if (!strncmp(order,"siZe", 4) + || (rev = !strncmp(order,"Reverse siZe", 12))) + return is_rev || rev ? SortSize : EndofList; + if (!strncmp(order,"scorE", 5) + || (rev = !strncmp(order,"Reverse scorE", 13))) + return is_rev || rev ? SortScore : EndofList; + return EndofList; +} + diff --git a/pith/sort.h b/pith/sort.h index ce383a04..2dfcde71 100644 --- a/pith/sort.h +++ b/pith/sort.h @@ -22,7 +22,7 @@ #define refresh_sort(S,M,F) sort_folder((S), (M), mn_get_sort(M), \ - mn_get_revsort(M), (F)) + mn_get_revsort(M), (F), 1) struct global_sort_data { MSGNO_S *msgmap; @@ -41,9 +41,9 @@ extern struct global_sort_data g_sort; /* exported protoypes */ char *sort_name(SortOrder); -void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned); -int decode_sort(char *, SortOrder *, int *); +void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned, int); +int decode_sort(char *, SortOrder *, int *, int); void reset_sort_order(unsigned); - +SortOrder translate(char *, int); #endif /* PITH_SORT_INCLUDED */ diff --git a/pith/state.c b/pith/state.c index fd2b4f71..6d1d8455 100644 --- a/pith/state.c +++ b/pith/state.c @@ -33,7 +33,7 @@ static char rcsid[] = "$Id: state.c 1074 2008-06-04 00:08:43Z hubert@u.washingto #include "../pith/remote.h" #include "../pith/list.h" #include "../pith/smime.h" - +#include "../pith/rules.h" /* * Globals referenced throughout pine... @@ -74,6 +74,7 @@ new_pine_struct(void) p = (struct pine *)fs_get(sizeof (struct pine)); memset((void *) p, 0, sizeof(struct pine)); + p->thread_def_sort = SortDate; p->def_sort = SortArrival; p->sort_types[0] = SortSubject; p->sort_types[1] = SortArrival; @@ -116,6 +117,9 @@ free_pine_struct(struct pine **pps) if(!(pps && (*pps))) return; + if((*pps)->subject != NULL) + fs_give((void **)&(*pps)->subject); + if((*pps)->hostname != NULL) fs_give((void **)&(*pps)->hostname); @@ -131,6 +135,9 @@ free_pine_struct(struct pine **pps) if((*pps)->folders_dir != NULL) fs_give((void **)&(*pps)->folders_dir); + if((*pps)->paterror == 0) + regfree(&(*pps)->colorpat); + if((*pps)->ui.homedir) fs_give((void **)&(*pps)->ui.homedir); @@ -192,6 +199,8 @@ free_pine_struct(struct pine **pps) if((*pps)->kw_colors) free_spec_colors(&(*pps)->kw_colors); + free_allowed_qstr(); + if((*pps)->atmts){ int i; @@ -206,6 +215,9 @@ free_pine_struct(struct pine **pps) if((*pps)->msgmap) msgno_give(&(*pps)->msgmap); + if((*pps)->rule_list) + free_parsed_rule_list(&(*pps)->rule_list); + free_vars(*pps); fs_give((void **) pps); diff --git a/pith/state.h b/pith/state.h index 47e97ce4..6dbdd025 100644 --- a/pith/state.h +++ b/pith/state.h @@ -33,7 +33,7 @@ #include "../pith/stream.h" #include "../pith/color.h" #include "../pith/user.h" - +#include "../pith/rulestype.h" /* * Printing control structure @@ -105,6 +105,11 @@ struct pine { MAILSTREAM *mail_stream; /* ptr to current folder stream */ MSGNO_S *msgmap; /* ptr to current message map */ + char screen_name[10]; /* name of current screen */ + char *role; /* role used when composing */ + char *procid; /* procedure id when needed */ + int exiting; + unsigned read_predicted:1; char cur_folder[MAXPATH+1]; @@ -137,6 +142,8 @@ struct pine { unsigned unseen_in_view:1; unsigned start_in_context:1; /* start fldr_scrn in current cntxt */ unsigned def_sort_rev:1; /* true if reverse sort is default */ + unsigned thread_def_sort_rev:1; /* true if reverse sort is default in thread screen */ + unsigned msgmap_thread_def_sort_rev:1; /* true if reverse sort is being used in thread screen */ unsigned restricted:1; unsigned save_msg_rule:5; @@ -244,10 +251,23 @@ struct pine { SPEC_COLOR_S *hdr_colors; /* list of configed colors for view */ SPEC_COLOR_S *index_token_colors; /* list of configed colors for index */ + char *prefix; /* prefix for fillpara */ + char **list_qstr; /* list of known quote strings */ short init_context; + struct { + ACTION_S *role_chosen; + int attach; + int strip; + int no_send_flowed; + int inchdr; + } reply; + int *initial_cmds; /* cmds to execute on startup */ int *free_initial_cmds; /* used to free when done */ + int *initial_cmds_backup; /* keep a copy in case they are freed */ + int *free_initial_cmds_backup; /* free the copy */ + int initial_cmds_offset; /* how many commands we have executed */ char c_client_error[300]; /* when nowhow_error is set and PARSE */ @@ -285,6 +305,9 @@ struct pine { EditWhich ew_for_srch_take; SortOrder def_sort, /* Default sort type */ + thread_def_sort, /* Default Sort Type in Thread Screen */ + thread_cur_sort, /* current sort style for threads */ + msgmap_thread_sort, sort_types[22]; int preserve; @@ -301,10 +324,16 @@ struct pine { int nmw_width; + char *subject; + int send_immediately; + int failed_read; + int hours_to_timeout; int tcp_query_timeout; + int sleep; /* time in seconds to sleep before removing temp file */ + int inc_check_timeout; int inc_check_interval; /* for local and IMAP */ int inc_second_check_interval; /* for other */ @@ -323,6 +352,8 @@ struct pine { char *display_charmap; /* needs to be freed */ char *keyboard_charmap; /* needs to be freed */ void *input_cs; + regex_t colorpat; + int paterror; char *posting_charmap; /* needs to be freed */ @@ -334,6 +365,7 @@ struct pine { struct { char *(*display_filter)(char *, STORE_S *, gf_io_t, FILTLIST_S *); char *(*display_filter_trigger)(BODY *, char *, size_t); + char *(*exec_rule)(char *, gf_io_t, gf_io_t); } tools; KEYWORD_S *keywords; @@ -344,6 +376,9 @@ struct pine { char last_error[500]; INIT_ERR_S *init_errs; + PRULELIST_S *rule_list; + char *pressed_key; + PRINT_S *print; #ifdef SMIME diff --git a/pith/store.c b/pith/store.c index e8508257..58154c53 100644 --- a/pith/store.c +++ b/pith/store.c @@ -171,6 +171,14 @@ so_get(SourceType source, char *name, int rtype) if(source == TmpFileStar) our_unlink(so->name); + if (ps_global->send_immediately){ + ps_global->failed_read++; + if(ps_global->failed_read == 5){ + printf("No configurationf file found. Where is your .pinerc file?\n"); + exit(1); + } + } + fs_give((void **)&so->name); fs_give((void **)&so); /* so freed & set to NULL */ } diff --git a/pith/string.c b/pith/string.c index 84717c3e..2cd43d7d 100644 --- a/pith/string.c +++ b/pith/string.c @@ -20,6 +20,7 @@ static char rcsid[] = "$Id: string.c 910 2008-01-14 22:28:38Z hubert@u.washingto string.c Misc extra and useful string functions - rplstr replace a substring with another string + - collspaces consecutive spaces are reduced to one space. - sqzspaces Squeeze out the extra blanks in a string - sqznewlines Squeeze out \n and \r. - removing_trailing_white_space @@ -132,6 +133,31 @@ rplstr(char *os, size_t oslen, int dl, char *is) return(x3); } +/*---------------------------------------------------------------------- + collapse blank space + ----------------------------------------------------------------------*/ +void +collspaces(char *string) +{ + char *p = string; + int only_one_space = 0; + + if(!string) + return; + + for(;isspace(*p); p++); + + while(*string = *p++) + if(!isspace((unsigned char)*string)){ + only_one_space = 0; + string++; + } + else if(!only_one_space){ + string++; + only_one_space++; + } + *string = '\0'; +} /*---------------------------------------------------------------------- @@ -2860,3 +2886,35 @@ free_strlist(STRLIST_S **strp) fs_give((void **) strp); } } + + +void +removing_extra_stuff(string) + char *string; +{ + char *p = NULL; + int change = 0, length = 0; + + + if(!string) + return; + + for(; *string; string++, length++) + p = ((unsigned char)*string != ',') ? NULL : (!p) ? string : p; + + if(p) + *p = '\0'; + + string -= length; + for (; *string; string++){ + if (change){ + *string = ' '; + change = 0; + } + if ((((unsigned char)*string == ' ') || + ((unsigned char)*string == ',')) && + ((unsigned char)*(string + 1) == ',')) + change++; + } +} + diff --git a/pith/string.h b/pith/string.h index 11c4d45f..e6ee6497 100644 --- a/pith/string.h +++ b/pith/string.h @@ -86,12 +86,14 @@ struct date { /* exported protoypes */ char *rplstr(char *, size_t, int, char *); +void collspaces(char *); void sqzspaces(char *); void sqznewlines(char *); void removing_leading_white_space(char *); void removing_trailing_white_space(char *); void removing_leading_and_trailing_white_space(char *); int removing_double_quotes(char *); +void removing_extra_stuff (char *); char *skip_white_space(char *); char *skip_to_white_space(char *); char *removing_quotes(char *); diff --git a/pith/text.c b/pith/text.c index 5de53e51..2857ddad 100644 --- a/pith/text.c +++ b/pith/text.c @@ -92,7 +92,7 @@ decode_text(ATTACH_S *att, char *err, *charset; int filtcnt = 0, error_found = 0, column, wrapit; int is_in_sig = OUT_SIG_BLOCK; - int is_flowed_msg = 0; + int is_flowed_msg = 0, add_me = 1, doraw = RAWSTRING; int is_delsp_yes = 0; int filt_only_c0 = 0; char *parmval; @@ -171,6 +171,15 @@ decode_text(ATTACH_S *att, gf_url_hilite_opt(&uh,handlesp,0)); } + if((flags & FM_DISPLAY) + && !(flags & FM_NOCOLOR) + && pico_usingcolor() + && VAR_SPECIAL_TEXT_FORE_COLOR + && VAR_SPECIAL_TEXT_BACK_COLOR){ + filters[filtcnt].filter = gf_line_test; + filters[filtcnt++].data = gf_line_test_opt(color_this_text, NULL); + } + /* * First, paint the signature. * Disclaimers noted below for coloring quotes apply here as well. @@ -180,7 +189,7 @@ decode_text(ATTACH_S *att, && pico_usingcolor() && VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR){ - filters[filtcnt].filter = gf_line_test; + filters[filtcnt].filter = gf_quote_test; filters[filtcnt++].data = gf_line_test_opt(color_signature, &is_in_sig); } @@ -198,9 +207,9 @@ decode_text(ATTACH_S *att, && pico_usingcolor() && VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR){ - filters[filtcnt].filter = gf_line_test; - filters[filtcnt++].data = gf_line_test_opt(color_a_quote, - &is_flowed_msg); + add_me = 0; + filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(color_a_quote, &is_flowed_msg); } } else if(!strucmp(att->body->subtype, "richtext")){ @@ -281,6 +290,11 @@ decode_text(ATTACH_S *att, } } + if (add_me){ + filters[filtcnt].filter = gf_quote_test; + filters[filtcnt++].data = gf_line_test_opt(select_quote, &doraw); + } + /* * If the message is not flowed, we do the quote suppression before * the wrapping, because the wrapping does not preserve the quote @@ -305,7 +319,7 @@ decode_text(ATTACH_S *att, dq.handlesp = handlesp; dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor()); - filters[filtcnt].filter = gf_line_test; + filters[filtcnt].filter = gf_quote_test; filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); } if(ps_global->VAR_QUOTE_REPLACE_STRING @@ -364,7 +378,7 @@ decode_text(ATTACH_S *att, dq.handlesp = handlesp; dq.do_color = (!(flags & FM_NOCOLOR) && pico_usingcolor()); - filters[filtcnt].filter = gf_line_test; + filters[filtcnt].filter = gf_quote_test; filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); } @@ -569,7 +583,7 @@ delete_quotes(long int linenum, char *line, LT_INS_S **ins, void *local) { DELQ_S *dq; char *lp; - int i, lines, not_a_quote = 0; + int i, lines, not_a_quote = 0, code; size_t len; dq = (DELQ_S *) local; @@ -589,6 +603,8 @@ delete_quotes(long int linenum, char *line, LT_INS_S **ins, void *local) for(i = dq->indent_length; i > 0 && !not_a_quote && *lp; i--) if(*lp++ != SPACE) not_a_quote++; + while(isspace((unsigned char) *lp)) + lp++; /* skip over leading tags */ while(!not_a_quote @@ -628,13 +644,12 @@ delete_quotes(long int linenum, char *line, LT_INS_S **ins, void *local) } } - /* skip over whitespace */ - if(!dq->is_flowed) - while(isspace((unsigned char) *lp)) - lp++; - - /* check first character to see if it is a quote */ - if(!not_a_quote && *lp != '>') + len = lp - line; + if(strlen(tmp_20k_buf) > len) + strcpy(tmp_20k_buf, tmp_20k_buf+len); + code = (dq->is_flowed ? IS_FLOWED : NO_FLOWED) | DELETEQUO; + select_quote(linenum, lp, ins, &code); + if (!not_a_quote && !tmp_20k_buf[0]) not_a_quote++; if(not_a_quote){ diff --git a/pith/thread.c b/pith/thread.c index ff9bff54..fc0e32b3 100644 --- a/pith/thread.c +++ b/pith/thread.c @@ -30,12 +30,18 @@ static char rcsid[] = "$Id: thread.c 942 2008-03-04 18:21:33Z hubert@u.washingto #include "../pith/mailcmd.h" #include "../pith/ablookup.h" +static int erase_thread_info = 1; + +typedef struct sizethread_t { + int count; + long pos; +} SIZETHREAD_T; /* * Internal prototypes */ long *sort_thread_flatten(THREADNODE *, MAILSTREAM *, long *, - char *, long, PINETHRD_S *, unsigned); + char *, long, PINETHRD_S *, unsigned, int, long, long); void make_thrdflags_consistent(MAILSTREAM *, MSGNO_S *, PINETHRD_S *, int); THREADNODE *collapse_threadnode_tree(THREADNODE *); THREADNODE *collapse_threadnode_tree_sorted(THREADNODE *); @@ -43,6 +49,7 @@ THREADNODE *sort_threads_and_collapse(THREADNODE *); THREADNODE *insert_tree_in_place(THREADNODE *, THREADNODE *); unsigned long branch_greatest_num(THREADNODE *, int); long calculate_visible_threads(MAILSTREAM *); +int pine_compare_size_thread(const qsort_t *, const qsort_t *); PINETHRD_S * @@ -95,20 +102,22 @@ void set_flags_for_thread(MAILSTREAM *stream, MSGNO_S *msgmap, int f, PINETHRD_S *thrd, int v) { PINETHRD_S *nthrd, *bthrd; + unsigned long next = 0L, branch = 0L; if(!(stream && thrd && msgmap)) return; set_lflag(stream, msgmap, mn_raw2m(msgmap, thrd->rawno), f, v); - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next = get_next(stream,thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) set_flags_for_thread(stream, msgmap, f, nthrd, v); } - if(thrd->branch){ - bthrd = fetch_thread(stream, thrd->branch); + + if(branch = get_branch(stream, thrd)){ + bthrd = fetch_thread(stream, branch); if(bthrd) set_flags_for_thread(stream, msgmap, f, bthrd, v); } @@ -122,7 +131,7 @@ erase_threading_info(MAILSTREAM *stream, MSGNO_S *msgmap) MESSAGECACHE *mc; PINELT_S *peltp; - if(!(stream && stream->spare)) + if(!(stream && stream->spare) || !erase_thread_info) return; ps_global->view_skipped_index = 0; @@ -155,7 +164,7 @@ sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree) PINETHRD_S *thrd = NULL; unsigned long msgno, rawno; int un_view_thread = 0; - long raw_current; + long raw_current, branch; char *dup_chk = NULL; @@ -168,10 +177,11 @@ sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree) * way. If the dummy node is at the top-level, then its children are * promoted to the top-level as separate threads. */ - if(F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global)) - collapsed_tree = collapse_threadnode_tree_sorted(tree); - else - collapsed_tree = collapse_threadnode_tree(tree); + collapsed_tree = F_ON(F_ENHANCED_THREAD, ps_global) + ? copy_tree(tree) + : (F_ON(F_THREAD_SORTS_BY_ARRIVAL, ps_global) + ? collapse_threadnode_tree_sorted(tree) + : collapse_threadnode_tree(tree)); /* dup_chk is like sort with an origin of 1 */ dup_chk = (char *) fs_get((mn_get_nmsgs(g_sort.msgmap)+1) * sizeof(char)); @@ -182,7 +192,7 @@ sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree) (void) sort_thread_flatten(collapsed_tree, stream, &g_sort.msgmap->sort[1], dup_chk, mn_get_nmsgs(g_sort.msgmap), - NULL, THD_TOP); + NULL, THD_TOP, 0, 1L, 0L); /* reset the inverse array */ msgno_reset_isort(g_sort.msgmap); @@ -340,12 +350,14 @@ sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree) else{ thrd = fetch_head_thread(stream); while(thrd){ + unsigned long raw = thrd->rawno; + unsigned long top = top_thread(stream, raw); /* * The top-level threads aren't hidden by collapse. */ msgno = mn_raw2m(g_sort.msgmap, thrd->rawno); - if(msgno) - set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0); + if(msgno && !get_lflag(stream, NULL,thrd->rawno, MN_COLL)) + set_lflag(stream, g_sort.msgmap, msgno, MN_CHID, 0); if(thrd->next){ PINETHRD_S *nthrd; @@ -359,9 +371,10 @@ sort_thread_callback(MAILSTREAM *stream, THREADNODE *tree) MN_COLL)); } - if(thrd->nextthd) - thrd = fetch_thread(stream, thrd->nextthd); - else + while (thrd && top_thread(stream, thrd->rawno) == top + && thrd->nextthd) + thrd = fetch_thread(stream, thrd->nextthd); + if (!(thrd && thrd->nextthd)) thrd = NULL; } } @@ -412,7 +425,7 @@ make_thrdflags_consistent(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *thrd, int a_parent_is_collapsed) { PINETHRD_S *nthrd, *bthrd; - unsigned long msgno; + unsigned long msgno, next, branch; if(!thrd) return; @@ -430,8 +443,8 @@ make_thrdflags_consistent(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *thrd, set_lflag(stream, msgmap, msgno, MN_CHID, 0); } - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next = get_next(stream, thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) make_thrdflags_consistent(stream, msgmap, nthrd, a_parent_is_collapsed @@ -440,8 +453,8 @@ make_thrdflags_consistent(MAILSTREAM *stream, MSGNO_S *msgmap, PINETHRD_S *thrd, MN_COLL)); } - if(thrd->branch){ - bthrd = fetch_thread(stream, thrd->branch); + if(branch = get_branch(stream, thrd)){ + bthrd = fetch_thread(stream, branch); if(bthrd) make_thrdflags_consistent(stream, msgmap, bthrd, a_parent_is_collapsed); @@ -488,9 +501,10 @@ calculate_visible_threads(MAILSTREAM *stream) long * sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream, long *entry, char *dup_chk, long maxno, - PINETHRD_S *thrd, unsigned int flags) + PINETHRD_S *thrd, unsigned int flags, + int adopted, long top, long threadno) { - PINETHRD_S *newthrd = NULL; + PINETHRD_S *newthrd = NULL, *save_thread = NULL; if(node){ if(node->num > 0L && node->num <= maxno){ /* holes happen */ @@ -498,6 +512,9 @@ sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream, *entry = node->num; dup_chk[node->num] = 1; + if(adopted == 2) + top = node->num; + /* * Build a richer threading structure that will help us paint * and operate on threads and subthreads. @@ -506,20 +523,51 @@ sort_thread_flatten(THREADNODE *node, MAILSTREAM *stream, if(newthrd){ entry++; + if(adopted == 2) + threadno = newthrd->thrdno; + if(adopted){ + newthrd->toploose = top; + newthrd->thrdno = threadno; + } + adopted = adopted ? 1 : 0; if(node->next) entry = sort_thread_flatten(node->next, stream, entry, dup_chk, maxno, - newthrd, THD_NEXT); + newthrd, THD_NEXT, adopted, top, threadno); if(node->branch) entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, maxno, newthrd, - (flags == THD_TOP) ? THD_TOP - : THD_BRANCH); + ((flags == THD_TOP) ? THD_TOP + : THD_BRANCH), + adopted, top, threadno); } } } + else{ + adopted = 2; + if(node->next) + entry = sort_thread_flatten(node->next, stream, entry, dup_chk, + maxno, thrd, THD_TOP, adopted, top, threadno); + adopted = 0; + if(node->branch){ + if(entry){ + long *last_entry = entry; + + do{ + last_entry--; + save_thread = ((PINELT_S *)mail_elt(stream, *last_entry)->sparep)->pthrd; + } while (save_thread->parent != 0L); + entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, + maxno, save_thread, (flags == THD_TOP ? THD_TOP : THD_BRANCH), + adopted, top, threadno); + } + else + entry = sort_thread_flatten(node->branch, stream, entry, dup_chk, + maxno, NULL, THD_TOP, adopted, top, threadno); + } + } } return(entry); @@ -788,7 +836,7 @@ msgno_thread_info(MAILSTREAM *stream, long unsigned int rawno, */ void collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, - long unsigned int msgno) + long unsigned int msgno, int display) { int collapsed, adjust_current = 0; PINETHRD_S *thrd = NULL, *nthrd; @@ -841,7 +889,7 @@ collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, if(!thrd) return; - collapsed = get_lflag(stream, NULL, thrd->rawno, MN_COLL) && thrd->next; + collapsed = this_thread_is_kolapsed(ps_global, stream, msgmap, thrd->rawno); if(collapsed){ msgno = mn_raw2m(msgmap, thrd->rawno); @@ -859,13 +907,13 @@ collapse_or_expand(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, msgno = mn_raw2m(msgmap, thrd->rawno); if(msgno > 0L && msgno <= mn_get_total(msgmap)){ set_lflag(stream, msgmap, msgno, MN_COLL, 1); - if((nthrd = fetch_thread(stream, thrd->next)) != NULL) + if((thrd->next) && ((nthrd = fetch_thread(stream, thrd->next)) != NULL)) set_thread_subtree(stream, nthrd, msgmap, 1, MN_CHID); clear_index_cache_ent(stream, msgno, 0); } } - else + else if(display) q_status_message(SM_ORDER, 0, 1, _("No thread to collapse or expand on this line")); @@ -952,18 +1000,19 @@ count_flags_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, long int flags) unsigned long count = 0; PINETHRD_S *nthrd, *bthrd; MESSAGECACHE *mc; + unsigned long next = 0L, branch = 0L; if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) return count; - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next = get_next(stream, thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) count += count_flags_in_thread(stream, nthrd, flags); } - if(thrd->branch){ - bthrd = fetch_thread(stream, thrd->branch); + if(branch = get_branch(stream, thrd)){ + bthrd = fetch_thread(stream, branch); if(bthrd) count += count_flags_in_thread(stream, bthrd, flags); } @@ -1051,20 +1100,21 @@ int mark_msgs_in_thread(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap) { int count = 0; + long next, branch; PINETHRD_S *nthrd, *bthrd; MESSAGECACHE *mc; if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) return count; - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next = get_next(stream, thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) count += mark_msgs_in_thread(stream, nthrd, msgmap); } - if(thrd->branch){ - bthrd = fetch_thread(stream, thrd->branch); + if(branch = get_branch(stream, thrd)){ + bthrd = fetch_thread(stream, branch); if(bthrd) count += mark_msgs_in_thread(stream, bthrd, msgmap); } @@ -1098,7 +1148,7 @@ set_thread_lflags(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int fla /* flags to set or clear */ /* set or clear? */ { - unsigned long msgno; + unsigned long msgno, next, branch; PINETHRD_S *nthrd, *bthrd; if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) @@ -1122,14 +1172,14 @@ set_thread_lflags(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int fla if(msgno > 0L && flags == MN_CHID2 && v == 1) clear_index_cache_ent(stream, msgno, 0); - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next = get_next(stream, thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) set_thread_lflags(stream, nthrd, msgmap, flags, v); } - if(thrd->branch){ - bthrd = fetch_thread(stream, thrd->branch); + if(branch = get_branch(stream,thrd)){ + bthrd = fetch_thread(stream, branch); if(bthrd) set_thread_lflags(stream, bthrd, msgmap, flags, v); } @@ -1210,7 +1260,8 @@ status_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, IndexColType type /* * Symbol is * if some message in thread is important, * + if some message is to us, - * - if mark-for-cc and some message is cc to us, else blank. + * - if mark-for-cc and some message is cc to us, + * . if mark-for-group and some message is to us in a group, else blank. */ char to_us_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, int consider_flagged) @@ -1218,45 +1269,48 @@ to_us_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, int consider_flagg char to_us = ' '; char branch_to_us = ' '; PINETHRD_S *nthrd, *bthrd; + unsigned long next = 0L, branch = 0L; MESSAGECACHE *mc; if(!thrd || !stream || thrd->rawno < 1L || thrd->rawno > stream->nmsgs) return to_us; - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next = get_next(stream,thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) to_us = to_us_symbol_for_thread(stream, nthrd, consider_flagged); } if(((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+')) - && thrd->branch){ + && (branch = get_branch(stream, thrd))){ bthrd = fetch_thread(stream, thrd->branch); if(bthrd) branch_to_us = to_us_symbol_for_thread(stream, bthrd, consider_flagged); /* use branch to_us symbol if it has higher priority than what we have so far */ if(to_us == ' '){ - if(branch_to_us == '-' || branch_to_us == '+' || branch_to_us == '*') + if(branch_to_us == '-' || branch_to_us == '+' + || branch_to_us == '.' || branch_to_us == '*') to_us = branch_to_us; } else if(to_us == '-'){ - if(branch_to_us == '+' || branch_to_us == '*') + if(branch_to_us == '+' || branch_to_us == '.' || branch_to_us == '*') to_us = branch_to_us; } - else if(to_us == '+'){ + else if(to_us == '+' || to_us == '.'){ if(branch_to_us == '*') to_us = branch_to_us; } } - if((consider_flagged && to_us != '*') || (!consider_flagged && to_us != '+')){ + if((consider_flagged && to_us != '*') + || (!consider_flagged && to_us != '+' && to_us != '.')){ if(consider_flagged && thrd && thrd->rawno > 0L && stream && thrd->rawno <= stream->nmsgs && (mc = mail_elt(stream, thrd->rawno)) && FLAG_MATCH(F_FLAG, mc, stream)) to_us = '*'; - else if(to_us != '+' && !IS_NEWS(stream)){ + else if(to_us != '+' && to_us != '.' && !IS_NEWS(stream)){ INDEXDATA_S idata; MESSAGECACHE *mc; ADDRESS *addr; @@ -1280,7 +1334,7 @@ to_us_symbol_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, int consider_flagg break; } - if(to_us != '+' && resent_to_us(&idata)) + if(to_us != '+' && !idata.bogus && resent_to_us(&idata)) to_us = '+'; if(to_us == ' ' && F_ON(F_MARK_FOR_CC,ps_global)) @@ -1328,7 +1382,8 @@ set_thread_subtree(MAILSTREAM *stream, PINETHRD_S *thrd, MSGNO_S *msgmap, int v, set_lflag(stream, msgmap, msgno, flags, v); - if(thrd->next && (hiding || !get_lflag(stream,NULL,thrd->rawno,MN_COLL))){ + if(thrd->next + && (hiding || !get_lflag(stream,NULL,thrd->rawno,MN_COLL))){ nthrd = fetch_thread(stream, thrd->next); if(nthrd) set_thread_subtree(stream, nthrd, msgmap, v, flags); @@ -1368,8 +1423,8 @@ view_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int set_lfl if(rawno) thrd = fetch_thread(stream, rawno); - if(thrd && thrd->top && thrd->top != thrd->rawno) - thrd = fetch_thread(stream, thrd->top); + if(thrd && thrd->top && top_thread(stream,thrd->top) != thrd->rawno) + thrd = fetch_thread(stream, top_thread(stream,thrd->top)); if(!thrd) return 0; @@ -1433,7 +1488,7 @@ unview_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap) thrd = fetch_thread(stream, rawno); if(thrd && thrd->top) - topthrd = fetch_thread(stream, thrd->top); + topthrd = fetch_thread(stream, top_thread(stream,thrd->top)); if(!topthrd) return 0; @@ -1539,6 +1594,7 @@ void set_search_bit_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, SEARCHSET **msgset) { PINETHRD_S *nthrd, *bthrd; + unsigned long next, branch; if(!(stream && thrd)) return; @@ -1547,15 +1603,622 @@ set_search_bit_for_thread(MAILSTREAM *stream, PINETHRD_S *thrd, SEARCHSET **msgs && (!(msgset && *msgset) || in_searchset(*msgset, thrd->rawno))) mm_searched(stream, thrd->rawno); - if(thrd->next){ - nthrd = fetch_thread(stream, thrd->next); + if(next= get_next(stream, thrd)){ + nthrd = fetch_thread(stream, next); if(nthrd) set_search_bit_for_thread(stream, nthrd, msgset); } - if(thrd->branch){ - bthrd = fetch_thread(stream, thrd->branch); + if(branch = get_branch(stream, thrd)){ + bthrd = fetch_thread(stream, branch); if(bthrd) set_search_bit_for_thread(stream, bthrd, msgset); } } + +/* + * Make a copy of c-client's THREAD tree + */ +THREADNODE * +copy_tree(THREADNODE *tree) +{ + THREADNODE *newtree = NULL; + + if(tree){ + newtree = mail_newthreadnode(NULL); + newtree->num = tree->num; + if(tree->next) + newtree->next = copy_tree(tree->next); + + if(tree->branch) + newtree->branch = copy_tree(tree->branch); + } + return(newtree); +} + +long +top_thread(MAILSTREAM *stream, long rawmsgno) +{ + PINETHRD_S *thrd = NULL; + unsigned long rawno; + + if(!stream) + return -1L; + + if(rawmsgno) + thrd = fetch_thread(stream, rawmsgno); + + if(!thrd) + return -1L; + + return F_ON(F_ENHANCED_THREAD, ps_global) + ? (thrd->toploose ? thrd->toploose : thrd->top) + : thrd->top; +} + +void +move_top_thread(MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) +{ + mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream, rawmsgno))); +} + +long +top_this_thread(MAILSTREAM *stream, long rawmsgno) +{ + PINETHRD_S *thrd = NULL; + unsigned long rawno; + + if(!stream) + return -1L; + + if(rawmsgno) + thrd = fetch_thread(stream, rawmsgno); + + if(!thrd) + return -1L; + + return thrd->top; +} + +void +move_top_this_thread(MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) +{ + mn_set_cur(msgmap,mn_raw2m(msgmap, top_this_thread(stream, rawmsgno))); +} + +int +thread_is_kolapsed(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) +{ + int collapsed; + PINETHRD_S *thrd = NULL; + unsigned long rawno, orig, orig_rawno; + + if(!stream) + return -1; + + orig = mn_get_cur(msgmap); + move_top_thread(stream, msgmap, rawmsgno); + rawno = orig_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return -1; + + while(collapsed = this_thread_is_kolapsed(state, stream, msgmap, rawno)) + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) + || !(rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_rawno != top_thread(stream, rawno))) + break; + + mn_set_cur(msgmap,orig); /* return home */ + + return collapsed; +} + +/* this function tells us if the thread (or branch in the case of loose threads) + * is collapsed + */ + +int +this_thread_is_kolapsed(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawmsgno) +{ + int collapsed; + PINETHRD_S *thrd = NULL; + unsigned long rawno, orig; + + if(!stream) + return -1; + + rawno = rawmsgno; + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return -1; + + collapsed = get_lflag(stream, NULL, rawno, MN_COLL | MN_CHID); + + if (!thrd->next){ + if (thrd->rawno != top_thread(stream, thrd->rawno)) + collapsed = get_lflag(stream, NULL, rawno, MN_CHID); + else + collapsed = get_lflag(stream, NULL, rawno, MN_COLL); + } + + return collapsed; +} + +/* + * This function assumes that it is called at a top of a thread in its + * first call + */ + +int +count_this_thread(MAILSTREAM *stream, unsigned long rawno) +{ + unsigned long top, orig_top, topnxt; + PINETHRD_S *thrd = NULL; + int count = 1; + + if(!stream) + return 0; + + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return 0; + + if (thrd->next) + count += count_this_thread(stream, thrd->next); + + if (thrd->branch) + count += count_this_thread(stream, thrd->branch); + + return count; +} + +int +count_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, long rawno) +{ + unsigned long top, orig, orig_top; + PINETHRD_S *thrd = NULL; + int done = 0, count = 0; + + if(!stream) + return 0; + + orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_thread(stream, msgmap,rawno); + top = orig_top = top_thread(stream, rawno); + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return 0; + + while (!done){ + count += count_this_thread(stream, top); + if (F_OFF(F_ENHANCED_THREAD, state) + || (move_next_this_thread(state, stream, msgmap, 0) <= 0) + || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_top != top_thread(stream, top))) + done++; + } + mn_set_cur(msgmap,mn_raw2m(msgmap, orig)); + return count; +} + +unsigned long +get_branch(MAILSTREAM *stream, PINETHRD_S *thrd) +{ + PINETHRD_S *nthrd = NULL; + unsigned long top; + + if (thrd->toploose && thrd->nextthd) + nthrd = fetch_thread(stream, thrd->nextthd); + if (!nthrd) + return thrd->branch; + top = top_thread(stream, thrd->rawno); + return thrd->branch + ? thrd->branch + : (F_ON(F_ENHANCED_THREAD, ps_global) + ? (top == top_thread(stream, nthrd->rawno) ? thrd->nextthd : 0L) + : 0L); +} + +unsigned long +get_next(MAILSTREAM *stream, PINETHRD_S *thrd) +{ + return thrd->next; +} + +long +get_length_branch(MAILSTREAM *stream, long rawno) +{ + int branchp = 0, done = 0; + long top, count = 1L, raw; + PINETHRD_S *thrd, *pthrd = NULL, *nthrd; + + thrd = fetch_thread(stream, rawno); + + if (!thrd) + return -1L; + + top = thrd->top; + + if (thrd->parent) + pthrd = fetch_thread(stream, thrd->parent); + + if (thrd->rawno == top) + branchp++; + + if (!branchp && !pthrd){ /* what!!?? */ + raw = top; + while (!done){ + pthrd = fetch_thread(stream, raw); + if ((pthrd->next == rawno) || (pthrd->branch == rawno)) + done++; + else{ + if (pthrd->next) + raw = pthrd->next; + else if (pthrd->branch) + raw = pthrd->branch; + } + } + } + + if (pthrd && pthrd->next == thrd->rawno && thrd->branch) + branchp++; + + if (pthrd && pthrd->next && pthrd->next != thrd->rawno){ + nthrd = fetch_thread(stream, pthrd->next); + while (nthrd && nthrd->branch && nthrd->branch != thrd->rawno) + nthrd = fetch_thread(stream, nthrd->branch); + if(nthrd && nthrd->branch && nthrd->branch == thrd->rawno) + branchp++; + } + + if(branchp){ + int entry = 0; + while(thrd && thrd->next){ + entry = 1; + count++; + thrd = fetch_thread(stream, thrd->next); + if (thrd->branch) + break; + } + if (entry && thrd->branch) + count--; + } + return branchp ? (count ? count : 1L) : 0L; +} + +int pine_compare_size_thread(const qsort_t *a, const qsort_t *b) +{ + SIZETHREAD_T *s = (SIZETHREAD_T *) a, *t = (SIZETHREAD_T *) b; + + return s->count == t->count ? s->pos - t->pos : s->count - t->count; +} + + + +void +find_msgmap(MAILSTREAM *stream, MSGNO_S *msgmap, int flags, SortOrder ordersort, unsigned is_rev) +{ + long *old_arrival,*new_arrival; + long init_thread, end_thread, current; + long i, j, k; + long tmsg, ntmsg, nthreads; + SIZETHREAD_T *l; + PINETHRD_S *thrd; + + erase_thread_info = 0; + current = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + switch(ordersort){ + case SortSize: + sort_folder(stream, msgmap, SortThread, 0, SRT_VRB, 0); + tmsg = mn_get_total(msgmap) + 1; + + if(tmsg <= 1) + return; + + for (i= 1L, k = 0L; i <= mn_get_total(msgmap); i += count_thread(ps_global, stream, msgmap, msgmap->sort[i]), k++); + l = (SIZETHREAD_T *) fs_get(k*sizeof(SIZETHREAD_T)); + for (j = 0L, i=1L; j < k && i<= mn_get_total(msgmap); ){ + l[j].count = count_thread(ps_global, stream, msgmap, msgmap->sort[i]); + l[j].pos = i; + i += l[j].count; + j++; + } + qsort((void *)l, (size_t) k, sizeof(SIZETHREAD_T), pine_compare_size_thread); + old_arrival = (long *) fs_get(tmsg * sizeof(long)); + for(i = 1L, j = 0; j < k; j++){ /* copy thread of length .count */ + int p; + for(p = 0; p < l[j].count; p++) + old_arrival[i++] = msgmap->sort[l[j].pos + p]; + } + fs_give((void **)&l); + break; + default: + sort_folder(stream, msgmap, ordersort, 0, SRT_VRB, 0); + tmsg = mn_get_total(msgmap) + 1; + + if (tmsg <= 1) + return; + + old_arrival = (long *) fs_get(tmsg * sizeof(long)); + for (i= 1L;(i <= mn_get_total(msgmap)) && (old_arrival[i] = msgmap->sort[i]); i++); + /* sort by thread */ + sort_folder(stream, msgmap, SortThread, 0, SRT_VRB, 0); + break; + + } + + ntmsg = mn_get_total(msgmap) + 1; + if (tmsg != ntmsg){ /* oh oh, something happened, we better try again */ + fs_give((void **)&old_arrival); + find_msgmap(stream, msgmap, flags, ordersort, is_rev); + return; + } + + /* reconstruct the msgmap */ + + new_arrival = (long *) fs_get(tmsg * sizeof(long)); + memset(new_arrival, 0, tmsg*sizeof(long)); + i = mn_get_total(msgmap); + /* we copy from the bottom, the last one to be filled is new_arrival[1] */ + while (new_arrival[1] == 0){ + int done = 0; + long n; + + init_thread = top_thread(stream, old_arrival[i]); + thrd = fetch_thread(stream, init_thread); + for (n = mn_get_total(msgmap); new_arrival[n] != 0 && !done; n--) + done = (new_arrival[n] == init_thread); + if (!done){ + mn_set_cur(msgmap, mn_raw2m(msgmap, init_thread)); + if(move_next_thread(ps_global, stream, msgmap, 0) <= 0) + j = mn_get_total(msgmap) - mn_raw2m(msgmap, init_thread) + 1; + else + j = mn_get_cur(msgmap) - mn_raw2m(msgmap, init_thread); + end_thread = mn_raw2m(msgmap, init_thread) + j; + for(k = 1L; k <= j; k++) + new_arrival[tmsg - k] = msgmap->sort[end_thread - k]; + tmsg -= j; + } + i--; + } + relink_threads(stream, msgmap, new_arrival); + for (i = 1; (i <= mn_get_total(msgmap)) + && (msgmap->sort[i] = new_arrival[i]); i++); + msgno_reset_isort(msgmap); + + fs_give((void **)&new_arrival); + fs_give((void **)&old_arrival); + + + if(is_rev && (mn_get_total(msgmap) > 1L)){ + long *rev_sort; + long i = 1L, l = mn_get_total(msgmap); + + rev_sort = (long *) fs_get((mn_get_total(msgmap)+1L) * sizeof(long)); + memset(rev_sort, 0, (mn_get_total(msgmap)+1L)*sizeof(long)); + while (l > 0L){ + if (top_thread(stream, msgmap->sort[l]) == msgmap->sort[l]){ + long init_thread = msgmap->sort[l]; + long j, k; + + mn_set_cur(msgmap, mn_raw2m(msgmap, init_thread)); + if (move_next_thread(ps_global, stream, msgmap, 0) <= 0) + j = mn_get_total(msgmap) - mn_raw2m(msgmap, init_thread) + 1; + else + j = mn_get_cur(msgmap) - mn_raw2m(msgmap, init_thread); + for (k = 0L; (k < j) && (rev_sort[i+k] = msgmap->sort[l+k]); k++); + i += j; + } + l--; + } + relink_threads(stream, msgmap, rev_sort); + for (i = 1L; i <= mn_get_total(msgmap); i++) + msgmap->sort[i] = rev_sort[i]; + msgno_reset_isort(msgmap); + fs_give((void **)&rev_sort); + } + mn_reset_cur(msgmap, first_sorted_flagged(is_rev ? F_NONE : F_SRCHBACK, + stream, mn_raw2m(msgmap, current), FSF_SKIP_CHID)); + msgmap->top = -1L; + + sp_set_unsorted_newmail(ps_global->mail_stream, 0); + + for(i = 1L; i <= ps_global->mail_stream->nmsgs; i++) + mail_elt(ps_global->mail_stream, i)->spare7 = 0; + + mn_set_sort(msgmap, SortThread); + mn_set_revsort(msgmap, is_rev); + erase_thread_info = 1; + clear_index_cache(stream, 0); +} + +void +move_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int direction) +{ + long new_cursor, old_cursor = mn_get_cur(msgmap); + int rv; + PINETHRD_S *thrd; + + rv = direction > 0 ? move_next_thread(state, stream, msgmap, 1): + move_prev_thread(state, stream, msgmap, 1); + if (rv > 0 && THRD_INDX_ENABLED()){ + new_cursor = mn_get_cur(msgmap); + mn_set_cur(msgmap, old_cursor); + unview_thread(state, stream, msgmap); + thrd = fetch_thread(stream,mn_m2raw(msgmap, new_cursor)); + mn_set_cur(msgmap, new_cursor); + view_thread(state, stream, msgmap, 1); + state->next_screen = SCREEN_FUN_NULL; + } +} + +void +relink_threads(MAILSTREAM *stream, MSGNO_S *msgmap, long *new_arrival) +{ + long last_thread = 0L; + long i = 0L, j = 1L, k; + PINETHRD_S *thrd, *nthrd; + + while (j <= mn_get_total(msgmap)){ + i++; + thrd = fetch_thread(stream, new_arrival[j]); + if (!thrd) /* sort failed!, better leave from here now!!! */ + break; + thrd->prevthd = last_thread; + thrd->thrdno = i; + thrd->head = new_arrival[1]; + last_thread = thrd->rawno; + mn_set_cur(msgmap, mn_raw2m(msgmap,thrd->top)); + k = mn_get_cur(msgmap); + if (move_next_thread(ps_global, stream, msgmap, 0) <= 0) + j += mn_get_total(msgmap) + 1 - k; + else + j += mn_get_cur(msgmap) - k; + if (!thrd->toploose) + thrd->nextthd = (j <= mn_get_total(msgmap)) ? new_arrival[j] : 0L; + else{ + int done = 0; + while(thrd->nextthd && !done){ + thrd->thrdno = i; + thrd->head = new_arrival[1]; + if (thrd->nextthd) + nthrd = fetch_thread(stream, thrd->nextthd); + else + done++; + if(top_thread(stream, thrd->rawno) == top_thread(stream, nthrd->rawno)) + thrd = nthrd; + else + done++; + } + thrd->nextthd = (j <= mn_get_total(msgmap)) ? new_arrival[j] : 0L; + last_thread = thrd->rawno; + } + } +} + +int +move_next_this_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) +{ + PINETHRD_S *thrd = NULL, *thrdnxt; + unsigned long rawno, top; + int rv = 1; + + if(!stream) + return -1; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return -1; + + top = top_thread(stream, rawno); + + thrdnxt = (top == rawno) ? fetch_thread(stream, top) : thrd; + if (thrdnxt->nextthd) + mn_set_cur(msgmap,mn_raw2m(msgmap, thrdnxt->nextthd)); + else{ + rv = 0; + if (display) + q_status_message(SM_ORDER, 0, 1, "No more Threads to advance"); + } + return rv; +} + +int +move_next_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) +{ + int collapsed, rv = 1, done = 0; + PINETHRD_S *thrd = NULL; + unsigned long orig, orig_top, top; + + if(!stream) + return 0; + + orig = mn_m2raw(msgmap, mn_get_cur(msgmap)); + move_top_thread(stream, msgmap,orig); + top = orig_top = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + if(top) + thrd = fetch_thread(stream, top); + + if(!thrd) + return 0; + + while (rv > 0 && !done){ + rv = move_next_this_thread(state, stream, msgmap, display); + if (F_OFF(F_ENHANCED_THREAD, state) + || !(top = mn_m2raw(msgmap, mn_get_cur(msgmap))) + || (orig_top != top_thread(stream, top))) + done++; + } + if (display){ + if (rv > 0 && SEP_THRDINDX()) + q_status_message(SM_ORDER, 0, 2, "Viewing next thread"); + if (!rv) + q_status_message(SM_ORDER, 0, 2, "No more threads to advance"); + } + if(rv <= 0){ + rv = 0; + mn_set_cur(msgmap, mn_raw2m(msgmap, orig)); + } + + return rv; +} + +int +move_prev_thread(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int display) +{ + PINETHRD_S *thrd = NULL; + unsigned long rawno, top; + int rv = 1; + + if(!stream) + return -1; + + rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + if(rawno) + thrd = fetch_thread(stream, rawno); + + if(!thrd) + return -1; + + top = top_thread(stream, rawno); + + if (top != rawno) + mn_set_cur(msgmap,mn_raw2m(msgmap, top)); + else if (thrd->prevthd) + mn_set_cur(msgmap,mn_raw2m(msgmap, top_thread(stream,thrd->prevthd))); + else + rv = 0; + if (display){ + if (rv && SEP_THRDINDX()) + q_status_message(SM_ORDER, 0, 2, "Viewing previous thread"); + if (!rv) + q_status_message(SM_ORDER, 0, 2, "No more threads to go back"); + } + + return rv; +} + +/* add more keys to this list */ +int +allowed_thread_key(SortOrder sort) +{ + return sort == SortArrival || sort == SortDate + || sort == SortScore || sort == SortThread + || sort == SortSize; +} + diff --git a/pith/thread.h b/pith/thread.h index 40b09bb0..4c5f1a97 100644 --- a/pith/thread.h +++ b/pith/thread.h @@ -37,6 +37,7 @@ typedef struct pine_thrd { unsigned long nextthd; /* next thread, only tops have this */ unsigned long prevthd; /* previous thread, only tops have this */ unsigned long top; /* top of this thread */ + unsigned long toploose; /* top of this thread, if is loose */ unsigned long head; /* head of the whole thread list */ } PINETHRD_S; @@ -92,7 +93,7 @@ void erase_threading_info(MAILSTREAM *, MSGNO_S *); void sort_thread_callback(MAILSTREAM *, THREADNODE *); void collapse_threads(MAILSTREAM *, MSGNO_S *, PINETHRD_S *); PINETHRD_S *msgno_thread_info(MAILSTREAM *, unsigned long, PINETHRD_S *, unsigned); -void collapse_or_expand(struct pine *, MAILSTREAM *, MSGNO_S *, unsigned long); +void collapse_or_expand(struct pine *, MAILSTREAM *, MSGNO_S *, unsigned long, int); void select_thread_stmp(struct pine *, MAILSTREAM *, MSGNO_S *); unsigned long count_flags_in_thread(MAILSTREAM *, PINETHRD_S *, long); unsigned long count_lflags_in_thread(MAILSTREAM *, PINETHRD_S *, MSGNO_S *, int); @@ -106,6 +107,24 @@ int view_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); int unview_thread(struct pine *, MAILSTREAM *, MSGNO_S *); PINETHRD_S *find_thread_by_number(MAILSTREAM *, MSGNO_S *, long, PINETHRD_S *); void set_search_bit_for_thread(MAILSTREAM *, PINETHRD_S *, SEARCHSET **); - +void find_msgmap(MAILSTREAM *, MSGNO_S *, int, SortOrder, unsigned); +void move_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +void relink_threads(MAILSTREAM *, MSGNO_S *, long *); +long top_thread(MAILSTREAM *, long); +long top_this_thread(MAILSTREAM *, long); +long get_length_branch(MAILSTREAM *, long); +unsigned long get_next(MAILSTREAM *,PINETHRD_S *); +unsigned long get_branch(MAILSTREAM *,PINETHRD_S *); +int count_thread(struct pine *, MAILSTREAM *, MSGNO_S *, long); +int count_this_thread(MAILSTREAM *, unsigned long); +int this_thread_is_kolapsed(struct pine *, MAILSTREAM *, MSGNO_S *, long); +int thread_is_kolapsed(struct pine *, MAILSTREAM *, MSGNO_S *, long); +int move_prev_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +int move_next_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +int move_next_this_thread(struct pine *, MAILSTREAM *, MSGNO_S *, int); +void move_top_thread(MAILSTREAM *, MSGNO_S *, long); +void move_top_this_thread(MAILSTREAM *, MSGNO_S *, long); +THREADNODE *copy_tree(THREADNODE *); +int allowed_thread_key(SortOrder sort); #endif /* PITH_THREAD_INCLUDED */ @@ -53,7 +53,7 @@ char * rfc1738_scan(char *line, int *len) { char *colon, *start, *end; - int n; + int n, delim; /* process each : in the line */ for(; (colon = strindex(line, ':')) != NULL; line = end){ @@ -137,6 +137,7 @@ rfc1738_scan(char *line, int *len) if(i != j){ *len = end - start; + delim = start > line && *(start - 1) == '<'; /* * Special case handling for comma. @@ -146,8 +147,8 @@ rfc1738_scan(char *line, int *len) * In most cases any way, that's why we have the * exception. */ - if(*(end - 1) == ',' - || (*(end - 1) == '.' && (!*end || *end == ' '))) + if(delim == 0 && (*(end - 1) == ',' + || (*(end - 1) == '.' && (!*end || *end == ' ')))) (*len)--; if(*len - (colon - start) > 0) diff --git a/po/Makefile.in b/po/Makefile.in index 9aa7c9f1..b0300698 100644 --- a/po/Makefile.in +++ b/po/Makefile.in @@ -11,7 +11,7 @@ # Origin: gettext-0.16 PACKAGE = alpine -VERSION = 2.10.9 +VERSION = 2.11 PACKAGE_BUGREPORT = chappa@washington.edu SHELL = /bin/sh diff --git a/web/src/alpined.d/Makefile.am b/web/src/alpined.d/Makefile.am index 9101e4fb..149271a2 100644 --- a/web/src/alpined.d/Makefile.am +++ b/web/src/alpined.d/Makefile.am @@ -50,3 +50,4 @@ local.c: alpineldap.c color.c imap.c ldap.c remote.c signal.c \ debug.c status.c stubs.c alpined.h color.h ldap.h echo "char datestamp[]="\"`date`\"";" > local.c echo "char hoststamp[]="\"`hostname`\"";" >> local.c + cat ../../../patchlevel >> local.c diff --git a/web/src/alpined.d/Makefile.in b/web/src/alpined.d/Makefile.in index 644fd530..c219193c 100644 --- a/web/src/alpined.d/Makefile.in +++ b/web/src/alpined.d/Makefile.in @@ -689,7 +689,7 @@ local.c: alpineldap.c color.c imap.c ldap.c remote.c signal.c \ debug.c status.c stubs.c alpined.h color.h ldap.h echo "char datestamp[]="\"`date`\"";" > local.c echo "char hoststamp[]="\"`hostname`\"";" >> local.c - + cat ../../../patchlevel >> local.c # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: diff --git a/web/src/alpined.d/alpined.c b/web/src/alpined.d/alpined.c index e35ba9e6..b1ab636e 100644 --- a/web/src/alpined.d/alpined.c +++ b/web/src/alpined.d/alpined.c @@ -2755,7 +2755,7 @@ PEConfigCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST init_save_defaults(); break; case V_SORT_KEY: - decode_sort(ps_global->VAR_SORT_KEY, &ps_global->def_sort, &def_sort_rev); + decode_sort(ps_global->VAR_SORT_KEY, &ps_global->def_sort, &def_sort_rev, 0); break; case V_VIEW_HDR_COLORS : set_custom_spec_colors(ps_global); @@ -6331,7 +6331,7 @@ PEMailboxCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST && mn_get_revsort(sp_msgmap(ps_global->mail_stream)) == reversed)) sort_folder(ps_global->mail_stream, sp_msgmap(ps_global->mail_stream), ps_global->sort_types[i], - reversed, 0); + reversed, 0, 1); break; } |