From 8aaa8a4d27d6fd9df570f3e0d84c9552f9d415c8 Mon Sep 17 00:00:00 2001 From: Erich Eckner Date: Sun, 1 Sep 2019 22:53:55 +0200 Subject: alpine: add rules.patch --- alpine/PKGBUILD | 7 +- alpine/rules.patch | 5676 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 5681 insertions(+), 2 deletions(-) create mode 100644 alpine/rules.patch (limited to 'alpine') diff --git a/alpine/PKGBUILD b/alpine/PKGBUILD index 209a9278e..4c17d01a7 100644 --- a/alpine/PKGBUILD +++ b/alpine/PKGBUILD @@ -2,9 +2,9 @@ # Contributor: Adrian C. pkgname=alpine -pkgver=2.21.99999.r13.418e8bc +pkgver=2.21.99999.r14.6076d5d _commit=${pkgver##*.} -pkgrel=2 +pkgrel=1 arch=("i686" "pentium4" "x86_64") pkgdesc="Apache licensed PINE mail user agent" url="http://alpine.freeiz.com" @@ -20,14 +20,17 @@ replaces=("pine") options=("!makeflags") source=( "alpine::git://repo.or.cz/alpine.git#commit=${_commit}" + 'rules.patch' 'topal-patch.patch' ) sha512sums=('SKIP' + 'b6719c245e5539af07d18b14e4e30e47c8ac1fcd91b431de40e8c0968c09707c1476adb1d322567e0683f28923022aed07291781de055298b49e39d897dbec23' 'bdaf9f0ed2bb9d10eaf4b456e24684cd09eb9e97ae2cf6eb4a2bc2bb5e231e8254d79efa63da9918022302fd970180f2dcd90a69d8c8d817c729c6a18fd6d506') prepare() { cd "${srcdir}/${pkgname}" patch -p1 -i "${srcdir}/topal-patch.patch" + patch -p1 -i "${srcdir}/rules.patch" } pkgver() { diff --git a/alpine/rules.patch b/alpine/rules.patch new file mode 100644 index 000000000..01369ced9 --- /dev/null +++ b/alpine/rules.patch @@ -0,0 +1,5676 @@ +diff --git a/alpine/adrbkcmd.c b/alpine/adrbkcmd.c +index 66b1191..23d25a3 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 e0c1f40..99a8a23 100644 +--- a/alpine/alpine.c ++++ b/alpine/alpine.c +@@ -497,6 +497,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){ +@@ -3245,6 +3246,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/confscroll.c b/alpine/confscroll.c +index 2fe034b..2b69445 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") +@@ -2442,6 +2443,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); + +@@ -5206,6 +5210,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); +diff --git a/alpine/dispfilt.c b/alpine/dispfilt.c +index 16ade44..41927ae 100644 +--- a/alpine/dispfilt.c ++++ b/alpine/dispfilt.c +@@ -461,3 +461,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 c61a7a8..833ff6f 100644 +--- a/alpine/dispfilt.h ++++ b/alpine/dispfilt.h +@@ -25,7 +25,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 c48536f..46803f5 100644 +--- a/alpine/folder.c ++++ b/alpine/folder.c +@@ -248,7 +248,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; +@@ -345,6 +345,7 @@ folder_screen(struct pine *ps) + pine_mail_close(*fs.cache_streamp); + + ps->prev_screen = folder_screen; ++ strcpy(ps->screen_name, "unknown"); + } + + +diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c +index 204c314..943f074 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 +@@ -2708,6 +2709,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': +@@ -2758,12 +2762,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; +@@ -2771,6 +2775,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) + alpine_panic("no context ptr in save_prompt"); +@@ -2780,6 +2785,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)) +diff --git a/alpine/mailindx.c b/alpine/mailindx.c +index c5862c6..0e83c90 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"); + } + + +diff --git a/alpine/mailview.c b/alpine/mailview.c +index c151a1e..45d90e9 100644 +--- a/alpine/mailview.c ++++ b/alpine/mailview.c +@@ -244,6 +244,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")); +@@ -480,6 +482,8 @@ mail_view_screen(struct pine *ps) + } + while(ps->next_screen == SCREEN_FUN_NULL); + ++ strcpy(ps->screen_name, "unknown"); ++ + if(we_cancel) + cancel_busy_cue(-1); + +diff --git a/alpine/osdep/termin.gen.c b/alpine/osdep/termin.gen.c +index dfea78f..9af7674 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" +@@ -72,7 +74,8 @@ int pcpine_oe_cursor(int, long); + * 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 +117,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(); + +@@ -1158,6 +1196,7 @@ process_config_input(UCS *ch) + if(ps_global->initial_cmds && !*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/reply.c b/alpine/reply.c +index 89c0d9b..0e0a3e2 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... +@@ -287,8 +352,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 */ +@@ -307,13 +382,7 @@ 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 (ps_global->reply.role_chosen) +- role = ps_global->reply.role_chosen; +- else if(role_arg) +- role = copy_action(role_arg); +- +- if(!role){ ++ if(!do_role_early){ + rflags = ROLE_REPLY; + if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){ + /* setup default role */ +@@ -724,6 +793,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); + +@@ -1598,9 +1670,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; +@@ -1832,6 +1909,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); +diff --git a/alpine/roleconf.c b/alpine/roleconf.c +index a226f47..0d00b79 100644 +--- a/alpine/roleconf.c ++++ b/alpine/roleconf.c +@@ -7706,6 +7706,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 41dff03..af7d2a1 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; +@@ -240,6 +240,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); +@@ -449,8 +454,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; + +@@ -645,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 +@@ -2477,6 +2491,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); +diff --git a/pith/Makefile.am b/pith/Makefile.am +index 09d8f99..de2ea57 100644 +--- a/pith/Makefile.am ++++ b/pith/Makefile.am +@@ -26,7 +26,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 ical.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 64b9a46..ea51bc9 100644 +--- a/pith/Makefile.in ++++ b/pith/Makefile.in +@@ -134,7 +134,7 @@ am_libpith_a_OBJECTS = ablookup.$(OBJEXT) abdlc.$(OBJEXT) \ + mimedesc.$(OBJEXT) mimetype.$(OBJEXT) msgno.$(OBJEXT) \ + newmail.$(OBJEXT) news.$(OBJEXT) pattern.$(OBJEXT) \ + pipe.$(OBJEXT) readfile.$(OBJEXT) remote.$(OBJEXT) \ +- reply.$(OBJEXT) rfc2231.$(OBJEXT) save.$(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) \ +@@ -437,7 +437,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 ical.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 + +@@ -580,6 +580,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@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +diff --git a/pith/adrbklib.c b/pith/adrbklib.c +index fe054d9..09e093c 100644 +--- a/pith/adrbklib.c ++++ b/pith/adrbklib.c +@@ -5138,8 +5138,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/conf.c b/pith/conf.c +index cea59de..59e6744 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" +@@ -223,6 +224,36 @@ 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."; + + #ifdef _WINDOWS +@@ -555,6 +586,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}, +@@ -2653,6 +2712,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 */ +@@ -3099,6 +3159,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, +@@ -7725,6 +7787,34 @@ config_help(int var, int feature) + 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 : +diff --git a/pith/conf.h b/pith/conf.h +index a2e5a8a..cf66627 100644 +--- a/pith/conf.h ++++ b/pith/conf.h +@@ -148,6 +148,46 @@ + #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 +diff --git a/pith/conftype.h b/pith/conftype.h +index 42573ce..cff46b3 100644 +--- a/pith/conftype.h ++++ b/pith/conftype.h +@@ -70,6 +70,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 +@@ -334,6 +348,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, +diff --git a/pith/detoken.c b/pith/detoken.c +index 01f2d51..43687e5 100644 +--- a/pith/detoken.c ++++ b/pith/detoken.c +@@ -25,7 +25,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 +@@ -91,6 +91,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; +@@ -104,14 +106,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; +@@ -302,7 +315,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/indxtype.h b/pith/indxtype.h +index 33ef5d9..c34ea39 100644 +--- a/pith/indxtype.h ++++ b/pith/indxtype.h +@@ -84,6 +84,11 @@ typedef enum {iNothing, iStatus, iFStatus, iIStatus, iSIStatus, + iCurNews, iArrow, + iMailbox, iAddress, iInit, iCursorPos, + iDay2Digit, iMon2Digit, iYear2Digit, ++ iFolder, iFlag, iCollection, iRole, iProcid, iScreen, iPkey, ++ iNick, iFccFrom, iFccSender, iAltAddress, ++ iAddressTo, iAddressCc, iAddressRecip, iAddressSender, ++ iBcc, iLcc, ++ iFfrom, iFadd, + iSTime, iSTime24, iKSize, + iRoleNick, iNewLine, + iHeader, iText, +@@ -105,15 +110,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/mailcmd.c b/pith/mailcmd.c +index 9a852c9..b1ab110 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! */ +@@ -991,6 +993,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 +@@ -1114,7 +1117,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) +@@ -1136,124 +1142,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: +- alpine_panic("Unexpected incoming startup case"); +- break; +- +- } ++ find_startup_position(use_this_startup_rule, m, pc); + } + else if(IS_NEWS(ps_global->mail_stream)){ + /* +@@ -1431,9 +1320,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 */ +@@ -1716,6 +1607,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; +@@ -1755,8 +1649,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'; +@@ -1794,7 +1689,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; +@@ -2761,3 +2658,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: ++ alpine_panic("Unexpected incoming startup case"); ++ break; ++ ++ } ++} +diff --git a/pith/mailcmd.h b/pith/mailcmd.h +index 0e00895..a7aa268 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 4c64996..6f11327 100644 +--- a/pith/mailindx.c ++++ b/pith/mailindx.c +@@ -41,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 +@@ -378,6 +379,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)){ +@@ -451,14 +459,14 @@ 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}, + {"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}, + {"SHORTSUBJECT", iShortSubject, FOR_INDEX|FOR_REPLY_INTRO|FOR_TEMPLATE}, + {"FULLSTATUS", iFStatus, FOR_INDEX}, + {"IMAPSTATUS", iIStatus, FOR_INDEX}, +@@ -470,56 +478,60 @@ 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}, ++ {"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}, + {"SMARTTIME24", iSTime24, 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}, ++ {"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}, ++ {"ADDRESSSENDER", iAddressSender, 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}, +@@ -528,56 +540,71 @@ 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}, ++ {"FCCFROM", iFccFrom, FOR_RULE|FOR_SAVE}, ++ {"FCCSENDER", iFccSender, FOR_RULE|FOR_SAVE}, ++ {"ALTADDRESS", iAltAddress, 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} + }; + +@@ -2484,6 +2511,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" +@@ -3855,7 +3900,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)); +@@ -5434,10 +5489,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; +@@ -5493,6 +5548,14 @@ subj_str(INDEXDATA_S *idata, char *str, size_t strsize, SubjKW kwtype, int openi + * 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); ++ } ++ + if(shorten) + shorten_subject(origsubj); + +@@ -5935,6 +5998,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); + } + + +@@ -6303,16 +6369,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", (int)(strsize-1-4), (int)(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; + } + +@@ -6325,7 +6408,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: +@@ -6623,3 +6714,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 355faec..248ef94 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/makefile.wnt b/pith/makefile.wnt +index 267ab76..0d1d2d2 100644 +--- a/pith/makefile.wnt ++++ b/pith/makefile.wnt +@@ -46,7 +46,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 smime.h smkeys.h sort.h sorttype.h \ ++ rfc2231.h rules.h save.h savetype.h search.h send.h sequence.h signal.h smime.h smkeys.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 + +@@ -56,9 +56,9 @@ OFILES= ablookup.obj abdlc.obj addrbook.obj addrstring.obj adrbklib.obj bldaddr. + ical.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 \ +- smime.obj smkeys.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 ++ readfile.obj remote.obj reply.obj rfc2231.obj rules.obj save.obj search.obj sequence.obj \ ++ send.obj smime.obj smkeys.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 + + all: libpith.lib + +diff --git a/pith/pine.hlp b/pith/pine.hlp +index 41d7521..874b5bf 100644 +--- a/pith/pine.hlp ++++ b/pith/pine.hlp +@@ -4214,6 +4214,7 @@ There are also additional details on +
  • FEATURE: +
  • FEATURE: +
  • FEATURE: ++
  • FEATURE: +
  • FEATURE: +
  • FEATURE: +
  • FEATURE: +@@ -19408,6 +19409,7 @@ This set of special tokens may be used in the + "" option, + in the "" option, + in signature files, ++in the "new-rules" option, + in template files used in + "roles", and in the folder name + that is the target of a Filter Rule. +@@ -19420,7 +19422,7 @@ and in the target of Filter Rules. +

    +

    + +-

    Tokens Available for all Cases (except Filter Rules)

    ++

    Tokens Available for all Cases (except Filter Rules or in some cases for new-rules)

    + +
    +
    SUBJECT
    +@@ -19454,6 +19456,22 @@ email address, never the personal name. + For example, "mailbox@domain". + + ++
    ADDRESSTO
    ++
    ++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". ++
    ++ ++
    ADDRESSSENDER
    ++
    ++This is similar to the "sender" token, only it is always the ++email address of all person listed in the Sender: field of the message. ++Example: "mailbox@domain". ++
    ++ +
    MAILBOX
    +
    + This is the same as the "ADDRESS" except that the +@@ -19501,6 +19519,15 @@ are unavailable) of the persons specified in the + message's "Cc:" header field. +
    + ++
    ADDRESSCC
    ++
    ++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". ++
    ++ +
    RECIPS
    +
    + This token represents the personal names (or email addresses if the names +@@ -19509,6 +19536,14 @@ message's "To:" header field and + the message's "Cc:" header field. +
    + ++
    ADDRESSRECIPS
    ++
    ++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. ++
    ++ ++ +
    NEWSANDRECIPS
    +
    + This token represents the newsgroups from the +@@ -20632,6 +20667,110 @@ This is an end of line marker. +
    +
    + ++

    ++

    Tokens Available Only for New-Rules

    ++ ++
    ++
    FCCFROM
    ++
    ++The Fcc: folder assigned to the email address in the From: field in the ++addressbook. ++
    ++
    ++ ++
    ++
    FCCSENDER
    ++
    ++The Fcc: folder assigned to the email address in the Sender: field in the ++addressbook. ++
    ++
    ++ ++
    ++
    ALTADDRESS
    ++
    ++The value of your ++ ++variable. At this time, no expansion of regular expressions is supported. ++
    ++
    ++ ++
    ++
    NICK
    ++
    ++Nickname of the person in the From field in your addressbook. ++
    ++
    ++ ++
    ++
    FOLDER
    ++
    ++Name of the folder where the rule will be applied. ++
    ++
    ++ ++
    ++
    COLLECTION
    ++
    ++Name of the collection list where the rule will be applied. ++
    ++
    ++ ++
    ++
    ROLE
    ++
    ++Name of the Role used to reply a message. ++
    ++
    ++ ++
    ++
    BCC
    ++
    ++Not implemented yet, but it will be implemented in future versions. It will ++be used for compose ++reply ++forward ++rules. ++
    ++
    ++ ++
    ++
    LCC
    ++
    ++This is the value of the Lcc: field at the moment that you start the composition. ++
    ++
    ++ ++
    ++
    FORWARDFROM
    ++
    ++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. ++
    ++
    ++ ++
    ++
    FORWARDADDRESS
    ++
    ++This is the address of the person that sent the message that you ++are forwarding. ++
    ++
    ++ ++ ++ ++ ++
    ++
    FLAG
    ++
    ++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 ++new rules explanation and example help. ++
    ++
    ++ +

    +

    Token Available Only for Templates and Signatures

    + +@@ -23856,6 +23995,897 @@ character sets Alpine knows about by using the "T" ToCharsets command. + <End of help on this topic> + + ++====== h_config_procid ===== ++ ++ ++Token: PROCID ++ ++ ++

    TOKEN: PROCID explained

    ++ ++

    ++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. ++ ++

    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. ++ ++

    ++Consider the following examples for forward-rules: ++ ++

    ++_ROLE_ == {work} => _SUBJECT_ := _COPY_{[tag] _SUBJECT_} ++ ++

    ++and ++ ++

    ++_ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ <_FORWARDADDRESS_>} ++ ++

    ++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 ++ ++

    ++_PROCID_ == {fwd-subject} && _ROLE_ == {work} => _SUBJECT_ := _COPY_{[tag] _SUBJECT_} ++ ++

    ++In this case, this rule will be tested fully only when we are determining ++the subject line of a forwarded message, not otherwise. ++ ++

    ++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. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_compose_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_compose-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    At this time, this option is used to generate values for signature ++files that is not possible to do with the use of ++roles. ++ ++

    For example, you can have a rule like:
    ++_TO_ >> {Peter Flintstones} => _SIGNATURE_{~/.petersignature} ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_forward_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_forward-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    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 this link. ++ ++

    If you want to edit the subject of a forwarded message, use the ++PROCID fwd-subject. For example you could have a rule like ++ ++

    ++_ROLE_ == {admin} && _SUBJECT_ !> {[tag] } => _COPY_{[tag] _SUBJECT_} ++ ++

    Another way in which this option can be used, is to trim the values of ++some fields. For this application the PROCID is fwd-lcc. For ++example it can be used in the following way: ++ ++

    ++_ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ <_FORWARDADDRESS_>} ++ ++

    Other functions that can be used in this option are _EXEC_ and _REXTRIM_. ++ ++

    You can also use the _EXEC_ function. The documentation for this function ++is in the ++ ++help text. ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_index_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_index-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to supersede the value of the option for specific folders. In ++this form you can have different index-formats for different folders. For ++example an entry here may be: ++ ++

    ++_FOLDER_ == {INBOX} => _INDEX_{IMAPSTATUS DATE FROM(33%) SIZE SUBJECT(67%)} ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_pretty_command ===== ++ ++ ++Pretty-Command Explained ++ ++ ++

    Pretty Command Explained

    ++ ++

    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. ++ ++

    [Command "RETURN" not defined for this screen. Use ? for help] ++ ++

    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. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_key_macro_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_key-definition-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    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. ++ ++

    Always use the _SCREEN_ token as defined below.. You have been ++warned! ++ ++

    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. ++ ++

    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. ++ ++

    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 (MESSAGE INDEX), ++the screen where your message is displayed ++(MESSAGE TEXT) and the screen where the list of ++folders is displayed (FOLDER LIST). 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 and are case sensitive. ++ ++

    In order to define the screen, you use the _SCREEN_ token, so for ++example, you can write _SCREEN_ == {index}. ++ ++

    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 "~" ++key to activate the command. Some keystrokes (like control, or ++function keys) are encoded in special ways. You should read the ++full explanation on how to find ++out the encoding for each keystroke. ++ ++

    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}. ++ ++

    Observe that in the above example the different inputs are separated ++by commas. This is the standard way in which the ++ 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. ++ ++

    ++"_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{$,h}" ++ ++

    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 ++ ++

    ++_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{"$h"} ++ ++

    For more information on how to define the argument of the _COMMAND_ ++token see the help of ++. ++ ++

    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. ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_replace_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_replace-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine print different values for specific ++tokens in the . For example you ++can replace strings like "To: newsgroup" by your name. ++ ++

    Here are examples of possible rules: ++ ++

    _FOLDER_ != {sent-mail} && _NICK_ != {} => _FROM_ := _REPLACE_{_FROM_ (_NICK_)} ++ ++

    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 ++ ++

    _FOLDER_ == {INBOX} => _SUBJECT_ := _REXTRIM_{\[some-tag-here #[0-9].*\]} ++ ++

    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.
    ++ ++

    _FOLDER_ == {some-folder} => _OPENINGTEXTNQ_ := _REXTRIM_{On.*wrote: } ++ ++

    You can also use the _EXEC_ function. The documentation for this function ++is in the ++ ++help text. ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_reply_leadin_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_reply-leadin-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine generate a different ++ string dependent either on ++the person you are replying to, or the folder where the message is being ++replied is in, or both. ++ ++

    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: ++

    ++_FOLDER_ << {pine-info;_NEWS_} => _REPLY_{*** _FROM_ _ADDRESS_("_FROM_" "" "(_ADDRESS_) ")wrote in_NEWS_("" " the" "") _FOLDER_ _NEWS_("" "list " "")_SMARTDATE_("Today" "today" "on _LONGDATE_"):} ++ ++

    Here there is an example that one can use to change the reply indent string ++to reply people that speak spanish. ++

    ++_FROM_{Condorito;Quico} => _REPLY_{*** _FROM_ (_ADDRESS_) escribió _SMARTDATE_("Today" "hoy" "en _LONGDATE_"):} ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_resub_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_reply-subject-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine generate a different subject when ++replying rather than the one Alpine would generate automatically. ++ ++

    Here there are a couple of examples about how to use this ++configuration option: ++ ++

    In order to have messages with empty subject to be replied with the message ++"your message" use the rule
    ++

    _SUBJECT_ == {} => _RESUB_{Re: your message}
    ++ ++

    If you want to trim some parts of the subject when you reply use the ++rule
    ++

    _SUBJECT_ >> {[one];two} => _SUBJECT_ := _TRIM_{[;];two}
    ++ ++

    this rule removes the brackets "[" and "]" whenever the string "[one]" ++appears in it, it also removes the word "two" from it. ++ ++

    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
    ++

    _SUBJECT_ >> {Sv: ;AW: } => _SUBJECT_ := _TRIM_{Sv: ;AW: }
    ++

    ++would eliminate undesired strings in replies. ++ ++

    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. ++ ++

    ++#!/bin/sh
    ++sed 's/a//g' $1 > /tmp/mytest
    ++mv /tmp/mytest $1
    ++
    ++ ++

    ++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. ++ ++

    ++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 ++ ++

    ++_SUBJECT_ := _EXEC_{/path/to/script} ++ ++

    for the action. So for example ++ ++

    ++_FOLDER_ := {sent-mail} => _SUBJECT_ := _EXEC_{/path/to/script} ++ ++

    is a valid rule. ++ ++

    You can also use this configuration option to customize reply subjects ++according to the sender of the message. ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++====== h_config_sort_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_sort-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used to have Alpine sort different folders in different orders ++and thus override the value already set in the ++ configuration option. ++ ++

    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. ++ ++

    ++_COLLECTION_ >> {Incoming-Folders;News} && _FOLDER_ != {INBOX} => _SORT_{tHread} ++ ++

    Another example could be
    ++_FOLDER_ == {Mailing List} => _SORT_{Reverse tHread} ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++ ++====== h_config_save_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_save-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    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). ++ ++

    If this option is set and the ++ configuration ++option is also enabled then these definitions will be used to move messages ++from your INBOX when exiting Alpine. ++ ++

    Here there are some examples
    ++_FLAG_ >> {D} -> Trash
    ++_FROM_ == {U2} -> Bono
    ++_FOLDER_ == {comp.mail.pine} -> pine-stuff
    ++_NICK_ != {} -> _NICK_/_NICK_
    ++_DATEISO_ >> {02-10;02-11} -> archive-oct-nov-2002 ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++ ++====== h_config_reply_indent_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_reply-indent-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    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 ++is used. ++ ++

    The associated function to this configuration option is called "RESTR" (for ++REply STRing). Some examples of its use are:
    ++_FROM_ == {Your Boss} => _RESTR_{"> "}
    ++_FROM_ == {My Wife} => _RESTR_{":* "}
    ++_FROM_ == {Perter Flintstone;Wilma Flintstone} => _RESTR_{"_INIT_ > "}
    ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++ ++====== h_config_smtp_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_smtp-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    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 ++"" configuration ++option according to the rules used in that variable. ++ ++

    The function associated to this configuration option is _SMTP_, an example ++of the use of this function is
    ++_ADDRESSTO_ == {peter@bedrock.com} => _SMTP_{smtp.bedrock.com} ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++ ++====== h_config_startup_rules ===== ++ ++ ++OPTION: <!--#echo var="VAR_startup-rules"--> ++ ++ ++

    OPTION:

    ++ ++

    This option is used when a folder is being opened. You can use it to specify its and override ++Alpine's global value set for all folders. ++ ++

    An example of the usage of this option is:
    ++_FOLDER_ == {Lynx;pine-info;_NEWS_} => _STARTUP_{first-unseen} ++ ++

    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 link. ++ ++

    <End of help on this topic> ++ ++ ++ ++====== h_config_new_rules ===== ++ ++ ++OPTION: New Rules Explained ++ ++ ++

    OPTION: New Rules Explained

    ++ ++This is a quite powerful option. Here you can define rules that override ++the values of any other option you have set in Alpine. ++ ++

    ++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. ++ ++

    ++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 satisfied. ++ ++

    ++ Here is an example: ++ ++

    ++ _FROM_ == {Fred Flintstone} => _SAVE_{Fred} ++ ++

    ++ Here the separator is "=>". Whatever is to the left of the separator ++is the condition (that is _FROM_ == {Fred Flintstone}) 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 ++Flintstone", 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. ++ ++

    ++ 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. ++ ++

    ++ 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. ++ ++

    ++ Later in the document you will find examples. Here is a short ++description of what each function does: ++ ++

    ++

      ++
    • _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 ++, ++ and ++. ++See the help of those options for examples of how to use this function ++and configure these rules. ++
       
      ++
    • _INDEX_ : This function takes as an argument an index-format, and ++makes that the index-format for the specified folder. ++
       
      ++
    • _REPLACE_ : This function replaces the subject/from of the given e-mail by ++another subject/from only when displaying the index. ++
       
      ++
    • _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. ++
       
      ++
    • _RESTR_ : This function takes as an argument the value of the ++reply-indent-string to be used to answer the message being replied to. ++
       
      ++
    • _RESUB_ : This function replaces the subject of the given e-mail by ++another subject only when replying to a message. ++
       
      ++
    • _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. ++
       
      ++
    • _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. ++
       
      ++
    • _SMTP_ : This function takes as an argument the definition of a ++SMTP server. ++
       
      ++
    • _SORT_ : This function takes as an argument a Sort Style, and sorts a ++specified folder in that sort order. ++
       
      ++
    • _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_. ++
       
      ++
    • _REXTRIM_ : Same as _TRIM_ but its argument is one and ++only one extended regular expression. ++
       
      ++
    • _STARTUP_ : This function takes as an argument an ++incoming-startup-rule, and open an specified folder using that rule. ++
       
      ++
    • _THREADSTYLE_ : This function takes as an argument a ++threading-display-style and uses it to display threads in a folder. ++
       
      ++
    • _THREADINDEX_ : This function takes as an argument a ++threading-index-style and uses it to display threads in a folder. ++
    ++ ++

    ++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: ++ ++

    ++ _FROM_ == {Fred Flintstone} => _SAVE_{Fred} ++ ++

    it will only be applied if the from is "Fred Flintstone". If ++the From is "Wilma Flintstone" the rule will be skipped. ++ ++

    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 here. ++ ++

    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 ++this link. ++ ++

    There are two more tokens related to the option ++key-definition-rules. Those tokens ++are only specific to that option, and hence are not explained here. ++ ++

    You can also test in different ways, you can use the following ++"test operands": <<, !<, >>, !>, == and !=. ++All of them are two characters long. Here is the meaning of them: ++ ++

    ++

      ++
    • << : 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. ++
    • >> : 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 Flintstone" or "Fred P. Flintstone" or "Freddy". ++
    • == : 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". ++
    • !< : This is true only when << is false and vice versa. ++
    • !> : This is true only when >> is false and vice versa. ++
    • != : This is true only when == is false and vice versa. ++
    ++ ++

    ++ 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. ++ ++

    ++ _FOLDER_ << {folder1; folder2} => _SORT_{OrderedSubj} ++ ++

    ++ Here is the first subtlety about these definitions. Notice that the ++following rule: ++ ++

    ++ _FOLDER_ == {folder1; folder2} => _SORT_{Reverse OrderedSubj} ++ ++

    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
    ++"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. ++ ++

    ++ Here are a few examples of what we have talked about before. ++ ++

    ++_NICK_ == {lisa;kika} => _SAVE_{_NICK_/_NICK_}
    ++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" ++ ++

    ++_FOLDER_ == {Lynx} -> lynx
    ++This, is an abbreviation of the following rule:
    ++_FOLDER_ == {Lynx} => _SAVE_{lynx}
    ++(note the change in separator from "=>" to "->"). In the future ++I will use that abbreviation. ++ ++

    _FOLDER_ << {comp.mail.pine; pine-info; pine-alpha} -> pine
    ++Any message in the folders "comp.mail.pine", "pine-info" or "pine-alpha" ++will be saved to the folder "pine". ++ ++

    _FROM_ << {Pine Master} -> pine
    ++Any message whose From field contains ++"Pine Master" will be saved in the folder pine. ++ ++

    _FOLDER_ << {Lynx; pine-info; comp.mail.pine} => ++_INDEX_{IMAPSTATUS MSGNO DATE FROMORTO(33%) SUBJECT(66%)}
    Use a ++different index-format for the folders "Lynx", "pine-info" and ++"comp.mail.pine", where the size is not present. ++ ++

    _FOLDER_ == {Lynx;pine-info} => _REPLY_{*** _FROM_ (_ADDRESS_) ++wrote in the _FOLDER_ list _SMARTDATE_("Today" "today" "on ++_LONGDATE_"):}
    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: ++ ++

    ++*** Steve Hubert (hubert@cac.washington.edu) wrote in the pine-info list today: ++ ++

    ++However replying for a message in the Lynx list would look: ++ ++

    ++*** mattack@area.com (mattack@area.com) wrote in the Lynx list today: ++ ++

    ++If you write in more than one language you can use this feature to create ++Reply-leadin-strings in different languages. ++ ++

    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. ++ ++

    _FOLDER_ << {Lynx; comp.mail.pine; pine_info; pine-alpha} => ++_SORT_{OrderedSubj}
    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). ++ ++

    The last examples use the function _TRIM_ which has a special form. ++This function can only be used in the index list. ++ ++

    _FOLDER_ << {Lynx} => _SUBJECT_ := _TRIM_{lynx-dev }
    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. ++ ++

    _FROM_ >> {Name (Comment)} => _FROM_ := ++_TRIM_{ (Comment)}
    Remove the part " (Comment)" ++from the _FROM_, so when displaying in the index the real From "Name" ++will appear. ++ ++

    _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. ++ ++

    You can add more complexity to your rules by checking more than one ++conditions before a rule is executed. More than one condition can be ++checked by separating different conditions by the && (and) separator, ++or using the || (or) separator. For example we could have a rule that ++saves all ++messages in inbox from Rubye, to the Personal folder, as ++ ++

    _FOLDER_ == {INBOX} && _FROM_ >> {Rubye} => _SAVE_{Personal} ++ ++

    We could also have a rule that is triggered by an "or" ++condition by, sat for messages from Andres or messages in the index ++to trigger a specific reply leadin string. ++ ++

    _FOLDER_ == {INBOX} || _FROM_ >> {Andres} => _REPLY_{You wrote:} ++ ++

    Observe that the construction ++ ++

    _TOKEN_ == {value1} || _TOKEN_ == {value2} ++ ++

    can be shortened to ++ ++

    _TOKEN_ == {value1;value2} ++ ++

    Round parentheses can be used to group some conditions, for example ++ ++

    (_FROM_ >> {Andres} && _FOLDER_ == {INBOX}) || _FROM_ >> {Rubye} ++ ++ ++

    You can also list your index by nick, in the following way:
    ++_NICK_ != {} => _FROM_ := _REPLACE_{_NICK_} ++ ++

    ++ If you want to open the folder "pine-info" in the first non-read message ++use the rule:
    ++_FOLDER_ == {pine-info} => _STARTUP_{first-unseen} ++ ++

    ++ If you want to move your deleted messages to a folder, called "Trash", use ++the following rule:
    ++_FLAG_ >> {D} -> Trash ++ ++

    ++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. ++ ++

    If you want to use a specific signature when you are in a specific collection ++use the following rule:
    ++_COLLECTION_ == {Mail} => _SIGNATURE_{/full/path/to/.signature} ++ ++

    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. ++ ++

    ++

    ++<End of help on this topic> ++ ++ + ====== h_config_char_set ===== + + +@@ -27421,6 +28451,76 @@ MESSAGE TEXT screen. + + + ++

    ++

    ++<End of help on this topic> ++ ++ ++====== h_config_thread_display_style_rule ===== ++ ++ ++OPTION: Threading-Display-Style-Rule ++ ++ ++

    OPTION: Threading-Display-Style-Rule

    ++ ++This option is very similar to ++, but it is a rule which specifies the ++display styles for a thread that you want displayed in a specific ++folder or collection. ++

    ++The token to be used in this function is _THREADSTYLE_. Here there is ++an example of its use ++

    ++_FOLDER_ == {pine-info} => _THREADSTYLE_{mutt-like} ++

    ++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 threading-display-style ++configuration option. ++ ++

    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 link. ++ ++

    ++

    ++<End of help on this topic> ++ ++ ++====== h_config_thread_index_style_rule ===== ++ ++ ++OPTION: Threading-Index-Style-Rule ++ ++ ++

    OPTION: Threading-Index-Style-Rule

    ++ ++This option is very similar to ++, but it is a rule which specifies the ++index styles for a thread that you want displayed in a specific ++folder or collection. ++

    ++The token to be used in this function is _THREADINDEX_. Here there is ++an example of its use ++

    ++_FOLDER_ == {pine-info} => _THREADINDEX_{regular-index-with-expanded-threads} ++

    ++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 ++configuration option. ++ ++

    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 link. ++ +

    +

      +
    • Finding more information and requesting help +@@ -31117,6 +32217,29 @@ them as deleted in the INBOX. Messages in the INBOX marked with an + <End of help on this topic> + + ++====== h_config_auto_read_msgs_rules ===== ++ ++ ++FEATURE: auto-move-read-msgs-using-rules ++ ++ ++

      FEATURE: auto-move-read-msgs-using-rules

      ++This feature controls an aspect of Alpine's behavior upon quitting. If set, ++and the ++"" ++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 ++"" and mark ++them as deleted in the INBOX. Messages in the INBOX marked with an ++"N" (meaning New, or unseen) are not affected. ++

      ++

      ++<End of help on this topic> ++ ++ + ====== h_config_auto_fcc_only ===== + + +diff --git a/pith/reply.c b/pith/reply.c +index ccd4531..9da0022 100644 +--- a/pith/reply.c ++++ b/pith/reply.c +@@ -47,6 +47,8 @@ static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washingto + #include "../pith/mailcmd.h" + #include "../pith/margin.h" + #include "../pith/smime.h" ++#include "../pith/copyaddr.h" ++#include "../pith/rules.h" + + + /* +@@ -864,8 +866,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 */ +@@ -917,10 +938,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))); + } + + +@@ -1485,6 +1525,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; +@@ -1897,21 +1941,193 @@ 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'; ++ 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 iAltAddress: ++ if(ps_global->VAR_ALT_ADDRS != NULL ++ && ps_global->VAR_ALT_ADDRS[0] != NULL){ ++ size_t len; ++ int i, j; ++ ++ for(i = 0, len = 0; len < maxlen && ps_global->VAR_ALT_ADDRS[i]; i++){ ++ for(j = 0; len < maxlen && ps_global->VAR_ALT_ADDRS[i][j] != '\0'; j++){ ++ if(ps_global->VAR_ALT_ADDRS[i][j] == ';') ++ buf[len++] = '\\'; ++ buf[len++] = ps_global->VAR_ALT_ADDRS[i][j]; ++ } ++ if(len < maxlen){ ++ if(ps_global->VAR_ALT_ADDRS[i+1] != NULL) ++ buf[len++] = ';'; ++ else ++ buf[len++] = '\0'; ++ } ++ } ++ buf[maxlen] = '\0'; + } + break; ++ ++ case iNick: ++ case iFccFrom: ++ case iFccSender: ++ if (env){ ++ ADDRESS *tmp_adr; ++ ++ switch(type){ ++ case iNick: ++ tmp_adr = env->from ? copyaddr(env->from) ++ : env->sender ? copyaddr(env->sender) : NULL; ++ break; ++ case iFccFrom: ++ tmp_adr = env->from ? copyaddr(env->from) : NULL; ++ break; ++ case iFccSender: ++ tmp_adr = env->sender ? copyaddr(env->sender) : NULL; ++ break; ++ default: alpine_panic("Unhandled Rules case (01)"); ++ } ++ if(type == iNick) ++ get_nickname_from_addr(tmp_adr, buf, maxlen); ++ else ++ get_fcc_from_addr(tmp_adr, buf, maxlen); ++ mail_free_address(&tmp_adr); ++ } ++ break; ++ ++ case iAddressSender: ++ 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) ++ : (type == iAddressSender) ++ ? ((env && env->sender) ++ ? copyaddr(env->sender) ++ : 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)){ +@@ -1940,6 +2156,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); +@@ -1989,6 +2210,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: + case iShortSubject: + if(env && env->subject){ +@@ -2051,7 +2280,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)){ +@@ -2310,6 +2550,7 @@ forward_subject(ENVELOPE *env, int flags) + { + size_t l; + char *p, buftmp[MAILTMPLEN]; ++ RULE_RESULT *rule; + + if(!env) + return(NULL); +@@ -2317,9 +2558,19 @@ 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)){ ++ snprintf(buftmp, sizeof(buftmp), "%s", rule->result); ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ else if(env->subject) ++ snprintf(buftmp, sizeof(buftmp), "%s", 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) +diff --git a/pith/rules.c b/pith/rules.c +new file mode 100644 +index 0000000..3ab373e +--- /dev/null ++++ b/pith/rules.c +@@ -0,0 +1,1416 @@ ++/* This module was written by ++ * ++ * Eduardo Chappa (chappa@washington.edu) ++ * http://patches.freeiz.com/alpine/ ++ * ++ * Original Version: November 1999 ++ * Last Modified : September 14, 2013 ++ * ++ * Send bug reports about this module to the address above. ++ */ ++ ++#include "../pith/headers.h" ++#include "../pith/state.h" ++#include "../pith/conf.h" ++#include "../pith/copyaddr.h" ++#include "../pith/mailindx.h" ++#include "../pith/rules.h" ++ ++#define CSEP_C ('\001') ++#define CSEP_S ("\001") ++ ++/* Internal Prototypes */ ++ ++int test_condition (CONDITION_S *, int, ENVELOPE *); ++int test_in (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++int test_ni (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++int test_not_in (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++int test_not_ni (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++int test_eq (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++int test_not_eq (CONDITION_S *, TOKEN_VALUE *, ENVELOPE *, int); ++int isolate_condition (char *, char **, int *); ++int sanity_check_condition (char *); ++char *test_rule (RULELIST *, int, ENVELOPE *, int *); ++char *trim (RULEACTION_S *, int, ENVELOPE *); ++char *rextrim (RULEACTION_S *, int, ENVELOPE *); ++char *raw_value (RULEACTION_S *, int, ENVELOPE *); ++char *extended_value (RULEACTION_S *, int, ENVELOPE *); ++char *exec_fcn (RULEACTION_S *, int, ENVELOPE *); ++char *expand (char *, void *); ++char *get_name_token (char *); ++char *advance_to_char (char *, char, int, int *); ++char **functions_for_token (char *); ++char *canonicalize_condition (char *, int *); ++void free_token_value (TOKEN_VALUE **); ++void free_condition (CONDITION_S **); ++void free_condition_value (CONDVALUE_S **); ++void free_ruleaction (RULEACTION_S **); ++void free_rule (RULE_S **); ++void free_rule_list (RULELIST **); ++void free_alloc_rule (void **, int); ++void *alloc_mem (size_t); ++void add_rule (int, int); ++void set_rule_list (struct variable *); ++void parse_patterns_into_action(TOKEN_VALUE **); ++void free_parsed_value(TOKEN_VALUE **value); ++RULE_S *parse_rule (char *, int); ++RULELIST *get_rule_list (char **, int, int); ++TOKEN_VALUE *parse_group_data (char *,int *); ++TOKEN_VALUE *copy_parsed_value (TOKEN_VALUE *, int, ENVELOPE *); ++CONDVALUE_S *fill_condition_value (char *); ++CONDITION_S *fill_condition (char *); ++CONDITION_S *parse_condition (char *, int *); ++PRULELIST_S *add_prule (PRULELIST_S *, PRULELIST_S *); ++RULEACTION_S *parse_action (char *, int); ++ ++REL_TOKEN rel_rules_test[] = { ++ {EQ_REL, Equal, test_eq}, ++ {IN_REL, Subset, test_in}, ++ {NI_REL, Includes, test_ni}, ++ {NOT_EQ_REL, NotEqual, test_not_eq}, ++ {NOT_IN_REL, NotSubset, test_not_in}, ++ {NOT_NI_REL, NotIncludes, test_not_ni}, ++ {NULL, EndTypes, NULL} ++}; ++ ++#define NREL (sizeof(rel_rules_test)/sizeof(rel_rules_test[0]) - 1) ++ ++RULE_FCN rule_fcns[] = { ++{COPY_FCN, extended_value, FOR_SAVE|FOR_COMPOSE}, ++{SAVE_FCN, extended_value, FOR_SAVE}, ++{EXEC_FCN, exec_fcn, FOR_REPLACE|FOR_TRIM|FOR_RESUB|FOR_COMPOSE}, ++{REPLY_FCN, extended_value, FOR_REPLY_INTRO}, ++{TRIM_FCN, trim, FOR_TRIM|FOR_RESUB|FOR_COMPOSE}, ++{REPLACE_FCN, extended_value, FOR_REPLACE}, ++{SORT_FCN, raw_value, FOR_SORT}, ++{INDEX_FCN, raw_value, FOR_INDEX}, ++{COMMAND_FCN, raw_value, FOR_KEY}, ++{REPLYSTR_FCN, raw_value, FOR_COMPOSE}, ++{SIGNATURE_FCN, raw_value, FOR_COMPOSE}, ++{RESUB_FCN, extended_value, FOR_RESUB}, ++{STARTUP_FCN, raw_value, FOR_STARTUP}, ++{REXTRIM_FCN, rextrim, FOR_TRIM|FOR_RESUB|FOR_COMPOSE}, ++{THRDSTYLE_FCN, raw_value, FOR_THREAD}, ++{THRDINDEX_FCN, raw_value, FOR_THREAD}, ++{SMTP_FCN, raw_value, FOR_COMPOSE}, ++{NULL, 0, FOR_NOTHING} ++}; ++ ++char* token_rules[] = { ++ FROM_TOKEN, ++ NICK_TOKEN, ++ FCCF_TOKEN, ++ FCCS_TOKEN, ++ OTEXT_TOKEN, ++ OTEXTNQ_TOKEN, ++ ROLE_TOKEN, ++ FOLDER_TOKEN, ++ SUBJ_TOKEN, ++ PROCID_TOKEN, ++ THDDSPSTY_TOKEN, ++ THDNDXSTY_TOKEN, ++ FLAG_TOKEN, ++ COLLECT_TOKEN, ++ THDDSPSTY_TOKEN, ++ ADDR_TOKEN, ++ TO_TOKEN, ++ ADDTO_TOKEN, ++ ADDCC_TOKEN, ++ ADDRECIP_TOKEN, ++ SCREEN_TOKEN, ++ KEY_TOKEN, ++ SEND_TOKEN, ++ CC_TOKEN, ++ LCC_TOKEN, ++ BCC_TOKEN, ++ FFROM_TOKEN, ++ FADDRESS_TOKEN, ++ NULL ++}; ++ ++#define NTOKENS (sizeof(token_rules)/sizeof(token_rules[0]) - 1) ++#define NFCN (sizeof(rule_fcns)/sizeof(rule_fcns[0]) - 1) ++ ++char *subj_fcn[] = {SUBJ_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++char *from_fcn[] = {FROM_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++char *otext_fcn[] = {OTEXT_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++char *otextnq_fcn[] = {OTEXTNQ_TOKEN, REPLACE_FCN, TRIM_FCN, REXTRIM_FCN, EXEC_FCN}; ++ ++char *adto_fcn[] = {ADDTO_TOKEN, EXEC_FCN, NULL, NULL, NULL}; ++ ++char **fcns_for_index[] = {subj_fcn, from_fcn, otext_fcn, otextnq_fcn}; ++ ++#define NFCNFI (sizeof(fcns_for_index)/sizeof(fcns_for_index[0])) /*for idx*/ ++#define NFPT (sizeof(fcns_for_index[0])) /* functions pert token */ ++ ++SPAREP_S * ++get_sparep_for_rule(char *value, int flag) ++{ ++ SPAREP_S *rv; ++ rv = (SPAREP_S *) alloc_mem(sizeof(SPAREP_S)); ++ rv->flag = flag; ++ rv->value = value ? cpystr(value) : NULL; ++ return rv; ++} ++ ++void free_sparep_for_rule(void **sparep) ++{ ++ SPAREP_S *spare = (SPAREP_S *) *sparep; ++ if(!spare) return; ++ if(spare->value) ++ fs_give((void **)&spare->value); ++ fs_give((void **)sparep); ++} ++ ++ ++int context_for_function(char *name) ++{ ++ int i, j; ++ for (i = 0; i < NFCN && strcmp(rule_fcns[i].name, name); i++); ++ return i == NFCN ? 0 : rule_fcns[i].what_for; ++ ++} ++ ++char **functions_for_token(char *name) ++{ ++ int i; ++ for (i = 0; i < NFCNFI && strcmp(fcns_for_index[i][0], name); i++); ++ return i == NFCNFI ? NULL : fcns_for_index[i]; ++} ++ ++void ++free_alloc_rule (void **voidtext, int code) ++{ ++ switch(code){ ++ case FREEREGEX : regfree((regex_t *)*voidtext); ++ break; ++ default: break; ++ } ++} ++ ++ ++ ++void free_token_value(TOKEN_VALUE **token) ++{ ++ if(token && *token){ ++ if ((*token)->testxt) ++ fs_give((void **)&(*token)->testxt); ++ free_alloc_rule (&(*token)->voidtxt, (*token)->codefcn); ++ if((*token)->next) ++ free_token_value(&(*token)->next); ++ fs_give((void **)token); ++ } ++} ++ ++void ++free_condition_value(CONDVALUE_S **cvalue) ++{ ++ if(cvalue && *cvalue){ ++ if ((*cvalue)->tname) ++ fs_give((void **)&(*cvalue)->tname); ++ if ((*cvalue)->value) ++ free_token_value(&(*cvalue)->value); ++ fs_give((void **)cvalue); ++ } ++} ++ ++void free_condition(CONDITION_S **condition) ++{ ++ if(condition && *condition){ ++ if((*condition)->cndtype == Condition) ++ free_condition_value((CONDVALUE_S **)&(*condition)->cndrule); ++ else if((*condition)->cndtype == ParOpen || (*condition)->cndtype == ParClose) ++ fs_give(&(*condition)->cndrule); ++ if((*condition)->next) ++ free_condition(&((*condition)->next)); ++ fs_give((void **)condition); ++ } ++} ++ ++void free_ruleaction(RULEACTION_S **raction) ++{ ++ if(raction && *raction){ ++ if ((*raction)->token) ++ fs_give((void **)&((*raction)->token)); ++ if ((*raction)->function) ++ fs_give((void **)&((*raction)->function)); ++ if ((*raction)->value) ++ free_token_value(&(*raction)->value); ++ fs_give((void **)raction); ++ } ++} ++ ++void free_rule(RULE_S **rule) ++{ ++ if(rule && *rule){ ++ free_condition(&((*rule)->condition)); ++ free_ruleaction(&((*rule)->action)); ++ fs_give((void **)rule); ++ } ++} ++ ++void free_rule_list(RULELIST **rule) ++{ ++ if(!*rule) ++ return; ++ ++ if((*rule)->next) ++ free_rule_list(&((*rule)->next)); ++ ++ if((*rule)->prule) ++ free_rule(&((*rule)->prule)); ++ ++ fs_give((void **)rule); ++} ++ ++void ++free_parsed_rule_list(PRULELIST_S **rule) ++{ ++ if(!*rule) ++ return; ++ ++ if((*rule)->next) ++ free_parsed_rule_list(&((*rule)->next)); ++ ++ if((*rule)->rlist) ++ free_rule_list(&((*rule)->rlist)); ++ ++ fs_give((void **)rule); ++} ++ ++void * ++alloc_mem (size_t amount) ++{ ++ void *genmem; ++ memset(genmem = fs_get(amount), 0, amount); ++ return genmem; ++} ++ ++ ++void ++parse_patterns_into_action(TOKEN_VALUE **tokenp) ++{ ++ if(!*tokenp) ++ return; ++ ++ if((*tokenp)->testxt){ ++ regex_t preg; ++ ++ (*tokenp)->voidtxt = NULL; ++ (*tokenp)->voidtxt = fs_get(sizeof(regex_t)); ++ if (regcomp((regex_t *)(*tokenp)->voidtxt, ++ (*tokenp)->testxt, REG_EXTENDED) != 0){ ++ regfree((regex_t *)(*tokenp)->voidtxt); ++ (*tokenp)->voidtxt = NULL; ++ } ++ } ++ if((*tokenp)->voidtxt) ++ (*tokenp)->codefcn = FREEREGEX; ++ if((*tokenp)->next) ++ parse_patterns_into_action(&(*tokenp)->next); ++} ++ ++ ++int ++isolate_condition (char *data, char **cvalue, int *len) ++{ ++ char *p = data; ++ int done = 0, error = 0, next_condition = 0, l; ++ ++ if(*p == '"' && p[strlen(p) - 1] == '"'){ ++ p[strlen(p) - 1] = '\0'; ++ p++; ++ } ++ *cvalue = NULL; ++ while (*p && !done){ ++ switch (*p){ ++ case '_': *cvalue = advance_to_char(p,'}', STRICTLY, NULL); ++ if(*cvalue){ ++ strcat(*cvalue,"}"); ++ p += strlen(*cvalue); ++ } ++ else ++ error++; ++ done++; ++ case ' ': p++; ++ break; ++ case '&': ++ case '|': if (*(p+1) == *p){ /* looking for && or ||*/ ++ p += 2; ++ next_condition++; ++ } ++ else{ ++ error++; ++ done++; ++ } ++ break; ++ case '=': /* looking for => or -> */ ++ case '-': if (*(p+1) != '>' || next_condition) ++ error++; ++ done++; ++ break; ++ default : done++; ++ error++; ++ break; ++ } ++ } ++ *len = p - data; ++ return error ? -1 : (*cvalue ? 1 : 0); ++} ++ ++TOKEN_VALUE * ++parse_group_data (char *data, int *error) ++{ ++ TOKEN_VALUE *rvalue; ++ char *p, *d; ++ int offset, err = 0, freeme = 0; ++ ++ if(error) ++ *error = 0; ++ ++ if (!data) ++ return (TOKEN_VALUE *) NULL; ++ ++ if(*data == '_'){ ++ d = detoken_src(data, FOR_RULE, NULL, NULL, NULL, NULL); ++ freeme++; ++ } ++ else ++ d = data; ++ ++ rvalue = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ if (p = advance_to_char(d,';', STRICTLY, &offset)){ ++ rvalue->testxt = p; ++ rvalue->next = parse_group_data(d + strlen(p) + 1 + offset, error); ++ } ++ else if (p = advance_to_char(d,'}', STRICTLY, NULL)) ++ rvalue->testxt = p; ++ else if (d && *d == '}') ++ rvalue->testxt = cpystr(""); ++ else{ ++ err++; ++ free_token_value(&rvalue); ++ } ++ if (error) ++ *error += err; ++ if(freeme != 0 && d != NULL) ++ fs_give((void **)&d); ++ return(rvalue); ++} ++ ++CONDVALUE_S * ++fill_condition_value(char *data) ++{ ++ CONDVALUE_S *condition; ++ int i, done, error = 0; ++ char *group; ++ ++ for (i = 0, done = 0; done == 0 && token_rules[i] != NULL; i++) ++ done = strncmp(data,token_rules[i], strlen(token_rules[i])) ? 0 : 1; ++ if (done){ ++ condition = alloc_mem(sizeof(CONDVALUE_S)); ++ condition->tname = cpystr(token_rules[--i]); ++ data += strlen(token_rules[i]); ++ } ++ else if (*data == '_') { ++ char *itokname; ++ for (i = 0, done = 0; done == 0 && (itokname = itoken(i)->name) != NULL; i++) ++ done = strncmp(data+1, itokname, strlen(itokname)) ++ ? 0 : data[strlen(itokname) + 1] == '_'; ++ if (done){ ++ condition = (CONDVALUE_S *) alloc_mem(sizeof(CONDVALUE_S)); ++ condition->tname = fs_get(strlen(itokname) + 3); ++ sprintf(condition->tname, "_%s_", itokname); ++ data += strlen(itokname) + 2; ++ } ++ else ++ return NULL; ++ } ++ else ++ return NULL; ++ ++ for (; *data && *data == ' '; data++); ++ if (*data){ ++ for (i = 0, done = 0; done == 0 && rel_rules_test[i].value != NULL; i++) ++ done = strncmp(data, rel_rules_test[i].value, 2) ? 0 : 1; ++ if (done) ++ condition->ttype = rel_rules_test[--i].ttype; ++ else{ ++ free_condition_value(&condition); ++ return NULL; ++ } ++ } ++ else{ ++ free_condition_value(&condition); ++ return NULL; ++ } ++ ++ data += 2; ++ for (; *data && *data == ' '; data++); ++ if (*data++ != '{'){ ++ free_condition_value(&condition); ++ return NULL; ++ } ++ group = advance_to_char(data,'}', STRICTLY, &error); ++ if (group || (!group && error < 0)){ ++ condition->value = parse_group_data(data, &error); ++ if(group && error) ++ free_condition_value(&condition); ++ if(group) ++ fs_give((void **) &group); ++ } ++ else ++ free_condition_value(&condition); ++ return condition; ++} ++ ++char * ++canonicalize_condition(char *data, int *eoc) ++{ ++ char *p = data, *s, *t, c; ++ char *q = fs_get((5*strlen(data)+1)*sizeof(char)); ++ char tmp[10]; ++ int level, done, error, i; ++ ++ if(eoc) *eoc = -1; /* assume error */ ++ *q = '\0'; ++ if(*p == '"'){ ++ if(p[strlen(p) - 1] == '"') ++ p[strlen(p) - 1] = '\0'; ++ p++; ++ } ++ for(level = done = error = 0; *p && !done && !error; ){ ++ switch(*p){ ++ case ' ' : p++; break; ++ case '(' : strcat(q, CSEP_S); strcat(q, "("); ++ sprintf(tmp, "%d ", level++); ++ strcat(q, tmp); ++ p++; ++ break; ++ case ')' : strcat(q, CSEP_S); strcat(q, ")"); ++ sprintf(tmp, "%d ", --level); ++ strcat(q, tmp); ++ p++; ++ if(level < 0) error++; ++ break; ++ case '_' : for(s = p+1; *s >= 'A' && *s <= 'Z'; s++); ++ for(i = 0; token_rules[i] != NULL; i++) ++ if(!strncmp(token_rules[i], p, s-p)) ++ break; ++ if(token_rules[i] == NULL) ++ error++; ++ else if(*s++ == '_'){ ++ for(; *s == ' '; s++); ++ if(*s && *(s+1)){ ++ for(i = 0; rel_rules_test[i].value != NULL; i++) ++ if(!strncmp(rel_rules_test[i].value, s, 2)) ++ break; ++ if (rel_rules_test[i].value == NULL) ++ error++; ++ else{ ++ s += 2; ++ for(; *s == ' '; s++); ++ if(*s == '{'){ ++ if(*(s+1) != '}') ++ t = advance_to_char(s+1,'}', STRICTLY, NULL); ++ else ++ t = cpystr(""); ++ if(t != NULL){ ++ for(i = 0; t[i] != '\0' && t[i] != CSEP_C; i++); ++ if(t[i] == CSEP_C) error++; ++ if(error == 0){ ++ strcat(q, CSEP_S); strcat(q, "C["); ++ s += strlen(t) + 1; /* get past '{' */ ++ *s = '\0'; ++ strcat(q, p); ++ strcat(q, "}] "); ++ *s++ = '}'; ++ p = s; ++ } ++ fs_give((void **) &t); ++ } ++ else error++; ++ } ++ else ++ error++; ++ } ++ } ++ } ++ else error++; ++ break; ++ case '|': ++ case '&': if(*(p+1) = *p){ ++ strcat(q, CSEP_S); strcat(q, *p == '|' ? "OR " : "AND "); ++ p += 2; ++ } else error++; ++ break; ++ case '-': ++ case '=': if (*(p+1) == '>'){ ++ if(eoc) *eoc = p - data; ++ done++; ++ } ++ else ++ error++; ++ break; ++ default : error++; ++ break; ++ } ++ } ++ if(error || level > 0) /*simplistic approach by now */ ++ fs_give((void **)&q); ++ else ++ q[strlen(q)-1] = '\0'; ++ return q; ++} ++ ++/* for a canonical condition, return if it is constructed according ++ * to logical rules such as AND or OR between conditions, etc. We assume ++ * we already canonicalized data, or else this will not work. ++ */ ++int ++sanity_check_condition(char *data) ++{ ++ int i, error; ++ char *s, *t, *d; ++ ++ if(data == NULL || *data == '\0') /* no data in, no data out */ ++ return 0; ++ ++ d = fs_get((strlen(data)+1)*sizeof(char)); ++ for(s = data,i = 0; (t = strchr(s, CSEP_C))!= NULL && (d[i] = *(t+1)); s = t+1, i++); ++ d[i] = '\0'; ++ for(i = 0, error = 0; d[i] != '\0' && error == 0; i++){ ++ switch(d[i]){ ++ case 'C': if((d[i+1] != '\0' && (d[i+1] == '(' || d[i+1] == 'C')) ++ || (i == 0 && d[1] != 'A' && d[1] != 'O' && d[1] != '\0')) ++ error++; ++ break; ++ case ')': if(i == 0 || d[i+1] != '\0' && (d[i+1] == 'C' || d[i+1] == '(')) ++ error++; ++ break; ++ case '(': if(d[i+1] == '\0' || d[i+1] == ')' || d[i+1] == 'A' || d[i+1] == 'O') ++ error++; ++ break; ++ case 'O': ++ case 'A': if(i == 0 || d[i+1] == '\0' || d[i+1] == ')' || d[i+1] == 'A' || d[i+1] == 'O') ++ error++; ++ break; ++ default : error++; ++ } ++ } ++ if(d) fs_give((void **)&d); ++ return error ? 0 : 1; ++} ++ ++/* given a parsed data that satisfies sanity checks, parse it ++ * into a condition we can check later on. ++ */ ++CONDITION_S * ++fill_condition(char *data) ++{ ++ char *s, *t, *u; ++ CONDITION_S *rv = NULL; ++ CONDVALUE_S *cvalue; ++ int *i; ++ ++ if(data == NULL || *data == '\0' || (s = strchr(data, CSEP_C)) == NULL) ++ return NULL; ++ ++ rv = (CONDITION_S *) alloc_mem(sizeof(CONDITION_S)); ++ switch(*++s){ ++ case ')': ++ case '(': i = fs_get(sizeof(int)); ++ *i = atoi(s+1); ++ rv->cndrule = (void *) i; ++ rv->cndtype = *s == '(' ? ParOpen : ParClose; ++ break; ++ ++ case 'C': if((u = strchr(s+2, CSEP_C)) != NULL){ ++ *u = '\0'; ++ t = strrchr(s, ']'); ++ t = '\0'; ++ *u = CSEP_C; ++ } else ++ s[strlen(s) - 1] = '\0'; ++ rv->cndrule = (void *) fill_condition_value(s+2); ++ rv->cndtype = Condition; ++ break; ++ ++ case 'A': ++ case 'O': rv->cndtype = *s == 'A' ? And : Or; ++ break; ++ ++ default : fs_give((void **)&rv); ++ break; ++ } ++ rv->next = fill_condition(strchr(s, CSEP_C)); ++ ++ return rv; ++} ++ ++/* eoc = end of condition, equal to -1 on error */ ++CONDITION_S * ++parse_condition (char *data, int *eoc) ++{ ++ CONDITION_S *condition = NULL; ++ char *pvalue; ++ ++ if((pvalue = canonicalize_condition(data, eoc)) != NULL ++ && sanity_check_condition(pvalue) > 0) ++ condition = fill_condition(pvalue); ++ ++ if(pvalue) ++ fs_give((void **)&pvalue); ++ ++ if (condition == NULL && eoc) ++ *eoc = -1; ++ ++ return condition; ++} ++ ++RULEACTION_S * ++parse_action (char *data, int context) ++{ ++ int i, done, is_save; ++ RULEACTION_S *raction = NULL; ++ char *function, *p = data; ++ ++ if (p == NULL || *p == '\0') ++ return NULL; ++ ++ is_save = *p == '-'; ++ p += 2; ++ for (; *p == ' '; p++); ++ ++ if (is_save){ /* got "->", a save-rule separator */ ++ raction = (RULEACTION_S *) alloc_mem(sizeof(RULEACTION_S)); ++ raction->function = cpystr("_SAVE_"); ++ raction->value = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ raction->context |= FOR_SAVE; ++ raction->exec = extended_value; ++ raction->value->testxt = cpystr(p); ++ return raction; ++ } ++ for (i = 0, done = 0; !done && (i < NFCN); i++) ++ done = (strstr(p,rule_fcns[i].name) == p); ++ p += done ? strlen(rule_fcns[--i].name) + 1 : 0; ++ if(!*p || (rule_fcns[i].what_for && !(rule_fcns[i].what_for & context))) ++ return NULL; ++ if (done){ ++ raction = alloc_mem(sizeof(RULEACTION_S)); ++ /* We assign raction->token to be subject. This is not necessary for ++ most rules. It is done only for rules that need it and will not ++ make any difference in rules that do not need it. It will hopefully ++ reduce complexity in the language ++ */ ++ raction->token = cpystr(SUBJ_TOKEN); ++ raction->function = cpystr(rule_fcns[i].name); ++ raction->context = rule_fcns[i].what_for; ++ raction->exec = rule_fcns[i].execute; ++ raction->value = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ raction->value->testxt = advance_to_char(p,'}', STRICTLY, NULL); ++ if(!raction->value->testxt) ++ free_ruleaction(&raction); ++ return raction; ++ } ++ ++ done = (((function = strstr(p, "_TRIM_")) != NULL) ++ ? 1 : ((function = strstr(p, "_COPY_")) != NULL) ++ ? 2 : ((function = strstr(p, "_EXEC_")) != NULL) ++ ? 3 : ((function = strstr(p, "_REXTRIM_")) != NULL) ++ ? 4 : ((function = strstr(p, "_REPLACE_")) != NULL) ++ ? 5 : 0); ++ ++ if(!function) ++ return (RULEACTION_S *) NULL; ++ ++ *function = '\0'; ++ raction = (RULEACTION_S *) alloc_mem(sizeof(RULEACTION_S)); ++ raction->token = get_name_token(p); ++ *function = '_'; ++ p += strlen(raction->token) + 1; ++ for (; *p && *p == ' '; p++); ++ if (!strncmp(p, ":=", 2)) ++ p += 2; ++ else{ ++ free_ruleaction(&raction); ++ return NULL; ++ } ++ for (; *p && *p == ' '; p++); ++ if (p != function){ ++ free_ruleaction(&raction); ++ return NULL; ++ } ++ p += done <= 3 ? 6 : 9; /* 6 = strlen("_EXEC_"), 9 = strlen("_REPLACE_") */ ++ if (*p != '{'){ ++ free_ruleaction(&raction); ++ return NULL; ++ } ++ *p = '\0'; ++ for(i = 0; i < NFCN && strcmp(function, rule_fcns[i].name);i++); ++ raction->function = cpystr(function); ++ raction->is_trim = strcmp(function,"_TRIM_") ? 0 : 1; ++ raction->is_rextrim = strcmp(function,"_REXTRIM_") ? 0 : 1; ++ raction->is_replace = strcmp(function,"_REPLACE_") ? 0 : 1; ++ raction->context = rule_fcns[i].what_for; ++ raction->exec = rule_fcns[i].execute; ++ *p++ = '{'; ++ if((raction->value = parse_group_data(p, NULL)) == NULL ++ || raction->value->testxt == NULL) ++ free_ruleaction(&raction); ++ if(raction && raction->is_rextrim) ++ parse_patterns_into_action(&raction->value); ++ return raction; ++} ++ ++RULE_S * ++parse_rule (char *data, int context) ++{ ++ RULE_S *prule; /*parsed rule */ ++ int len = 0; ++ ++ if (!(prule = (RULE_S *) alloc_mem(sizeof(RULE_S))) || ++ !(prule->condition = parse_condition(data, &len)) || ++ !(prule->action = parse_action(data+len, context))) ++ free_rule(&prule); ++ ++ return prule; ++} ++ ++RULELIST * ++get_rule_list(char **list, int context, int i) ++{ ++ RULE_S *rule; ++ RULELIST *trulelist = NULL; ++ ++ if (list[i] && *list[i]){ ++ if(rule = parse_rule(list[i], context)){ ++ trulelist = (RULELIST *)alloc_mem(sizeof(RULELIST)); ++ trulelist->prule = rule; ++ trulelist->next = get_rule_list(list, context, i+1); ++ } ++ else ++ trulelist = get_rule_list(list, context, i+1); ++ } ++ return trulelist; ++} ++ ++PRULELIST_S * ++add_prule(PRULELIST_S *rule_list, PRULELIST_S *rule) ++{ ++ if (!rule_list) ++ rule_list = (PRULELIST_S *) alloc_mem(sizeof(PRULELIST_S)); ++ ++ if(rule_list->next) ++ rule_list->next = add_prule(rule_list->next, rule); ++ else{ ++ if (rule_list->rlist) ++ rule_list->next = rule; ++ else ++ rule_list = rule; ++ } ++ return rule_list; ++} ++ ++void ++add_rule(int code, int context) ++{ ++ char **list = ps_global->vars[code].current_val.l; ++ PRULELIST_S *prulelist, *trulelist, *orulelist; ++ ++ if (list && *list && **list){ ++ trulelist = (PRULELIST_S *)alloc_mem(sizeof(PRULELIST_S)); ++ trulelist->varnum = code; ++ if (trulelist->rlist = get_rule_list(list, context, 0)) ++ ps_global->rule_list = add_prule(ps_global->rule_list, trulelist); ++ else ++ free_parsed_rule_list(&trulelist); ++ } ++} ++ ++/* see create_rule_list below */ ++void ++set_rule_list(struct variable *vars) ++{ ++ set_current_val(&vars[V_THREAD_DISP_STYLE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_THREAD_INDEX_STYLE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_COMPOSE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_FORWARD_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_INDEX_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_KEY_RULES], FALSE, TRUE); ++ set_current_val(&vars[V_REPLACE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_REPLY_INDENT_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_REPLY_LEADIN_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_RESUB_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_SAVE_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_SMTP_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_SORT_RULES], TRUE, TRUE); ++ set_current_val(&vars[V_STARTUP_RULES], TRUE, TRUE); ++} ++ ++/* see set_rule_list above */ ++void ++create_rule_list(struct variable *vars) ++{ ++ set_rule_list(vars); ++ add_rule(V_THREAD_DISP_STYLE_RULES, FOR_THREAD); ++ add_rule(V_THREAD_INDEX_STYLE_RULES, FOR_THREAD); ++ add_rule(V_COMPOSE_RULES, FOR_COMPOSE); ++ add_rule(V_FORWARD_RULES, FOR_COMPOSE); ++ add_rule(V_INDEX_RULES, FOR_INDEX); ++ add_rule(V_KEY_RULES, FOR_KEY); ++ add_rule(V_REPLACE_RULES, FOR_REPLACE); ++ add_rule(V_REPLY_INDENT_RULES, FOR_COMPOSE); ++ add_rule(V_REPLY_LEADIN_RULES, FOR_REPLY_INTRO); ++ add_rule(V_RESUB_RULES, FOR_RESUB|FOR_TRIM); ++ add_rule(V_SAVE_RULES, FOR_SAVE); ++ add_rule(V_SMTP_RULES, FOR_COMPOSE); ++ add_rule(V_SORT_RULES, FOR_SORT); ++ add_rule(V_STARTUP_RULES, FOR_STARTUP); ++} ++ ++int ++condition_contains_token(CONDITION_S *condition, char *token) ++{ ++ while(condition && condition->cndtype != Condition) ++ condition = condition->next; ++ ++ return condition ++ ? (!strcmp(COND(condition)->tname, token) ++ ? 1 ++ : condition_contains_token(condition->next, token)) ++ : 0; ++} ++ ++RULELIST * ++get_rulelist_from_code(int code, PRULELIST_S *list) ++{ ++ return list ? (list->varnum == code ? list->rlist ++ : get_rulelist_from_code(code, list->next)) ++ : (RULELIST *) NULL; ++} ++ ++char * ++test_rule(RULELIST *rlist, int ctxt, ENVELOPE *env, int *n) ++{ ++ char *result; ++ ++ if(!rlist) ++ return NULL; ++ ++ if (result = process_rule(rlist->prule, ctxt, env)) ++ return result; ++ else{ ++ (*n)++; ++ return test_rule(rlist->next, ctxt, env, n); ++ } ++} ++ ++RULE_S * ++get_rule (RULELIST *rule, int n) ++{ ++ return rule ? (n ? get_rule(rule->next, n-1) : rule->prule) ++ : NULL; ++} ++ ++/* get_result_rule: ++ * Parameters: list: the list of rules to be passed to the function to check ++ * rule_context: context of the rule ++ * env : envelope used to check the rule, if needed. ++ * ++ * Returns: The value of the first rule that is satisfied in the list, or ++ * NULL if not. This function should be called in the following ++ * way (notice that memory is freed by caller). ++ * ++ * You should use this function to obtain the result of a rule. You can ++ * also call directly "process_rule", but I advice to use this function if ++ * there's no difference on which function to call. ++ ++ RULE_RESULT *rule; ++ ++ rule = (RULE_RESULT *) ++ get_result_rule(V_SOME_RULE, context, envelope); ++ ++ if (rule){ ++ assign the value of rule->result; ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ */ ++ ++RULE_RESULT * ++get_result_rule(int code, int rule_context, ENVELOPE *env) ++{ ++ char *rule_result; ++ RULE_RESULT *rule = NULL; ++ RULELIST *rlist; ++ int n = 0; ++ ++ if(!(rule_context & FOR_RULE)) ++ rule_context |= FOR_RULE; ++ rlist = get_rulelist_from_code(code, ps_global->rule_list); ++ if (rlist){ ++ rule_result = test_rule(rlist, rule_context, env, &n); ++ if (rule_result && *rule_result){ ++ rule = (RULE_RESULT *) fs_get (sizeof(RULE_RESULT)); ++ rule->result = rule_result; ++ rule->number = n; ++ } ++ } ++ return rule; ++} ++ ++char *get_rule_result(int rule_context, char *newfolder, int code) ++{ ++ char *rule_result = NULL; ++ ENVELOPE *news_envelope; ++ RULE_RESULT *rule; ++ ++ if (IS_NEWS(ps_global->mail_stream)){ ++ news_envelope = mail_newenvelope(); ++ news_envelope->newsgroups = cpystr(newfolder); ++ } ++ else ++ news_envelope = NULL; ++ ++ rule = get_result_rule(code, rule_context, news_envelope); ++ ++ if (news_envelope) ++ mail_free_envelope (&news_envelope); ++ ++ if (rule){ ++ rule_result = cpystr(rule->result); ++ if (rule->result) ++ fs_give((void **)&rule->result); ++ fs_give((void **)&rule); ++ } ++ return rule_result; ++} ++ ++/* process_rule: ++ Parameters: prule, a processed rule, ready to be tested ++ rule_context: context of the rule, and ++ env: An envelope if needed. ++ ++ Returns : The value of the processed rule_data if the processing was ++ successful and matches context and possibly the envelope, or ++ NULL if there's no match ++ */ ++ ++char * ++process_rule (RULE_S *prule, int rule_context, ENVELOPE *env) ++{ ++ if(!prule) ++ return NULL; ++ ++ if(!(rule_context & FOR_RULE)) ++ rule_context |= FOR_RULE; ++ ++ return test_condition(prule->condition, rule_context, env) ++ ? (prule->action->exec)(prule->action, rule_context, env) ++ : NULL; ++} ++ ++TOKEN_VALUE * ++copy_parsed_value(TOKEN_VALUE *value, int ctxt, ENVELOPE *env) ++{ ++ TOKEN_VALUE *tval = NULL; ++ ++ if(!value) ++ return NULL; ++ ++ if(value->testxt){ ++ tval = (TOKEN_VALUE *) alloc_mem(sizeof(TOKEN_VALUE)); ++ tval->testxt = detoken_src(value->testxt, ctxt, env, NULL, NULL, NULL); ++ tval->voidtxt = value->voidtxt; ++ tval->codefcn = value->codefcn; ++ } ++ if(value->next) ++ tval->next = copy_parsed_value(value->next, ctxt, env); ++ ++ return tval; ++} ++ ++void ++free_parsed_value(TOKEN_VALUE **value) ++{ ++ TOKEN_VALUE *tval = NULL; ++ ++ if(!*value) ++ return; ++ ++ if((*value)->testxt) ++ fs_give((void **)&(*value)->testxt); ++ ++ if((*value)->next) ++ free_parsed_value(&(*value)->next); ++ ++ fs_give((void **)value); ++} ++ ++int ++test_condition_work(CONDITION_S *bc, CONDITION_S *ec, int rcntxt, ENVELOPE *env) ++{ ++ int rv,level; ++ TOKEN_VALUE *group; ++ CONDITION_S *cend; ++ ++ switch(bc->cndtype){ ++ case Condition: group = copy_parsed_value(COND(bc)->value, rcntxt, env); ++ rv = (*rel_rules_test[COND(bc)->ttype].execute)(bc, group, env, rcntxt); ++ free_parsed_value(&group); ++ if(bc == ec) ++ return rv; ++ if(bc->next == NULL) ++ return rv; ++ else ++ switch(bc->next->cndtype){ ++ case And: return rv ? test_condition_work(bc->next->next, ec, rcntxt, env) : 0; ++ break; ++ case Or : return rv ? 1 : test_condition_work(bc->next->next, ec, rcntxt, env); ++ break; ++ case ')': return rv; ++ default : rv = 0; break; /* fail, we should not be here */ ++ } ++ break; ++ ++ case ParOpen: level = ((int *)bc->cndrule)[0]; ++ for(cend = bc; cend->next && (cend->next->cndtype != ParClose ++ || ((int *)cend->next->cndrule)[0] != level); ++ cend = cend->next); ++ rv = test_condition_work(bc->next, cend, rcntxt, env); ++ cend = cend->next; /* here we are at ')' */ ++ if(cend->next == NULL) ++ return rv; ++ else{ ++ switch(cend->next->cndtype){ ++ case And: return rv ? test_condition_work(cend->next->next, ec, rcntxt, env) : 0; ++ break; ++ case Or : return rv ? 1 : test_condition_work(cend->next->next, ec, rcntxt, env); ++ break; ++ default : rv = 0; break; /* fail, we should not be here */ ++ } ++ } ++ break; ++ default: rv = 0; break; /* fail, we should not be here */ ++ } ++ return rv; /* we never ever get here */ ++} ++ ++ ++int ++test_condition(CONDITION_S *condition, int rcntxt, ENVELOPE *env) ++{ ++ return test_condition_work(condition, NULL, rcntxt, env); ++} ++ ++/* returns the name of the token it found or NULL if there is no token, the ++ * real value of the token is obtained by calling the detoken_src function. ++ */ ++ ++char * ++get_name_token (char *condition) ++{ ++ char *p = NULL, *q, *s; ++ ++ if ((q = strchr(condition,'_')) && (s = strchr(q+1,'_'))){ ++ char c = *++s; ++ *s = '\0'; ++ p = cpystr(q); ++ *s = c; ++ } ++ return p; ++} ++ ++/* This function tests if a string contained in the variable "group" is ++ * in the "condition" ++ */ ++int test_in (CONDITION_S *condition, TOKEN_VALUE *group, ENVELOPE *env, ++ int context) ++{ ++ int rv = 0; ++ char *test; ++ TOKEN_VALUE *test_group = group; ++ ++ test = env && env->sparep && ((SPAREP_S *)env->sparep)->flag & USE_RAW_SP ++ ? cpystr(((SPAREP_S *)env->sparep)->value) ++ : detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL); ++ if (test){ ++ while (rv == 0 && test_group){ ++ if(!*test || strstr(test_group->testxt, test)) ++ rv++; ++ else ++ test_group = test_group->next; ++ } ++ fs_give((void **)&test); ++ } ++ return rv; ++} ++ ++int test_ni (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++{ ++ int rv = 0; ++ char *test; ++ TOKEN_VALUE *test_group = group; ++ ++ test = env && env->sparep && ((SPAREP_S *)env->sparep)->flag & USE_RAW_SP ++ ? cpystr(((SPAREP_S *)env->sparep)->value) ++ : detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL); ++ if (test){ ++ if(!test_group) ++ rv++; ++ while (rv == 0 && test_group){ ++ if(!*test_group->testxt || strstr(test, test_group->testxt)) ++ rv++; ++ else ++ test_group = test_group->next; ++ } ++ fs_give((void **)&test); ++ } ++ return rv; ++} ++ ++int test_not_in (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++{ ++ return !test_in(condition, group, env, context); ++} ++ ++int test_not_ni (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++{ ++ return !test_ni(condition, group, env, context); ++} ++ ++int test_eq (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++{ ++ int rv = 0; ++ char *test; ++ TOKEN_VALUE *test_group = group; ++ ++ test = env && env->sparep && ((SPAREP_S *)env->sparep)->flag & USE_RAW_SP ++ ? cpystr(((SPAREP_S *)env->sparep)->value) ++ : detoken_src(COND(condition)->tname, context, env, NULL, NULL, NULL); ++ if (test){ ++ while (rv == 0 && test_group){ ++ if((!*test && !*test_group->testxt) || !strcmp(test_group->testxt, test)) ++ rv++; ++ else ++ test_group = test_group->next; ++ } ++ fs_give((void **)&test); ++ } ++ return rv; ++} ++ ++int test_not_eq (CONDITION_S *condition, TOKEN_VALUE *group, ++ ENVELOPE *env, int context) ++{ ++ return !test_eq(condition, group, env, context); ++} ++ ++char * ++do_trim (char *test, TOKEN_VALUE *tval) ++{ ++ char *begin_text; ++ int offset = 0; ++ ++ if (!tval) ++ return test; ++ ++ while(begin_text = strstr(test+offset,tval->testxt)){ ++ memmove(begin_text, begin_text+strlen(tval->testxt), strlen(begin_text) - strlen(tval->testxt)); ++ offset = begin_text - test; ++ } ++ ++ return do_trim(test, tval->next); ++} ++ ++char * ++trim (RULEACTION_S *action, int context, ENVELOPE *env) ++{ ++ char *begin_text, *test; ++ RULEACTION_S *taction = action; ++ int offset; ++ ++ if (taction->context & context){ ++ if (test = detoken_src(taction->token, context, env, NULL, NULL, NULL)) ++ test = do_trim(test, taction->value); ++ return test; ++ } ++ return NULL; ++} ++ ++ ++char * ++do_rextrim (char *test, TOKEN_VALUE *tval) ++{ ++ char *begin_text, *trim_text; ++ int offset = 0; ++ ++ if (!tval) ++ return test; ++ ++ trim_text = expand(test, tval->voidtxt); ++ while(trim_text && (begin_text = strstr(test+offset,trim_text))){ ++ strcpy(begin_text, begin_text+strlen(trim_text)); ++ offset = begin_text - test; ++ } ++ ++ return do_rextrim(test, tval->next); ++} ++ ++char * ++rextrim (RULEACTION_S *action, int context, ENVELOPE *env) ++{ ++ char *test = NULL; ++ RULEACTION_S *taction = action; ++ ++ if (taction->context & context && ++ (test = detoken_src(taction->token, context, env, NULL, NULL, NULL))) ++ test = do_rextrim(test, taction->value); ++ return test; ++} ++ ++char * ++raw_value (RULEACTION_S *action, int context, ENVELOPE *env) ++{ ++return (action->context & context) ? cpystr(action->value->testxt) : NULL; ++} ++ ++char * ++extended_value (RULEACTION_S *action, int ctxt, ENVELOPE *env) ++{ ++return (action->context & ctxt) ++ ? detoken_src(action->value->testxt, ctxt, env, NULL, NULL, NULL) ++ : NULL; ++} ++ ++/* advances given_string until it finds given_char, memory freed by caller */ ++char * ++advance_to_char(char *given_string, char given_char, int flag, int *error) ++{ ++ char *b, *s, c; ++ int i, err = 0, quoted ; ++ ++ if (error) ++ *error = 0; ++ ++ if (!given_string || !*given_string) ++ return NULL; ++ ++ b = s = cpystr(given_string); ++ for(i = 0, quoted = 0, c = *s; c ; c = *++s){ ++ if(c == '\\'){ ++ quoted++; ++ continue; ++ } ++ if(quoted){ ++ quoted = 0; ++ if (c == given_char){ ++ err += flag & STRICTLY ? 0 : 1; ++ err++; ++ break; ++ } ++ b[i++] = '\\'; ++ } ++ if(c == given_char){ ++ err += flag & STRICTLY ? 0 : 1; ++ break; ++ } ++ b[i++] = c; ++ } ++ b[i] = '\0'; ++ if (b && (strlen(b) == strlen(given_string)) && (flag & STRICTLY)){ ++ fs_give((void **)&b); ++ return NULL; /* character not found */ ++ } ++ ++ if(b && !*b){ ++ fs_give((void **)&b); ++ err = -1; ++ } ++ ++ if (error) ++ *error = err; ++ ++ return b; ++} ++ ++/* Regular Expressions Support */ ++char * ++expand (char *string, void *pattern) ++{ ++ char c, *ret_string = NULL; ++ regmatch_t pmatch; ++ ++ if((regex_t *)pattern == NULL) ++ return NULL; ++ ++ if(regexec((regex_t *)pattern, string , 1, &pmatch, 0) == 0 ++ && pmatch.rm_so < pmatch.rm_eo){ ++ c = string[pmatch.rm_eo]; ++ string[pmatch.rm_eo] = '\0'; ++ ret_string = cpystr(string+pmatch.rm_so); ++ string[pmatch.rm_eo] = c; ++ } ++ return ret_string; ++} ++ ++ ++char * ++exec_fcn (RULEACTION_S *action, int ctxt, ENVELOPE *env) ++{ ++ STORE_S *output_so; ++ gf_io_t gc, pc; ++ char *status, *rv, *cmd, *test; ++ ++ if(!(action->context & ctxt)) ++ return NULL; ++ ++ if((test = detoken_src(action->token, ctxt, env, NULL, NULL, NULL)) != NULL) ++ gf_set_readc(&gc, test, (unsigned long)strlen(test), CharStar, 0); ++ ++ if((output_so = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL) ++ gf_set_so_writec(&pc, output_so); ++ ++ cmd = (char *)fs_get((strlen(action->value->testxt) + strlen("_TMPFILE_") + 2)*sizeof(char)); ++ sprintf(cmd,"%s _TMPFILE_", action->value->testxt); ++ status = (*ps_global->tools.exec_rule)(cmd, gc, pc); ++ ++ so_seek(output_so, 0L, 0); ++ rv = cpystr(output_so->dp); ++ gf_clear_so_writec(output_so); ++ so_give(&output_so); ++ if(test) ++ fs_give((void **)&test); ++ ++ return status ? NULL : rv; ++} ++ ++ENVELOPE * ++rules_fetchenvelope(INDEXDATA_S *idata, int *we_clear) ++{ ++ ENVELOPE *env; ++ ++ if (idata->no_fetch){ ++ if(we_clear) ++ *we_clear = 1; ++ env = mail_newenvelope(); ++ env->from = copyaddrlist(idata->from); ++ env->to = copyaddrlist(idata->to); ++ env->cc = copyaddrlist(idata->cc); ++ env->sender = copyaddrlist(idata->sender); ++ env->subject = cpystr(idata->subject); ++ env->date = cpystr((unsigned char *) idata->date); ++ env->newsgroups = cpystr(idata->newsgroups); ++ return env; ++ } ++ if(we_clear) ++ *we_clear = 0; ++ env = pine_mail_fetchenvelope(idata->stream, idata->rawno); ++ return env; ++} +diff --git a/pith/rules.h b/pith/rules.h +new file mode 100644 +index 0000000..39fda49 +--- /dev/null ++++ b/pith/rules.h +@@ -0,0 +1,151 @@ ++/* Included file rules.h */ ++ ++#ifndef PITH_RULES_INCLUDED ++#define PITH_RULES_INCLUDED ++ ++#include "../pith/conftype.h" ++#include "../pith/detoken.h" ++#include "../pith/indxtype.h" ++#include "../pith/rulestype.h" ++ ++/* Exported prototypes */ ++ ++void create_rule_list (struct variable *); ++SPAREP_S *get_sparep_for_rule(char *, int); ++void free_sparep_for_rule(void **); ++void free_parsed_rule_list (PRULELIST_S **); ++RULE_RESULT *get_result_rule (int, int, ENVELOPE *); ++char *get_rule_result (int , char *, int); ++char *process_rule (RULE_S *, int, ENVELOPE *); ++char **functions_for_token (char *); ++RULELIST *get_rulelist_from_code (int, PRULELIST_S *); ++RULE_S *get_rule (RULELIST *, int); ++int condition_contains_token (CONDITION_S *, char *); ++int context_for_function (char *); ++ENVELOPE *rules_fetchenvelope(INDEXDATA_S *idata, int *we_clear); ++ ++/* Separators: ++ * ++ * A separator is a string that separates the rule condition with the rule ++ * action. Below is the list of separators ++ * ++ */ ++ ++#define SAVE_TO_SEP "->" ++#define APPLY_SEP "=>" ++ ++/*------- Definitions of tokens -------*/ ++/*------ Keep the list alphabetically sorted, thanks -------*/ ++ ++#define ADDR_TOKEN "_ADDRESS_" ++#define ADDCC_TOKEN "_ADDRESSCC_" ++#define ADDRECIP_TOKEN "_ADDRESSRECIPS_" ++#define ADDTO_TOKEN "_ADDRESSTO_" ++#define BCC_TOKEN "_BCC_" ++#define CC_TOKEN "_CC_" ++#define COLLECT_TOKEN "_COLLECTION_" ++#define FCCF_TOKEN "_FCCFROM_" ++#define FCCS_TOKEN "_FCCSENDER_" ++#define FLAG_TOKEN "_FLAG_" ++#define FOLDER_TOKEN "_FOLDER_" ++#define FADDRESS_TOKEN "_FORWARDADDRESS_" ++#define FFROM_TOKEN "_FORWARDFROM_" ++#define FROM_TOKEN "_FROM_" ++#define KEY_TOKEN "_PKEY_" ++#define LCC_TOKEN "_LCC_" ++#define NICK_TOKEN "_NICK_" ++#define OTEXT_TOKEN "_OPENINGTEXT_" ++#define OTEXTNQ_TOKEN "_OPENINGTEXTNQ_" ++#define PROCID_TOKEN "_PROCID_" ++#define ROLE_TOKEN "_ROLE_" ++#define SCREEN_TOKEN "_SCREEN_" ++#define SEND_TOKEN "_SENDER_" ++#define SUBJ_TOKEN "_SUBJECT_" ++#define THDDSPSTY_TOKEN "_THREADSTYLE_" ++#define THDNDXSTY_TOKEN "_THREADINDEX_" ++#define TO_TOKEN "_TO_" ++ ++/*------ Definitions of relational operands -------------*/ ++ ++typedef struct { ++ char *value; ++ TestType ttype; ++ int (*execute)(); ++} REL_TOKEN; ++ ++/* Relational Operands */ ++#define AND_REL "&&" /* For putting more than one condition */ ++#define IN_REL "<<" /* For belonging relation */ ++#define NI_REL ">>" /* For contain relation */ ++#define NOT_IN_REL "!<" /* Negation of IN_REL */ ++#define NOT_NI_REL "!>" /* Negation of NI_REL */ ++#define EQ_REL "==" /* Test of equality */ ++#define NOT_EQ_REL "!=" /* Test of inequality */ ++#define OPEN_SET "{" /* Braces to open a set */ ++#define CLOSE_SET "}" /* Braces to close a set*/ ++ ++/*--- Context in which these variables can be used ---*/ ++ ++typedef struct use_context { ++ char *name; ++ int what_for; ++} USE_IN_CONTEXT; ++ ++ ++static USE_IN_CONTEXT tokens_use[] = { ++ {NICK_TOKEN, FOR_SAVE}, ++ {FROM_TOKEN, FOR_SAVE}, ++ {OTEXT_TOKEN, FOR_SAVE|FOR_FOLDER}, ++ {OTEXTNQ_TOKEN, FOR_SAVE|FOR_FOLDER}, ++ {ROLE_TOKEN, FOR_COMPOSE}, ++ {FOLDER_TOKEN, FOR_SAVE|FOR_FOLDER|FOR_THREAD|FOR_COMPOSE}, ++ {SUBJ_TOKEN, FOR_SAVE|FOR_FOLDER|FOR_COMPOSE}, ++ {FLAG_TOKEN, FOR_SAVE|FOR_FLAG}, ++ {COLLECT_TOKEN, FOR_SAVE|FOR_COMPOSE|FOR_FOLDER|FOR_THREAD}, ++ {THDDSPSTY_TOKEN, FOR_THREAD}, ++ {THDNDXSTY_TOKEN, FOR_THREAD}, ++ {ADDR_TOKEN, FOR_SAVE|FOR_FOLDER}, ++ {TO_TOKEN, FOR_SAVE}, ++ {ADDTO_TOKEN, FOR_SAVE|FOR_COMPOSE}, ++ {ADDCC_TOKEN, FOR_SAVE|FOR_COMPOSE}, ++ {ADDRECIP_TOKEN, FOR_SAVE|FOR_COMPOSE}, ++ {SCREEN_TOKEN, FOR_KEY}, ++ {KEY_TOKEN, FOR_KEY}, ++ {SEND_TOKEN, FOR_SAVE}, ++ {CC_TOKEN, FOR_SAVE}, ++ {BCC_TOKEN, FOR_COMPOSE}, ++ {LCC_TOKEN, FOR_COMPOSE}, ++ {FFROM_TOKEN, FOR_COMPOSE}, ++ {FADDRESS_TOKEN, FOR_COMPOSE}, ++ {NULL, FOR_NOTHING} ++}; ++ ++ ++typedef struct { ++ char *name; ++ char* (*execute)(); ++ int what_for; ++} RULE_FCN; ++ ++#define COMMAND_FCN "_COMMAND_" ++#define COPY_FCN "_COPY_" ++#define EXEC_FCN "_EXEC_" ++#define INDEX_FCN "_INDEX_" ++#define REPLACE_FCN "_REPLACE_" ++#define REPLYSTR_FCN "_RESTR_" ++#define REPLY_FCN "_REPLY_" ++#define RESUB_FCN "_RESUB_" ++#define REXTRIM_FCN "_REXTRIM_" ++#define SAVE_FCN "_SAVE_" ++#define SIGNATURE_FCN "_SIGNATURE_" ++#define SMTP_FCN "_SMTP_" ++#define SORT_FCN "_SORT_" ++#define STARTUP_FCN "_STARTUP_" ++#define THRDSTYLE_FCN "_THREADSTYLE_" ++#define THRDINDEX_FCN "_THREADINDEX_" ++#define TRIM_FCN "_TRIM_" ++ ++#define STRICTLY 0x1 ++#define RELAXED 0x2 ++ ++#endif /* PITH_RULES_INCLUDED */ +diff --git a/pith/rulestype.h b/pith/rulestype.h +new file mode 100644 +index 0000000..8e7ddd9 +--- /dev/null ++++ b/pith/rulestype.h +@@ -0,0 +1,85 @@ ++#ifndef PITH_RULESTYPE_INCLUDED ++#define PITH_RULESTYPE_INCLUDED ++ ++typedef struct rule { ++ char *result; /* The result of the rule */ ++ int number; /* The number of the rule that succeded, -1 if not */ ++} RULE_RESULT; ++ ++typedef struct { ++ char *value; ++ int type; ++} RULE_ACTION; ++ ++ ++#define TOKEN_VALUE struct tokenvalue_s ++#define CONDITION_S struct condition_s ++#define RULEACTION_S struct ruleaction_s ++#define RULE_S struct rule_s ++#define RULELIST struct rulelist_s ++#define PRULELIST_S struct parsedrulelist_s ++ ++#define FREEREGEX 1 ++ ++TOKEN_VALUE { ++ char *testxt; ++ void *voidtxt; ++ int codefcn; ++ TOKEN_VALUE *next; ++}; ++ ++typedef enum {Equal, Subset, Includes, NotEqual, NotSubset, NotIncludes, EndTypes} TestType; ++ ++typedef enum {And, Or, ParOpen, ParClose, Condition} CondType; ++ ++typedef struct condvalue_s { ++ char *tname; /* tname ttype {value} */ ++ TestType ttype; /* type of rule */ ++ TOKEN_VALUE *value; /* value to check against */ ++} CONDVALUE_S; ++ ++CONDITION_S { ++ void *cndrule; /* text in condition */ ++ CondType cndtype; /* type of object */ ++ CONDITION_S *next; /* next condition to test */ ++}; ++ ++#define COND(C) ((CONDVALUE_S *)((C)->cndrule)) ++ ++RULEACTION_S { ++ char *token; /* token := function{value} or token = null */ ++ char *function; /* token := function{value} or simply function{value}*/ ++ TOKEN_VALUE *value; /* token := function{value} or simply function{value}*/ ++ int context; /* context in which this rule can be used */ ++ char* (*exec)(); ++ unsigned int is_trim:1; ++ unsigned int is_rextrim:1; ++ unsigned int is_replace:1; ++}; ++ ++RULE_S { ++ CONDITION_S *condition; ++ RULEACTION_S *action; ++}; ++ ++RULELIST { ++ RULE_S *prule; ++ RULELIST *next; ++}; ++ ++PRULELIST_S { ++ int varnum; /* number associated to the variable */ ++ RULELIST *rlist; ++ PRULELIST_S *next; ++}; ++ ++#define USE_RAW_SP 0x001 ++#define PROCESS_SP 0x010 ++ ++typedef struct sparep { ++ int flag; ++ char *value; ++} SPAREP_S; ++ ++ ++#endif /* PITH_RULESTYPE_INCLUDED */ +diff --git a/pith/save.c b/pith/save.c +index 77aa0fb..106241f 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) +diff --git a/pith/send.c b/pith/send.c +index 48d9344..27fb54e 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" +@@ -1734,9 +1735,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; +@@ -1891,20 +1892,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]; +diff --git a/pith/sort.c b/pith/sort.c +index 0de2824..b53680c 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 +@@ -686,7 +686,21 @@ 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; +@@ -704,7 +718,43 @@ reset_sort_order(unsigned int flags) + sort_is_rev = pat->action->revsort; + } + } +- ++ } + sort_folder(ps_global->mail_stream, ps_global->msgmap, + the_sort_order, sort_is_rev, flags); + } ++ ++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 af65825..d72f606 100644 +--- a/pith/sort.h ++++ b/pith/sort.h +@@ -45,6 +45,6 @@ char *sort_name(SortOrder); + void sort_folder(MAILSTREAM *, MSGNO_S *, SortOrder, int, unsigned); + int decode_sort(char *, SortOrder *, 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 837ed6f..22f0466 100644 +--- a/pith/state.c ++++ b/pith/state.c +@@ -35,6 +35,7 @@ static char rcsid[] = "$Id: state.c 1074 2008-06-04 00:08:43Z hubert@u.washingto + #include "../pith/smime.h" + #include "../pith/ical.h" + #include "../pith/bldaddr.h" ++#include "../pith/rules.h" + + /* + * Globals referenced throughout pine... +@@ -243,6 +244,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 cae43d4..e3c0264 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]; +@@ -352,6 +357,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; +@@ -362,6 +368,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/string.c b/pith/string.c +index 8707485..bd5a80f 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 +@@ -131,6 +132,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'; ++} + + + /*---------------------------------------------------------------------- +@@ -2997,3 +3023,35 @@ convert_decimal_to_alpha (char *rn, size_t len, long n, char l) + if(i < len) rn[i] = '\0'; + rn[len-1] = '\0'; + } ++ ++ ++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 4263249..206b485 100644 +--- a/pith/string.h ++++ b/pith/string.h +@@ -87,6 +87,7 @@ 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 *); +@@ -94,6 +95,7 @@ void removing_trailing_white_space(char *); + void replace_tabs_by_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 *); -- cgit v1.2.3-70-g09d2