summaryrefslogtreecommitdiff
path: root/manualPorts
diff options
context:
space:
mode:
Diffstat (limited to 'manualPorts')
-rw-r--r--manualPorts/alpine/.md5sum1
-rw-r--r--manualPorts/alpine/.signature5
-rw-r--r--manualPorts/alpine/Pkgfile4
-rw-r--r--manualPorts/alpine/rules.patch5676
4 files changed, 5683 insertions, 3 deletions
diff --git a/manualPorts/alpine/.md5sum b/manualPorts/alpine/.md5sum
index 8229bad..35615c6 100644
--- a/manualPorts/alpine/.md5sum
+++ b/manualPorts/alpine/.md5sum
@@ -1,2 +1,3 @@
5c071ee1429e18428a77533fc6181681 418e8bc.tar.gz
+623f16d82b46839e97726fcf59a23c97 rules.patch
09f73ce6a93a93e475253d3c90ba0315 topal-patch.patch
diff --git a/manualPorts/alpine/.signature b/manualPorts/alpine/.signature
index 44a6adb..1b4d996 100644
--- a/manualPorts/alpine/.signature
+++ b/manualPorts/alpine/.signature
@@ -1,6 +1,7 @@
untrusted comment: verify with /etc/ports/deepthought.pub
-RWQxCptPusLGGu8HE34lo32OwXgaR7NwV52VdzBZV4PjzrReTv6ZyKHT7eYxaKxlhODoJqPeE7gFEFZdgUOQtAnsAz5tNzVxFwI=
-SHA256 (Pkgfile) = cd0593c6972c78431ca8b9a253da4f30b2b8a5c22c1875a55fb65ae6f1b38d6e
+RWQxCptPusLGGi2xuuLpFMYYHoIoer2fB0QFwCPCkgcJ7K/kkvVzHstyxoLz4IWEiqqJfh356rtRDJZYaYbvVH8iuYVhi//M8Q4=
+SHA256 (Pkgfile) = 8c50525c407ca85759c3044a2bc9f8795dc64c52a9a1747fc7e4fe66afe25e89
SHA256 (.footprint) = dd67d2fc4470f569897d32b8ecd5d740c59592de8ead67668be423dd89456871
SHA256 (418e8bc.tar.gz) = 05aeefeb091e7e4f13487b22af082a8687b1575dc94563113438ea368c9da7af
+SHA256 (rules.patch) = 5a00431354d50b282294f7baa303f14dcb6084a9f70207dac2961f199a8ca342
SHA256 (topal-patch.patch) = 8ee8e1dfa18e3089cc9d8db0ed06c49898c089ff8ba5703cce608102fb9b0b9b
diff --git a/manualPorts/alpine/Pkgfile b/manualPorts/alpine/Pkgfile
index 7bf6eee..99086da 100644
--- a/manualPorts/alpine/Pkgfile
+++ b/manualPorts/alpine/Pkgfile
@@ -6,14 +6,16 @@
name=alpine
version=2.21.99999.r13.418e8bc
_commit=${version##*.}
-release=2
+release=3
source=("http://repo.or.cz/alpine.git/snapshot/${_commit}.tar.gz"
+rules.patch
topal-patch.patch)
build() {
cd "alpine-${_commit}"
patch -p1 -i "${SRC}/topal-patch.patch"
+ patch -p1 -i "${SRC}/rules.patch"
./configure \
--prefix=/usr \
diff --git a/manualPorts/alpine/rules.patch b/manualPorts/alpine/rules.patch
new file mode 100644
index 0000000..01369ce
--- /dev/null
+++ b/manualPorts/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
+ <li><a href="h_config_alt_reply_menu">FEATURE: <!--#echo var="FEAT_alternate-reply-menu"--></a>
+ <li><a href="h_config_force_low_speed">FEATURE: <!--#echo var="FEAT_assume-slow-link"--></a>
+ <li><a href="h_config_auto_read_msgs">FEATURE: <!--#echo var="FEAT_auto-move-read-msgs"--></a>
++<li><a href="h_config_auto_read_msgs_rules">FEATURE: <!--#echo var="FEAT_auto-move-read-msgs-using-rules"--></a>
+ <li><a href="h_config_auto_open_unread">FEATURE: <!--#echo var="FEAT_auto-open-next-unread"--></a>
+ <li><a href="h_config_auto_unselect">FEATURE: <!--#echo var="FEAT_auto-unselect-after-apply"--></a>
+ <li><a href="h_config_auto_unzoom">FEATURE: <!--#echo var="FEAT_auto-unzoom-after-apply"--></a>
+@@ -19408,6 +19409,7 @@ This set of special tokens may be used in the
+ <A HREF="h_config_index_format">&quot;<!--#echo var="VAR_index-format"-->&quot;</A> option,
+ in the <A HREF="h_config_reply_intro">&quot;<!--#echo var="VAR_reply-leadin"-->&quot;</A> option,
+ in signature files,
++in the <A HREF="h_config_reply_leadin_rules">&quot;new-rules&quot; option</A>,
+ in template files used in
+ <A HREF="h_rules_roles">&quot;roles&quot;</A>, and in the folder name
+ that is the target of a Filter Rule.
+@@ -19420,7 +19422,7 @@ and in the target of Filter Rules.
+ <P>
+ <P>
+
+-<H1><EM>Tokens Available for all Cases (except Filter Rules)</EM></H1>
++<H1><EM>Tokens Available for all Cases (except Filter Rules or in some cases for new-rules)</EM></H1>
+
+ <DL>
+ <DT>SUBJECT</DT>
+@@ -19454,6 +19456,22 @@ email address, never the personal name.
+ For example, &quot;mailbox@domain&quot;.
+ </DD>
+
++<DT>ADDRESSTO</DT>
++<DD>
++This is similar to the &quot;TO&quot; 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, &quot;mailbox@domain&quot; when
++the e-mail message contains only one person in the To: field, or
++&quot;peter@flintstones.com president@world.com&quot;.
++</DD>
++
++<DT>ADDRESSSENDER</DT>
++<DD>
++This is similar to the &quot;sender&quot; token, only it is always the
++email address of all person listed in the Sender: field of the message.
++Example: &quot;mailbox@domain&quot;.
++</DD>
++
+ <DT>MAILBOX</DT>
+ <DD>
+ This is the same as the &quot;ADDRESS&quot; except that the
+@@ -19501,6 +19519,15 @@ are unavailable) of the persons specified in the
+ message's &quot;Cc:&quot; header field.
+ </DD>
+
++<DT>ADDRESSCC</DT>
++<DD>
++This is similar to the &quot;CC&quot; 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: &quot;mailbox@domain&quot; when
++the e-mail message contains only one person in the Cc: field, or
++&quot;peter@flintstones.com president@world.com&quot;.
++</DD>
++
+ <DT>RECIPS</DT>
+ <DD>
+ This token represents the personal names (or email addresses if the names
+@@ -19509,6 +19536,14 @@ message's &quot;To:&quot; header field and
+ the message's &quot;Cc:&quot; header field.
+ </DD>
+
++<DT>ADDRESSRECIPS</DT>
++<DD>
++This token represent the e-mail addresses of the people in the To: and
++Cc: fields, exactly in that order separated by a space. It is almost obtained
++by concatenating the ADDRESSTO and ADDRESSCC tokens.
++</DD>
++
++
+ <DT>NEWSANDRECIPS</DT>
+ <DD>
+ This token represents the newsgroups from the
+@@ -20632,6 +20667,110 @@ This is an end of line marker.
+ </DD>
+ </DL>
+
++<P>
++<H1><EM>Tokens Available Only for New-Rules</EM></H1>
++
++<DL>
++<DT>FCCFROM</DT>
++<DD>
++The Fcc: folder assigned to the email address in the From: field in the
++addressbook.
++</DD>
++</DL>
++
++<DL>
++<DT>FCCSENDER</DT>
++<DD>
++The Fcc: folder assigned to the email address in the Sender: field in the
++addressbook.
++</DD>
++</DL>
++
++<DL>
++<DT>ALTADDRESS</DT>
++<DD>
++The value of your
++<a href="h_config_alt_addresses"><!--#echo var="VAR_alt-addresses"--></a>
++variable. At this time, no expansion of regular expressions is supported.
++</DD>
++</DL>
++
++<DL>
++<DT>NICK</DT>
++<DD>
++Nickname of the person in the From field in your addressbook.
++</DD>
++</DL>
++
++<DL>
++<DT>FOLDER</DT>
++<DD>
++Name of the folder where the rule will be applied.
++</DD>
++</DL>
++
++<DL>
++<DT>COLLECTION</DT>
++<DD>
++Name of the collection list where the rule will be applied.
++</DD>
++</DL>
++
++<DL>
++<DT>ROLE</DT>
++<DD>
++Name of the Role used to reply a message.
++</DD>
++</DL>
++
++<DL>
++<DT>BCC</DT>
++<DD>
++Not implemented yet, but it will be implemented in future versions. It will
++be used for <A HREF="h_config_compose_rules">compose</A>
++<A HREF="h_config_reply_rules">reply</A>
++<A HREF="h_config_forward_rules">forward</A>
++rules.
++</DD>
++</DL>
++
++<DL>
++<DT>LCC</DT>
++<DD>
++This is the value of the Lcc: field at the moment that you start the composition.
++</DD>
++</DL>
++
++<DL>
++<DT>FORWARDFROM</DT>
++<DD>
++This corresponds to the personal name (or address if there's no personal
++name) of the person who sent the message that you are forwarding.
++</DD>
++</DL>
++
++<DL>
++<DT>FORWARDADDRESS</DT>
++<DD>
++This is the address of the person that sent the message that you
++are forwarding.
++</DD>
++</DL>
++
++
++
++
++<DL>
++<DT>FLAG</DT>
++<DD>
++A string containing the value of all the flags associated to a specific
++message. The possible values of allowed flags are "*" for Important, "N"
++for recent or new, "U" for unseen or unread, "R" for seen or read, "A" for
++answered and "D" for deleted. See an example of its use in the
++<A HREF="h_config_new_rules">new rules</A> explanation and example help.
++</DD>
++</DL>
++
+ <P>
+ <H1><EM>Token Available Only for Templates and Signatures</EM></H1>
+
+@@ -23856,6 +23995,897 @@ character sets Alpine knows about by using the &quot;T&quot; ToCharsets command.
+ &lt;End of help on this topic&gt;
+ </BODY>
+ </HTML>
++====== h_config_procid =====
++<HTML>
++<HEAD>
++<TITLE>Token: PROCID</TITLE>
++</HEAD>
++<BODY>
++<H1>TOKEN: PROCID explained</H1>
++
++<P>
++The PROCID token is a way in which the user and the program can differentiate
++between different parts of a program. It allows the user to tell the
++program when to use a specific rule, and only use it at that specific
++moment.
++
++<P> The normal way in which this is done is by adding a new configuration
++variable. The idea behind the PROCID token is that instead of adding a new
++configuration variable (which means the user has to go through more
++configuration variables just to tune the program to his liking), we reuse
++an old variable and let the user look inside that variable for the desired
++behavior, which is actually set by setting the PROCID token.
++
++<P>
++Consider the following examples for forward-rules:
++
++<P>
++_ROLE_ == {work} =&gt; _SUBJECT_ := _COPY_{[tag] _SUBJECT_}
++
++<P>
++and
++
++<P>
++_ROLE_ == {work} =&gt; _LCC_ := _TRIM_{_FORWARDFROM_ &lt;_FORWARDADDRESS_&gt;}
++
++<P>
++both are triggered by the same condition. Since both are configured in the
++same variable, only one of them will be executed all the time (whichever
++is first). Therefore in order to differentiate, we add a _PROCID_ token.
++So, for example, the first example above will be executed only when we are
++determining the subject. In this case, the following rule will accomplish
++this task
++
++<P>
++_PROCID_ == {fwd-subject} && _ROLE_ == {work} =&gt; _SUBJECT_ := _COPY_{[tag] _SUBJECT_}
++
++<P>
++In this case, this rule will be tested fully only when we are determining
++the subject line of a forwarded message, not otherwise.
++
++<P>
++It is wise to add the _PROCID_ token as the first condition in a rule, so
++that other conditions will not be tested in a long list of rules.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_compose_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_compose-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_compose-rules"--></H1>
++
++<P> At this time, this option is used to generate values for signature
++files that is not possible to do with the use of
++<A HREF="h_rules_roles">roles</A>.
++
++<P> For example, you can have a rule like:<BR>
++_TO_ >> {Peter Flintstones} => _SIGNATURE_{~/.petersignature}
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_forward_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_forward-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_forward-rules"--></H1>
++
++<P> This option has several uses. This feature uses the PROCID function
++to identify different features of forwarding. You can read more about PROCID
++by following <A HREF="h_config_procid">this link</A>.
++
++<P> If you want to edit the subject of a forwarded message, use the
++PROCID <I>fwd-subject</I>. For example you could have a rule like
++
++<P>
++_ROLE_ == {admin} && _SUBJECT_ !&gt; {[tag] } =&gt; _COPY_{[tag] _SUBJECT_}
++
++<P> Another way in which this option can be used, is to trim the values of
++some fields. For this application the PROCID is <I>fwd-lcc</I>. For
++example it can be used in the following way:
++
++<P>
++_ROLE_ == {work} => _LCC_ := _TRIM_{_FORWARDFROM_ &lt;_FORWARDADDRESS_&gt;}
++
++<P> Other functions that can be used in this option are _EXEC_ and _REXTRIM_.
++
++<P> You can also use the _EXEC_ function. The documentation for this function
++is in the
++<A HREF="h_config_resub_rules"><!--#echo var="VAR_reply-subject-rules"--></A>
++help text.
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_index_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_index-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_index-rules"--></H1>
++
++<P> This option is used to supersede the value of the option <A
++HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A> for specific folders. In
++this form you can have different index-formats for different folders. For
++example an entry here may be:
++
++<P>
++_FOLDER_ == {INBOX} => _INDEX_{IMAPSTATUS DATE FROM(33%) SIZE SUBJECT(67%)}
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_pretty_command =====
++<HTML>
++<HEAD>
++<TITLE>Pretty-Command Explained</TITLE>
++</HEAD>
++<BODY>
++<H1>Pretty Command Explained</H1>
++
++<P> This text explains how to encode keys so that they will be recognized
++by Alpine in the _PKEY_ token. Most direct keystrokes are recognized in the
++same way. For example, the key ~ is recognized by the same character. The
++issue is how control, or functions keys are recognized. The internal code
++is most times easy to find out. If the key you want to use is not already
++recognized by Alpine simply press it. Alpine will print its code. For example,
++the return key is not recognized in this screen, so if you press it, you
++will see the following message.
++
++<P> [Command &quot;RETURN&quot; not defined for this screen. Use ? for help]
++
++<P> from here you can guess that the code for the return command is
++RETURN. You can try other commands, like Control-C, the TAB key, F4, etc.
++to see their codes.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_key_macro_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_key-definition-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_key-definition-rules"--></H1>
++
++<P> This option can be used to define macros, that is, to define a key that
++when pressed executes a group of predetermined keystrokes. Since Alpine is
++a menu driven program, sometimes the same key may have different meanings
++in different screens, so a global redefinition of a key although possible
++is not advisable.
++
++<P> <B>Always use the _SCREEN_ token as defined below.</B>. You have been
++warned!
++
++<P> In each screen, every time you press a recognized key, a command is
++activated. In order to understand this feature, think of commands instead
++of keystrokes. For example, you can think of the sort by thread command.
++This command is associated to the keystrokes $ and h. You may want to
++associate this command to a specific keystroke, like ~, so every time you
++press the ~ key, Alpine understand the $ and h keystrokes, which activates
++the sort by thread command.
++
++<P> Therefore, in order to use this option you must think of three
++components. The screen where you will use the macro, the keystroke you
++want to use and the set of keystrokes used by Alpine to accomplish the task
++you want to accomplish. We will talk about these three components in what
++follows.
++
++<P> First you must decide in which screen the macro will be used. This
++feature is currently only available for the screen where your messages
++are listed in index form (<A HREF="h_mail_index">MESSAGE INDEX</A>),
++the screen where your message is displayed
++(<A HREF="h_mail_view">MESSAGE TEXT</A>) and the screen where the list of
++folders is displayed (<A HREF="h_folder_maint">FOLDER LIST</A>). The
++internal names of these screens for this patch are &quot;index&quot;,
++&quot;text&quot; and
++&quot;folder&quot; respectively. Please note that the internal names are
++all in lowercase and are case sensitive.
++
++<P> In order to define the screen, you use the _SCREEN_ token, so for
++example, you can write _SCREEN_ == {index}.
++
++<P> Second you must think of which key you will use to activate the macro.
++Here you can use any key of your choice. The token you use to designate a
++key is the _PKEY_ token (PKEY stands for &quot;pressed key&quot;). For
++example you could use _PKEY_ == {~}, to designate the &quot;~&quot;
++key to activate the command. Some keystrokes (like control, or
++function keys) are encoded in special ways. You should read the
++<A HREF="h_config_pretty_command">full explanation</A> on how to find
++out the encoding for each keystroke.
++
++<P> Last, you must think of the list of keys you will use to accomplish
++the task you want Alpine to perform. Say for example you want to have the
++folder sorted by thread. That means you want Aline to execute the keys
++&quot;$&quot; and &quot;h&quot;. You use the _COMMAND_ function to specify
++this. The syntax in this case is _COMMAND_{$,h}.
++
++<P> Observe that in the above example the different inputs are separated
++by commas. This is the standard way in which the
++<A HREF="h_config_init_cmd_list"><!--#echo var="VAR_initial-keystroke-list"--></A> command works from
++the command line. Due to restrictions in the way Alpine works, a comma is a
++special character, which when added to a configuration option like this
++will cause the configuration to split into several lines in the
++configuration screen. This has the effect of producing several
++configuration options, all of which are incorrect. This is undesirable
++because what you want is to have it all in one line. In order to force the
++configuration into one line you must quote the comma. The best way to
++accomplish this is by quoting the full definition of the rule. For
++example.
++
++<P>
++&quot;_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{$,h}&quot;
++
++<P> Another way to accomplish the same effect is by quoting the command and
++not using quotes for the full command, nor commas to separate the
++keystrokes in the command, for example
++
++<P>
++_SCREEN_ == {index} && _PKEY_ == {~} => _COMMAND_{&quot;$h&quot;}
++
++<P> For more information on how to define the argument of the _COMMAND_
++token see the help of
++<A HREF="h_config_init_cmd_list"><!--#echo var="VAR_initial-keystroke-list"--></A>.
++
++<P> Because the $ command can also be used as the first character in the
++definition of an environemnt variable, no expansion of environment variables
++is done when parsing this variable. The $ character does not need quoting
++and quoting it will make Alpine fail to produce the correct result.
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_replace_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_replace-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_replace-rules"--></H1>
++
++<P> This option is used to have Alpine print different values for specific
++tokens in the <A HREF="h_config_index_format"><!--#echo var="VAR_index-format"--></A>. For example you
++can replace strings like "To: newsgroup" by your name.
++
++<P> Here are examples of possible rules:
++
++<P>_FOLDER_ != {sent-mail} && _NICK_ != {} =&gt; _FROM_ := _REPLACE_{_FROM_ (_NICK_)}
++
++<P> or if you receive messages with tags that contain arbitrary numbers, and
++you want them removed from the index (but not from the subject), use a rule
++like the following
++
++<P>_FOLDER_ == {INBOX} =&gt; _SUBJECT_ := _REXTRIM_{&#92;&#91;some-tag-here &#35;&#91;0-9&#93;.*&#92;&#93;}
++
++<P> You can also use this configuration option to remove specific strings of
++the index display screen, so that you can trim unnecessary information in
++your index, like the reply leadin string in the OPENINGTEXTNQ token of the index.<BR>
++
++<P>_FOLDER_ == {some-folder} => _OPENINGTEXTNQ_ := _REXTRIM_{On.*wrote: }
++
++<P> You can also use the _EXEC_ function. The documentation for this function
++is in the
++<A HREF="h_config_resub_rules"><!--#echo var="VAR_reply-subject-rules"--></A>
++help text.
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_reply_leadin_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_reply-leadin-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_reply-leadin-rules"--></H1>
++
++<P> This option is used to have Alpine generate a different
++<A HREF="h_config_reply_intro"><!--#echo var="VAR_reply-leadin"--></A> string dependent either on
++the person you are replying to, or the folder where the message is being
++replied is in, or both.
++
++<P> Here there are examples of how this can be used. One can use the definition
++below to post to newsgroups and the pine-info mailing list, say:
++<P>
++_FOLDER_ << {pine-info;_NEWS_} => _REPLY_{*** _FROM_ _ADDRESS_("_FROM_" "" "(_ADDRESS_) ")wrote in_NEWS_("" " the" "") _FOLDER_ _NEWS_("" "list " "")_SMARTDATE_("Today" "today" "on _LONGDATE_"):}
++
++<P> Here there is an example that one can use to change the reply indent string
++to reply people that speak spanish.
++<P>
++_FROM_{Condorito;Quico} => _REPLY_{*** _FROM_ (_ADDRESS_) escribi&oacute; _SMARTDATE_("Today" "hoy" "en _LONGDATE_"):}
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_resub_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_reply-subject-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_reply-subject-rules"--></H1>
++
++<P> This option is used to have Alpine generate a different subject when
++replying rather than the one Alpine would generate automatically.
++
++<P> Here there are a couple of examples about how to use this
++configuration option:
++
++<P> In order to have messages with empty subject to be replied with the message
++"your message" use the rule<BR>
++<center>_SUBJECT_ == {} => _RESUB_{Re: your message}</center>
++
++<P> If you want to trim some parts of the subject when you reply use the
++rule<BR>
++<center>_SUBJECT_ >> {[one];two} => _SUBJECT_ := _TRIM_{[;];two}</center>
++
++<P>this rule removes the brackets "[" and "]" whenever the string "[one]"
++appears in it, it also removes the word "two" from it.
++
++<P>Another example where you may want to use this rule is when you
++correspond with people that change the reply string from &quot;Re:&quot;
++to &quot;AW:&quot; or &quot;Sv:&quot;. In this case a rule like<BR>
++<center>_SUBJECT_ >> {Sv: ;AW: } => _SUBJECT_ := _TRIM_{Sv: ;AW: }</center>
++<P>
++would eliminate undesired strings in replies.
++
++<P> Another interesting use of this option is the use of the _EXEC_ function.
++This function takes as an argument a program or a script. This program
++must take as the input a file, and write its output to that file. For example,
++below is a sample of a script that removes the letter &quot;a&quot; of a file.
++
++<PRE>
++#!/bin/sh
++sed 's/a//g' $1 > /tmp/mytest
++mv /tmp/mytest $1
++</PRE>
++
++<P>
++As you can see this script took &quot;$1&quot; 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 &quot;$1&quot;. This is the kind of behavior
++that your program is expected to have.
++
++<P>
++The content of the input file (&quot;$1&quot; above) is the value of a token
++like _SUBJECT_. In order to indicate this, we use the notation
++
++<P>
++_SUBJECT_ := _EXEC_{/path/to/script}
++
++<P> for the action. So for example
++
++<P>
++_FOLDER_ := {sent-mail} =&gt; _SUBJECT_ := _EXEC_{/path/to/script}
++
++<P> is a valid rule.
++
++<P> You can also use this configuration option to customize reply subjects
++according to the sender of the message.
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_sort_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_sort-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_sort-rules"--></H1>
++
++<P> This option is used to have Alpine sort different folders in different orders
++and thus override the value already set in the
++<A HREF="h_config_sort_key"><!--#echo var="VAR_sort-key"--></A> configuration option.
++
++<P> Here's an example of the way it can be used. In this case all incoming
++folders are mailing lists, except for INBOX, so we sort INBOX by arrival
++(which is the default type of sort), but we want all the rest of mailing
++lists and newsgroups to be sorted by thread.
++
++<P>
++_COLLECTION_ >> {Incoming-Folders;News} && _FOLDER_ != {INBOX} => _SORT_{tHread}
++
++<P> Another example could be<BR>
++_FOLDER_ == {Mailing List} => _SORT_{Reverse tHread}
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++
++</BODY>
++</HTML>
++====== h_config_save_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_save-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_save-rules"--></H1>
++
++<P> This option is used to specify which folder should be used to save a
++message depending either on the folder the message is in, who the message
++is from, or text that the message contains in specific headers (Cc:,
++Subject:, etc).
++
++<P> If this option is set and the
++<A HREF="h_config_auto_read_msgs"><!--#echo var="FEAT_auto-move-read-msgs"--></A> configuration
++option is also enabled then these definitions will be used to move messages
++from your INBOX when exiting Alpine.
++
++<P>Here there are some examples<BR>
++_FLAG_ >> {D} -> Trash<BR>
++_FROM_ == {U2} -> Bono<BR>
++_FOLDER_ == {comp.mail.pine} -> pine-stuff<BR>
++_NICK_ != {} -> _NICK_/_NICK_<BR>
++_DATEISO_ >> {02-10;02-11} -> archive-oct-nov-2002
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++
++</BODY>
++</HTML>
++====== h_config_reply_indent_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_reply-indent-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_reply-indent-rules"--></H1>
++
++<P> This option is used to specify which reply-indent-string is to be used
++when replying to an e-mail. If none of the rules are successful, the result in
++the variable <a href="h_config_reply_indent_string"><!--#echo var="VAR_reply-indent-string"--></a>
++is used.
++
++<P> The associated function to this configuration option is called "RESTR" (for
++REply STRing). Some examples of its use are:<BR>
++_FROM_ == {Your Boss} => _RESTR_{"> "}<BR>
++_FROM_ == {My Wife} => _RESTR_{":* "}<BR>
++_FROM_ == {Perter Flintstone;Wilma Flintstone} => _RESTR_{"_INIT_ > "}<BR>
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++
++</BODY>
++</HTML>
++====== h_config_smtp_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_smtp-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_smtp-rules"--></H1>
++
++<P> This option is used to specify which SMTP server should be used when
++sending a message, if this rule is not defined, or the execution of the rule
++results in no server selected, then Alpine will look for
++the value from the role that is being used to compose the message. If no smtp
++server is defined in that role or you are not using a role, then Alpine will get
++the name of the server from the
++<A HREF="h_config_smtp_server">&quot;<!--#echo var="VAR_smtp-server"-->&quot;</A> configuration
++option according to the rules used in that variable.
++
++<P> The function associated to this configuration option is _SMTP_, an example
++of the use of this function is<BR>
++_ADDRESSTO_ == {peter@bedrock.com} => _SMTP_{smtp.bedrock.com}
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++
++</BODY>
++</HTML>
++====== h_config_startup_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: <!--#echo var="VAR_startup-rules"--></TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: <!--#echo var="VAR_startup-rules"--></H1>
++
++<P> This option is used when a folder is being opened. You can use it to specify its <A
++HREF="h_config_inc_startup"><!--#echo var="VAR_incoming-startup-rule"--></A> and override
++Alpine's global value set for all folders.
++
++<P> An example of the usage of this option is:<BR>
++_FOLDER_ == {Lynx;pine-info;_NEWS_} => _STARTUP_{first-unseen}
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>&lt;End of help on this topic&gt;
++
++</BODY>
++</HTML>
++====== h_config_new_rules =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: New Rules Explained</TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: New Rules Explained</H1>
++
++This is a quite powerful option. Here you can define rules that override
++the values of any other option you have set in Alpine.
++
++<P>
++For example, you can set your folders to be sorted in a certain way when
++you open them (say by Arrival). You may want, however, your newsgroups to
++be sorted by thread. The set of &quot;rules&quot; options allows you to
++configure this and many other options, including the index-format for
++specific folders, the way the subject is displayed in the index screen or
++the reply-leadin-string, to name a few.
++
++<P>
++Every rule has three parts: a condition, a separator and an action. The
++action is what will happen if the condition of the rule is satisfied.
++
++<P>
++ Here is an example:
++
++<P>
++ _FROM_ == {Fred Flintstone} =&gt; _SAVE_{Fred}
++
++<P>
++ Here the separator is "=&gt;". 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 &quot;Fred
++Flintstone&quot;, and the action will be that you will be offered to save
++it in the folder &quot;Fred&quot;, whenever you press the letter
++&quot;S&quot; to save a message.
++
++<P>
++ The separator is always &quot;=&gt;&quot;, with one exception to be seen
++later. But for the most part this will be the only one you will ever need.
++
++<P>
++ Now let us see how to do it. There are 13 functions already defined for
++you. These are: _EXEC_, _INDEX_, _REPLACE_, _REPLY_, _RESUB_, _SAVE_,
++_SIGNATURE_, _SORT_, _STARTUP_, _TRIM_, _REXTRIM_, _THREADSTYLE and
++_THREADINDEX_. The parameter of a function has to be enclosed between
++&quot;{&quot; and &quot;}&quot;, so for example you can specify
++_SAVE_{saved-messages} as a valid sentence.
++
++<P>
++ Later in the document you will find examples. Here is a short
++description of what each function does:
++
++<P>
++<UL>
++<LI> _EXEC_ : This function takes as an argument a program. This program
++gets as the input a file and must rewrite its output to that file, which
++is then taken as the value to replace from the contents of that file. You
++can use this function with
++<A HREF="h_config_resub_rules"><!--#echo var="VAR_reply-subject-rules"--></A>,
++<A HREF="h_config_replace_rules"><!--#echo var="VAR_replace-rules"--></A> and
++<A HREF="h_config_forward_rules"><!--#echo var="VAR_forward-rules"--></A>.
++See the help of those options for examples of how to use this function
++and configure these rules.
++<BR>&nbsp;<BR>
++<LI> _INDEX_ : This function takes as an argument an index-format, and
++makes that the index-format for the specified folder.
++<BR>&nbsp;<BR>
++<LI> _REPLACE_ : This function replaces the subject/from of the given e-mail by
++another subject/from only when displaying the index.
++<BR>&nbsp;<BR>
++<LI> _REPLY_ : This function takes as an argument a definition of a
++reply-leadin-string and makes this the reply-leading-string of the
++specified folder or person.
++<BR>&nbsp;<BR>
++<LI> _RESTR_ : This function takes as an argument the value of the
++reply-indent-string to be used to answer the message being replied to.
++<BR>&nbsp;<BR>
++<LI> _RESUB_ : This function replaces the subject of the given e-mail by
++another subject only when replying to a message.
++<BR>&nbsp;<BR>
++<LI> _SAVE_ : The save function takes as an argument the name of a
++possibly non existing folder, whenever you want to save a message, that
++folder will be offered for you to save.
++<BR>&nbsp;<BR>
++<LI> _SIGNATURE_ : This function takes as an argument a signature file and
++uses that file as the signature for the message you are about to
++compose/reply/forward.
++<BR>&nbsp;<BR>
++<LI> _SMTP_ : This function takes as an argument the definition of a
++SMTP server.
++<BR>&nbsp;<BR>
++<LI> _SORT_ : This function takes as an argument a Sort Style, and sorts a
++specified folder in that sort order.
++<BR>&nbsp;<BR>
++<LI> _TRIM_ : This function takes as an argument a list of strings that
++you want removed from another string. At this time this only works for
++_FROM_ and _SUBJECT_.
++<BR>&nbsp;<BR>
++<LI> _REXTRIM_ : Same as _TRIM_ but its argument is one and
++only one extended regular expression.
++<BR>&nbsp;<BR>
++<LI> _STARTUP_ : This function takes as an argument an
++incoming-startup-rule, and open an specified folder using that rule.
++<BR>&nbsp;<BR>
++<LI> _THREADSTYLE_ : This function takes as an argument a
++threading-display-style and uses it to display threads in a folder.
++<BR>&nbsp;<BR>
++<LI> _THREADINDEX_ : This function takes as an argument a
++threading-index-style and uses it to display threads in a folder.
++</UL>
++
++<P>
++You must me wondering how to define the person/folder over who to apply
++the action. This is done in the condition. When you specify a rule, the
++rule is only executed if the condition is satisfied. In another words for
++the rule:
++
++<P>
++ _FROM_ == {Fred Flintstone} =&gt; _SAVE_{Fred}
++
++<P> it will only be applied if the from is &quot;Fred Flintstone&quot;. If
++the From is &quot;Wilma Flintstone&quot; the rule will be skipped.
++
++<P> In order to test a condition you can use the following tokens (in
++alphabetical order): _ADDRESS_, _CC_, _FOLDER_, _FROM_,_NICK_, _ROLE,
++_SENDER_, _SUBJECT_ and _TO_. The token will always be tested against what
++it is between &quot;{&quot; and &quot;}&quot; in the condition, this part
++of the condition is called the &quot;condition set&quot;. The definition
++of each token can be found <A HREF="h_index_tokens">here</A>.
++
++<P> A special testing token called _PROCID_ can be used to differentiate
++inside a rule, between two rules that are triggered by the same condition.
++A full explanation of the _PROCID_ token can be found in
++<A HREF="h_config_procid">this link</A>.
++
++<P> There are two more tokens related to the option
++<A HREF="h_config_key_macro_rules">key-definition-rules</A>. Those tokens
++are only specific to that option, and hence are not explained here.
++
++<P> You can also test in different ways, you can use the following
++&quot;test operands&quot;: &lt;&lt;, !&lt;, &gt;&gt;, !&gt;, == and !=.
++All of them are two characters long. Here is the meaning of them:
++
++<P>
++<UL>
++<LI> &lt;&lt; : It tests if the value of the token is contained in
++the condition set. Here for example if the condition set were equal to
++&quot;Freddy&quot;, then the condition: _NICK_ &lt;&lt; {Freddy}, would be true if
++the value of _NICK_ were &quot;Fred&quot;, &quot;red&quot; or &quot;Freddy&quot;. You are just looking
++for substrings here.
++<LI> &gt;&gt; : 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
++&quot;Fred&quot;, then the condition: _FROM_ &gt;&gt; {Fred}, would be true if
++the value of _FROM_ were &quot;Fred Flintstone&quot; or &quot;Fred P. Flintstone&quot; or &quot;Freddy&quot;.
++<LI> == : It tests if the value of the token is exactly equal to the value
++of the set condition. For example _NICK_ == {Fred} will be false if the value
++of _NICK_ is &quot;Freddy&quot; or &quot;red&quot;.
++<LI> !&lt; : This is true only when &lt;&lt; is false and vice versa.
++<LI> !&gt; : This is true only when &gt;&gt; is false and vice versa.
++<LI> != : This is true only when == is false and vice versa.
++</UL>
++
++<P>
++ Now let us say that you want the same action to be applied to more than
++one person or folder, say you want &quot;folder1&quot; and &quot;folder2&quot; to be sorted by
++Ordered Subject upon entering. Then you can list them all of them in the
++condition part separting them by a &quot;;&quot;. Here is the way to do it.
++
++<P>
++ _FOLDER_ &lt;&lt; {folder1; folder2} =&gt; _SORT_{OrderedSubj}
++
++<P>
++ Here is the first subtlety about these definitions. Notice that the
++following rule:
++
++<P>
++ _FOLDER_ == {folder1; folder2} =&gt; _SORT_{Reverse OrderedSubj}
++
++<P> works only for &quot;folder1&quot; but not for &quot;folder2&quot;. This is because the
++comparison of the name of the folder is done with whatever is in between
++&quot;{&quot;, &quot;;&quot; or &quot;}&quot;, so in the above rule you would be testing <BR>
++&quot;folder2&quot; == &quot; folder2&quot;. The extra space makes the difference.
++The reason why the first rule does not fail is because
++&quot;folder2&quot; &lt;&lt; &quot; folder2&quot; is actually
++true. If something ever fails this may be something to look into.
++
++<P>
++ Here are a few examples of what we have talked about before.
++
++<P>
++_NICK_ == {lisa;kika} =&gt; _SAVE_{_NICK_/_NICK_} <BR>
++This means that if the nick is lisa, it will
++save the message in the folder &quot;lisa/lisa&quot;, and if the nick
++is &quot;kika&quot;, it will save the message in the folder &quot;kika/kika&quot;
++
++<P>
++_FOLDER_ == {Lynx} -&gt; lynx <BR>
++This, is an abbreviation of the following rule:<BR>
++_FOLDER_ == {Lynx} =&gt; _SAVE_{lynx} <BR>
++(note the change in separator from &quot;=&gt;&quot; to &quot;-&gt;&quot;). In the future
++I will use that abbreviation.
++
++<P> _FOLDER_ &lt;&lt; {comp.mail.pine; pine-info; pine-alpha} -&gt; pine <BR>
++Any message in the folders &quot;comp.mail.pine&quot;, &quot;pine-info&quot; or &quot;pine-alpha&quot;
++will be saved to the folder &quot;pine&quot;.
++
++<P> _FROM_ &lt;&lt; {Pine Master} -&gt; pine <BR>
++Any message whose From field contains
++&quot;Pine Master&quot; will be saved in the folder pine.
++
++<P> _FOLDER_ &lt;&lt; {Lynx; pine-info; comp.mail.pine} =&gt;
++_INDEX_{IMAPSTATUS MSGNO DATE FROMORTO(33%) SUBJECT(66%)} <BR> Use a
++different index-format for the folders &quot;Lynx&quot;, &quot;pine-info&quot; and
++&quot;comp.mail.pine&quot;, where the size is not present.
++
++<P> _FOLDER_ == {Lynx;pine-info} =&gt; _REPLY_{*** _FROM_ (_ADDRESS_)
++wrote in the _FOLDER_ list _SMARTDATE_(&quot;Today&quot; &quot;today&quot; &quot;on
++_LONGDATE_&quot;):}<BR> If a message is in one of the incoming folders &quot;Lynx&quot;
++or &quot;pine-info&quot;, create a reply-leadin-string that acknowledges that. Note
++the absence of &quot;,&quot; in the function _SMARTDATE_. For example answering to a
++message in the pine-info list would look like:
++
++<P>
++*** Steve Hubert (hubert@cac.washington.edu) wrote in the pine-info list today:
++
++<P>
++However replying for a message in the Lynx list would look:
++
++<P>
++*** mattack@area.com (mattack@area.com) wrote in the Lynx list today:
++
++<P>
++If you write in more than one language you can use this feature to create
++Reply-leadin-strings in different languages.
++
++<P> Note that at least for people you can create particular
++reply-leadin-string using the role features, but it does not work as this
++one does. This seems to be the right way to do it.
++
++<P> _FOLDER_ &lt;&lt; {Lynx; comp.mail.pine; pine_info; pine-alpha} =&gt;
++_SORT_{OrderedSubj}<BR> This means upon opening, sort the folders &quot;Lynx&quot;,
++&quot;comp.mail.pine&quot;, etc in ordered subject. All the others use the default
++sort order. You can not sort in reverse in this form. The possible
++arguments of this function are listed in the definition of the
++default-sort-rule (Arrival, scorE, siZe, etc).
++
++<P> The last examples use the function _TRIM_ which has a special form.
++This function can only be used in the index list.
++
++<P> _FOLDER_ &lt;&lt; {Lynx} =&gt; _SUBJECT_ := _TRIM_{lynx-dev }<BR> In
++the folder &quot;Lynx&quot; eliminate from the subject the string &quot;lynx-dev &quot; (with
++the space at the end). For example a message whose subject is &quot;Re:
++lynx-dev unvisited Visited Links&quot;, would be shown in the index with
++subject: &quot;Re: unvisited Visited Links&quot;, making the subject shorter and
++giving the same information.
++
++<P> _FROM_ &gt;&gt; {Name (Comment)} =&gt; _FROM_ :=
++_TRIM_{ (Comment)}<BR> Remove the part &quot; (Comment)&quot;
++from the _FROM_, so when displaying in the index the real From &quot;Name&quot;
++will appear.
++
++<P> _SUBJECT_ == {} =&gt; _RESUB_{Re: your mail without subject}
++If there is no subject in the message, use the subject &quot;Re: your mail
++wiyhout subject&quot; as a subject for the reply message.
++
++<P> You can add more complexity to your rules by checking more than one
++conditions before a rule is executed. 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
++
++<P> _FOLDER_ == {INBOX} && _FROM_ >> {Rubye} => _SAVE_{Personal}
++
++<P> We could also have a rule that is triggered by an &quot;or&quot;
++condition by, sat for messages from Andres or messages in the index
++to trigger a specific reply leadin string.
++
++<P> _FOLDER_ == {INBOX} || _FROM_ >> {Andres} =&gt; _REPLY_{You wrote:}
++
++<P>Observe that the construction
++
++<P> _TOKEN_ == {value1} || _TOKEN_ == {value2}
++
++<P>can be shortened to
++
++<P> _TOKEN_ == {value1;value2}
++
++<P> Round parentheses can be used to group some conditions, for example
++
++<P> (_FROM_ &gt;&gt; {Andres} && _FOLDER_ == {INBOX}) || _FROM_ &gt;&gt; {Rubye}
++
++
++<P> You can also list your index by nick, in the following way:<BR>
++_NICK_ != {} => _FROM_ := _REPLACE_{_NICK_}
++
++<P>
++ If you want to open the folder &quot;pine-info&quot; in the first non-read message
++use the rule:<BR>
++_FOLDER_ == {pine-info} => _STARTUP_{first-unseen}
++
++<P>
++ If you want to move your deleted messages to a folder, called &quot;Trash&quot;, use
++the following rule:<BR>
++_FLAG_ >> {D} -> Trash
++
++<P>
++The reason why the above test is not &quot;_FLAG_ == {D}&quot; is because that would mean
++that this is the only flag set in the message. It's better to test by containment in this case.
++
++<P> If you want to use a specific signature when you are in a specific collection
++use the following rule:<BR>
++_COLLECTION_ == {Mail} => _SIGNATURE_{/full/path/to/.signature}
++
++<P> Finally about the question of which rule will be executed. Only the
++first rule that matches will be executed. It is important to notice though
++that &quot;saving&quot; rules do not compete with &quot;sorting&quot; rules. So the first
++&quot;saving&quot; rule that matches will be executed in the case of saving and so
++on.
++
++<P>
++<UL>
++<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
++</UL><P>
++&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
+ ====== h_config_char_set =====
+ <HTML>
+ <HEAD>
+@@ -27421,6 +28451,76 @@ MESSAGE TEXT screen.
+
+ </DL>
+
++<P>
++<UL>
++<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
++</UL><P>
++&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_thread_display_style_rule =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: Threading-Display-Style-Rule</TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: Threading-Display-Style-Rule</H1>
++
++This option is very similar to <A HREF="h_config_thread_disp_style">
++<!--#echo var="VAR_threading-display-style"--></A>, but it is a rule which specifies the
++display styles for a thread that you want displayed in a specific
++folder or collection.
++<P>
++The token to be used in this function is _THREADSTYLE_. Here there is
++an example of its use
++<P>
++_FOLDER_ == {pine-info} => _THREADSTYLE_{mutt-like}
++<P>
++The values that can be given for the _THREADSTYLE_ function are the
++values of the threading-display-style function, which can be found
++listed in the <A HREF="h_config_thread_disp_style">threading-display-style</A>
++configuration option.
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
++<P>
++<UL>
++<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
++</UL><P>
++&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
++====== h_config_thread_index_style_rule =====
++<HTML>
++<HEAD>
++<TITLE>OPTION: Threading-Index-Style-Rule</TITLE>
++</HEAD>
++<BODY>
++<H1>OPTION: Threading-Index-Style-Rule</H1>
++
++This option is very similar to <A HREF="h_config_thread_index_style">
++<!--#echo var="VAR_threading-index-style"--></A>, but it is a rule which specifies the
++index styles for a thread that you want displayed in a specific
++folder or collection.
++<P>
++The token to be used in this function is _THREADINDEX_. Here there is
++an example of its use
++<P>
++_FOLDER_ == {pine-info} => _THREADINDEX_{regular-index-with-expanded-threads}
++<P>
++The values that can be given for the _THREADINDEX_ function are the
++values of the threading-index-display function, which can be found
++listed in the <A HREF="h_config_thread_index_style"><!--#echo var="VAR_threading-index-style"--></A>
++configuration option.
++
++<P> This configuration option is just one of many that allow you to
++override the value of some global configurations within Alpine. There is a
++help text explaining how to define all of them, which you can read by
++following this <A HREF="h_config_new_rules">link</A>.
++
+ <P>
+ <UL>
+ <LI><A HREF="h_finding_help">Finding more information and requesting help</A>
+@@ -31117,6 +32217,29 @@ them as deleted in the INBOX. Messages in the INBOX marked with an
+ &lt;End of help on this topic&gt;
+ </BODY>
+ </HTML>
++====== h_config_auto_read_msgs_rules =====
++<HTML>
++<HEAD>
++<TITLE>FEATURE: auto-move-read-msgs-using-rules</TITLE>
++</HEAD>
++<BODY>
++<H1>FEATURE: auto-move-read-msgs-using-rules</H1>
++This feature controls an aspect of Alpine's behavior upon quitting. If set,
++and the
++<A HREF="h_config_read_message_folder">&quot;<!--#echo var="VAR_read-message-folder"-->&quot;</A>
++option is also set, then Alpine will automatically transfer all read
++messages to the designated folder using the rules that you have defined in
++your
++<A HREF="h_config_save_rules">&quot;<!--#echo var="VAR_save-rules"-->&quot;</A> and mark
++them as deleted in the INBOX. Messages in the INBOX marked with an
++&quot;N&quot; (meaning New, or unseen) are not affected.
++<P>
++<UL>
++<LI><A HREF="h_finding_help">Finding more information and requesting help</A>
++</UL><P>
++&lt;End of help on this topic&gt;
++</BODY>
++</HTML>
+ ====== h_config_auto_fcc_only =====
+ <HTML>
+ <HEAD>
+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 *);