diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /pith/pattern.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'pith/pattern.c')
-rw-r--r-- | pith/pattern.c | 8226 |
1 files changed, 8226 insertions, 0 deletions
diff --git a/pith/pattern.c b/pith/pattern.c new file mode 100644 index 00000000..84a32c41 --- /dev/null +++ b/pith/pattern.c @@ -0,0 +1,8226 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: pattern.c 1204 2009-02-02 19:54:23Z hubert@u.washington.edu $"; +#endif +/* + * ======================================================================== + * Copyright 2006-2009 University of Washington + * Copyright 2013 Eduardo Chappa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * ======================================================================== + */ + +#include "../pith/headers.h" +#include "../pith/pattern.h" +#include "../pith/state.h" +#include "../pith/conf.h" +#include "../pith/string.h" +#include "../pith/msgno.h" +#include "../pith/status.h" +#include "../pith/list.h" +#include "../pith/flag.h" +#include "../pith/tempfile.h" +#include "../pith/addrstring.h" +#include "../pith/search.h" +#include "../pith/mailcmd.h" +#include "../pith/filter.h" +#include "../pith/save.h" +#include "../pith/mimedesc.h" +#include "../pith/reply.h" +#include "../pith/folder.h" +#include "../pith/maillist.h" +#include "../pith/sort.h" +#include "../pith/copyaddr.h" +#include "../pith/pipe.h" +#include "../pith/list.h" +#include "../pith/news.h" +#include "../pith/util.h" +#include "../pith/sequence.h" +#include "../pith/detoken.h" +#include "../pith/busy.h" +#include "../pith/indxtype.h" +#include "../pith/mailindx.h" +#include "../pith/send.h" +#include "../pith/icache.h" +#include "../pith/ablookup.h" +#include "../pith/keyword.h" + + +/* + * Internal prototypes + */ +void open_any_patterns(long); +void sub_open_any_patterns(long); +void sub_close_patterns(long); +int sub_any_patterns(long, PAT_STATE *); +PAT_LINE_S *parse_pat_lit(char *); +PAT_LINE_S *parse_pat_inherit(void); +PAT_S *parse_pat(char *); +void parse_patgrp_slash(char *, PATGRP_S *); +void parse_action_slash(char *, ACTION_S *); +ARBHDR_S *parse_arbhdr(char *); +char *next_arb(char *); +PAT_S *first_any_pattern(PAT_STATE *); +PAT_S *last_any_pattern(PAT_STATE *); +PAT_S *prev_any_pattern(PAT_STATE *); +PAT_S *next_any_pattern(PAT_STATE *); +int sub_write_patterns(long); +int write_pattern_file(char **, PAT_LINE_S *); +int write_pattern_lit(char **, PAT_LINE_S *); +int write_pattern_inherit(char **, PAT_LINE_S *); +char *data_for_patline(PAT_S *); +int charsets_present_in_msg(MAILSTREAM *, unsigned long, STRLIST_S *); +void collect_charsets_from_subj(ENVELOPE *, STRLIST_S **); +void collect_charsets_from_body(BODY *, STRLIST_S **); +SEARCHPGM *next_not(SEARCHPGM *); +SEARCHOR *next_or(SEARCHOR **); +void set_up_search_pgm(char *, PATTERN_S *, SEARCHPGM *); +void add_type_to_pgm(char *, PATTERN_S *, SEARCHPGM *); +void set_srch(char *, char *, SEARCHPGM *); +void set_srch_hdr(char *, char *, SEARCHPGM *); +void set_search_by_age(INTVL_S *, SEARCHPGM *, int); +void set_search_by_size(INTVL_S *, SEARCHPGM *); +int non_eh(char *); +void add_eh(char **, char **, char *, int *); +void set_extra_hdrs(char *); +int is_ascii_string(char *); +ACTION_S *combine_inherited_role_guts(ACTION_S *); +int move_filtered_msgs(MAILSTREAM *, MSGNO_S *, char *, int, char *); +void set_some_flags(MAILSTREAM *, MSGNO_S *, long, char **, char **, int, char *); + + +/* + * optional hook for external-program filter test + */ +void (*pith_opt_filter_pattern_cmd)(char **, SEARCHSET *, MAILSTREAM *, long, INTVL_S *); + + +void +role_process_filters(void) +{ + int i; + MAILSTREAM *stream; + MSGNO_S *msgmap; + + for(i = 0; i < ps_global->s_pool.nstream; i++){ + stream = ps_global->s_pool.streams[i]; + if(stream && pine_mail_ping(stream)){ + msgmap = sp_msgmap(stream); + if(msgmap) + reprocess_filter_patterns(stream, msgmap, MI_REFILTERING); + } + } +} + + +int +add_to_pattern(PAT_S *pat, long int rflags) +{ + PAT_LINE_S *new_patline, *patline; + PAT_S *new_pat; + PAT_STATE dummy; + + if(!any_patterns(rflags, &dummy)) + return(0); + + /* need a new patline */ + new_patline = (PAT_LINE_S *)fs_get(sizeof(*new_patline)); + memset((void *)new_patline, 0, sizeof(*new_patline)); + new_patline->type = Literal; + (*cur_pat_h)->dirtypinerc = 1; + + /* and a copy of pat */ + new_pat = copy_pat(pat); + + /* tie together */ + new_patline->first = new_patline->last = new_pat; + new_pat->patline = new_patline; + + + /* + * Manipulate bits directly in pattern list. + * Cur_pat_h is set by any_patterns. + */ + + + /* find last patline */ + for(patline = (*cur_pat_h)->patlinehead; + patline && patline->next; + patline = patline->next) + ; + + /* add new patline to end of list */ + if(patline){ + patline->next = new_patline; + new_patline->prev = patline; + } + else + (*cur_pat_h)->patlinehead = new_patline; + + return(1); +} + + +/* + * Does pattern quoting. Takes the string that the user sees and converts + * it to the config file string. + * + * Args: src -- The source string. + * + * The last arg to add_escapes causes \, and \\ to be replaced with hex + * versions of comma and backslash. That's so we can embed commas in + * list variables without having them act as separators. If the user wants + * a literal comma, they type backslash comma. + * If /, \, or " appear (other than the special cases in previous sentence) + * they are backslash-escaped like \/, \\, or \". + * + * Returns: An allocated string with quoting added. + * + * The caller is responsible for freeing the memory allocated for the answer. + */ +char * +add_pat_escapes(char *src) +{ + return(add_escapes(src, "/\\\"", '\\', "", ",\\")); +} + + +/* + * Undoes the escape quoting done by add_pat_escapes. + * + * Args: src -- The source string. + * + * Returns: A string with backslash quoting removed or NULL. The string starts + * at src and goes until the end of src or until a / is reached. The + * / is not included in the string. /'s may be quoted by preceding + * them with a backslash (\) and \'s may also be quoted by + * preceding them with a \. In fact, \ quotes any character. + * Not quite, \nnn is octal escape, \xXX is hex escape. + * Hex escapes are undone but left with a backslash in front. + * + * The caller is responsible for freeing the memory allocated for the answer. + */ +char * +remove_pat_escapes(char *src) +{ + char *ans = NULL, *q, *p; + int done = 0; + + if(src){ + p = q = (char *)fs_get(strlen(src) + 1); + + while(!done){ + switch(*src){ + case '\\': + src++; + if(*src){ + if(isdigit((unsigned char)*src)){ /* octal escape */ + *p++ = '\\'; + *p++ = (char)read_octal(&src); + } + else if((*src == 'x' || *src == 'X') && + *(src+1) && *(src+2) && isxpair(src+1)){ + *p++ = '\\'; + *p++ = (char)read_hex(src+1); + src += 3; + } + else + *p++ = *src++; + } + + break; + + case '\0': + case '/': + done++; + break; + + default: + *p++ = *src++; + break; + } + } + + *p = '\0'; + + ans = cpystr(q); + fs_give((void **)&q); + } + + return(ans); +} + + +/* + * This takes envelope data and adds the backslash escapes that the user + * would have been responsible for adding if editing manually. + * It just escapes commas and backslashes. + * + * Caller must free result. + */ +char * +add_roletake_escapes(char *src) +{ + return(add_escapes(src, ",\\", '\\', "", "")); +} + +/* + * This function only escapes commas. + */ +char * +add_comma_escapes(char *src) +{ + return(add_escapes(src, ",", '\\', "", "")); +} + + +/* + * These are the global pattern handles which all of the pattern routines + * use. Once we open one of these we usually leave it open until exiting + * pine. The _any versions are only used if we are altering our configuration, + * the _ne (NonEmpty) versions are used routinely. We open the patterns by + * calling either nonempty_patterns (normal use) or any_patterns (config). + * + * There are eight different pinerc variables which contain patterns. They are + * patterns-filters2, patterns-roles, patterns-scores2, patterns-indexcolors, + * patterns-other, and the old patterns, patterns-filters, and patterns-scores. + * The first five are the active patterns variables and the old variable are + * kept around so that we can convert old patterns to new. The reason we + * split it into five separate variables is so that each can independently + * be controlled by the main pinerc or by the exception pinerc. The reason + * for the change to filters2 and scores2 was so we could change the semantics + * of how rules work when there are pieces in the rule that we don't + * understand. We added a rule to detect 8bitSubjects. So a user might have + * a filter that deletes messages with 8bitSubjects. The problem was that + * that same filter in a old patterns-filters pine would match because it + * would ignore the 8bitSubject part of the pattern and match on the rest. + * So we changed the semantics so that rules with unknown pieces would be + * ignored instead of used. We had to change variable names at the same time + * because we were adding the 8bit thing and the old pines are still out + * there. Filters and Scores can both be dangerous. Roles, Colors, and Other + * seem less dangerous so not worth adding a new variable for them. + * + * Each of the eight variables has its own handle and status variables below. + * That means that they operate independently. + * + * Looking at just a single one of those variables, it has four possible + * values. In normal use, we use the current_val of the variable to set + * up the patterns. We do that by calling nonempty_patterns() with the + * appropriate rflags. When editing configurations, we have the other two + * variables to deal with: main_user_val and post_user_val. + * We only ever deal with one of those at a time, so we re-use the variables. + * However, we do sometimes want to deal with one of those and at the same + * time refer to the current current_val. For example, if we are editing + * the post or main user_val for the filters variable, we still want + * to check for new mail. If we find new mail we'll want to call + * process_filter_patterns which uses the current_val for filter patterns. + * That means we have to provide for the case where we are using current_val + * at the same time as we're using one of the user_vals. That's why we have + * both the _ne variables (NonEmpty) and the _any variables. + * + * In any_patterns (and first_pattern...) use_flags may only be set to + * one value at a time, whereas rflags may be more than one value OR'd together. + */ +PAT_HANDLE **cur_pat_h; +static PAT_HANDLE *pattern_h_roles_ne, *pattern_h_roles_any, + *pattern_h_scores_ne, *pattern_h_scores_any, + *pattern_h_filts_ne, *pattern_h_filts_any, + *pattern_h_filts_cfg, + *pattern_h_filts_ne, *pattern_h_filts_any, + *pattern_h_incol_ne, *pattern_h_incol_any, + *pattern_h_other_ne, *pattern_h_other_any, + *pattern_h_srch_ne, *pattern_h_srch_any, + *pattern_h_oldpat_ne, *pattern_h_oldpat_any; + +/* + * These contain the PAT_OPEN_MASK open status and the PAT_USE_MASK use status. + */ +static long *cur_pat_status; +static long pat_status_roles_ne, pat_status_roles_any, + pat_status_scores_ne, pat_status_scores_any, + pat_status_filts_ne, pat_status_filts_any, + pat_status_incol_ne, pat_status_incol_any, + pat_status_other_ne, pat_status_other_any, + pat_status_srch_ne, pat_status_srch_any, + pat_status_oldpat_ne, pat_status_oldpat_any, + pat_status_oldfilt_ne, pat_status_oldfilt_any, + pat_status_oldscore_ne, pat_status_oldscore_any; + +#define SET_PATTYPE(rflags) \ + set_pathandle(rflags); \ + cur_pat_status = \ + ((rflags) & PAT_USE_CURRENT) \ + ? (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_ne : \ + ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_ne : \ + ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_ne : \ + ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_ne : \ + ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_ne : \ + ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_ne : \ + ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_ne : \ + ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_ne :\ + &pat_status_oldpat_ne) \ + : (((rflags) & ROLE_DO_INCOLS) ? &pat_status_incol_any : \ + ((rflags) & ROLE_DO_OTHER) ? &pat_status_other_any : \ + ((rflags) & ROLE_DO_FILTER) ? &pat_status_filts_any : \ + ((rflags) & ROLE_DO_SCORES) ? &pat_status_scores_any : \ + ((rflags) & ROLE_DO_ROLES) ? &pat_status_roles_any : \ + ((rflags) & ROLE_DO_SRCH) ? &pat_status_srch_any : \ + ((rflags) & ROLE_OLD_FILT) ? &pat_status_oldfilt_any :\ + ((rflags) & ROLE_OLD_SCORE) ? &pat_status_oldscore_any:\ + &pat_status_oldpat_any); +#define CANONICAL_RFLAGS(rflags) \ + ((((rflags) & (ROLE_DO_ROLES | ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE)) \ + ? ROLE_DO_ROLES : 0) | \ + (((rflags) & (ROLE_DO_INCOLS | ROLE_INCOL)) \ + ? ROLE_DO_INCOLS : 0) | \ + (((rflags) & (ROLE_DO_SCORES | ROLE_SCORE)) \ + ? ROLE_DO_SCORES : 0) | \ + (((rflags) & (ROLE_DO_FILTER)) \ + ? ROLE_DO_FILTER : 0) | \ + (((rflags) & (ROLE_DO_OTHER)) \ + ? ROLE_DO_OTHER : 0) | \ + (((rflags) & (ROLE_DO_SRCH)) \ + ? ROLE_DO_SRCH : 0) | \ + (((rflags) & (ROLE_OLD_FILT)) \ + ? ROLE_OLD_FILT : 0) | \ + (((rflags) & (ROLE_OLD_SCORE)) \ + ? ROLE_OLD_SCORE : 0) | \ + (((rflags) & (ROLE_OLD_PAT)) \ + ? ROLE_OLD_PAT : 0)) + +#define SETPGMSTATUS(val,yes,no) \ + switch(val){ \ + case PAT_STAT_YES: \ + (yes) = 1; \ + break; \ + case PAT_STAT_NO: \ + (no) = 1; \ + break; \ + case PAT_STAT_EITHER: \ + default: \ + break; \ + } + +#define SET_STATUS(srchin,srchfor,assignto) \ + {char *qq, *pp; \ + int ii; \ + NAMEVAL_S *vv; \ + if((qq = srchstr(srchin, srchfor)) != NULL){ \ + if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \ + for(ii = 0; (vv = role_status_types(ii)); ii++) \ + if(!strucmp(pp, vv->shortname)){ \ + assignto = vv->value; \ + break; \ + } \ + \ + fs_give((void **)&pp); \ + } \ + } \ + } + +#define SET_MSGSTATE(srchin,srchfor,assignto) \ + {char *qq, *pp; \ + int ii; \ + NAMEVAL_S *vv; \ + if((qq = srchstr(srchin, srchfor)) != NULL){ \ + if((pp = remove_pat_escapes(qq+strlen(srchfor))) != NULL){ \ + for(ii = 0; (vv = msg_state_types(ii)); ii++) \ + if(!strucmp(pp, vv->shortname)){ \ + assignto = vv->value; \ + break; \ + } \ + \ + fs_give((void **)&pp); \ + } \ + } \ + } + +#define PATTERN_N (9) + + +void +set_pathandle(long int rflags) +{ + cur_pat_h = (rflags & PAT_USE_CURRENT) + ? ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_ne : + (rflags & ROLE_DO_OTHER) ? &pattern_h_other_ne : + (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_ne : + (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_ne : + (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_ne : + (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_ne : + &pattern_h_oldpat_ne) + : ((rflags & PAT_USE_CHANGED) + ? &pattern_h_filts_cfg + : ((rflags & ROLE_DO_INCOLS) ? &pattern_h_incol_any : + (rflags & ROLE_DO_OTHER) ? &pattern_h_other_any : + (rflags & ROLE_DO_FILTER) ? &pattern_h_filts_any : + (rflags & ROLE_DO_SCORES) ? &pattern_h_scores_any : + (rflags & ROLE_DO_ROLES) ? &pattern_h_roles_any : + (rflags & ROLE_DO_SRCH) ? &pattern_h_srch_any : + &pattern_h_oldpat_any)); +} + + +/* + * Rflags may be more than one pattern type OR'd together. It also contains + * the "use" parameter. + */ +void +open_any_patterns(long int rflags) +{ + long canon_rflags; + + dprint((7, "open_any_patterns(0x%x)\n", rflags)); + + canon_rflags = CANONICAL_RFLAGS(rflags); + + if(canon_rflags & ROLE_DO_INCOLS) + sub_open_any_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_FILTER) + sub_open_any_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_OTHER) + sub_open_any_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_SCORES) + sub_open_any_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_ROLES) + sub_open_any_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_SRCH) + sub_open_any_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_OLD_FILT) + sub_open_any_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_OLD_SCORE) + sub_open_any_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_OLD_PAT) + sub_open_any_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK)); +} + + +/* + * This should only be called with a single pattern type (plus use flags). + * We assume that patterns of this type are closed before this is called. + * This always succeeds unless we run out of memory, in which case fs_get + * never returns. + */ +void +sub_open_any_patterns(long int rflags) +{ + PAT_LINE_S *patline = NULL, *pl = NULL; + char **t = NULL; + struct variable *var; + + SET_PATTYPE(rflags); + + *cur_pat_h = (PAT_HANDLE *)fs_get(sizeof(**cur_pat_h)); + memset((void *)*cur_pat_h, 0, sizeof(**cur_pat_h)); + + if(rflags & ROLE_DO_ROLES) + var = &ps_global->vars[V_PAT_ROLES]; + else if(rflags & ROLE_DO_FILTER) + var = &ps_global->vars[V_PAT_FILTS]; + else if(rflags & ROLE_DO_OTHER) + var = &ps_global->vars[V_PAT_OTHER]; + else if(rflags & ROLE_DO_SCORES) + var = &ps_global->vars[V_PAT_SCORES]; + else if(rflags & ROLE_DO_INCOLS) + var = &ps_global->vars[V_PAT_INCOLS]; + else if(rflags & ROLE_DO_SRCH) + var = &ps_global->vars[V_PAT_SRCH]; + else if(rflags & ROLE_OLD_FILT) + var = &ps_global->vars[V_PAT_FILTS_OLD]; + else if(rflags & ROLE_OLD_SCORE) + var = &ps_global->vars[V_PAT_SCORES_OLD]; + else if(rflags & ROLE_OLD_PAT) + var = &ps_global->vars[V_PATTERNS]; + + switch(rflags & PAT_USE_MASK){ + case PAT_USE_CURRENT: + t = var->current_val.l; + break; + case PAT_USE_CHANGED: + /* + * some trickery to only use changed if actually changed. + * otherwise, use current_val + */ + t = var->is_changed_val ? var->changed_val.l : var->current_val.l; + break; + case PAT_USE_MAIN: + t = var->main_user_val.l; + break; + case PAT_USE_POST: + t = var->post_user_val.l; + break; + } + + if(t){ + for(; t[0] && t[0][0]; t++){ + if(*t && !strncmp("LIT:", *t, 4)) + patline = parse_pat_lit(*t + 4); + else if(*t && !strncmp("FILE:", *t, 5)) + patline = parse_pat_file(*t + 5); + else if(rflags & (PAT_USE_MAIN | PAT_USE_POST) && + patline == NULL && *t && !strcmp(INHERIT, *t)) + patline = parse_pat_inherit(); + else + patline = NULL; + + if(patline){ + if(pl){ + pl->next = patline; + patline->prev = pl; + pl = pl->next; + } + else{ + (*cur_pat_h)->patlinehead = patline; + pl = patline; + } + } + else + q_status_message1(SM_ORDER, 0, 3, + "Invalid patterns line \"%.200s\"", *t); + } + } + + *cur_pat_status = PAT_OPENED | (rflags & PAT_USE_MASK); +} + + +void +close_every_pattern(void) +{ + close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES + | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH + | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT + | PAT_USE_CURRENT); + /* + * Since there is only one set of variables for the other three uses + * we can just close any one of them. There can only be one open at + * a time. + */ + close_patterns(ROLE_DO_INCOLS | ROLE_DO_FILTER | ROLE_DO_SCORES + | ROLE_DO_OTHER | ROLE_DO_ROLES | ROLE_DO_SRCH + | ROLE_OLD_FILT | ROLE_OLD_SCORE | ROLE_OLD_PAT + | PAT_USE_MAIN); +} + + +/* + * Can be called with more than one pattern type. + */ +void +close_patterns(long int rflags) +{ + long canon_rflags; + + dprint((7, "close_patterns(0x%x)\n", rflags)); + + canon_rflags = CANONICAL_RFLAGS(rflags); + + if(canon_rflags & ROLE_DO_INCOLS) + sub_close_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_OTHER) + sub_close_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_FILTER) + sub_close_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_SCORES) + sub_close_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_ROLES) + sub_close_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_DO_SRCH) + sub_close_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_OLD_FILT) + sub_close_patterns(ROLE_OLD_FILT | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_OLD_SCORE) + sub_close_patterns(ROLE_OLD_SCORE | (rflags & PAT_USE_MASK)); + if(canon_rflags & ROLE_OLD_PAT) + sub_close_patterns(ROLE_OLD_PAT | (rflags & PAT_USE_MASK)); +} + + +/* + * Can be called with only a single pattern type. + */ +void +sub_close_patterns(long int rflags) +{ + SET_PATTYPE(rflags); + + if(*cur_pat_h != NULL){ + free_patline(&(*cur_pat_h)->patlinehead); + fs_give((void **)cur_pat_h); + } + + *cur_pat_status = PAT_CLOSED; + + scores_are_used(SCOREUSE_INVALID); +} + + +/* + * Can be called with more than one pattern type. + * Nonempty always uses PAT_USE_CURRENT (the current_val). + */ +int +nonempty_patterns(long int rflags, PAT_STATE *pstate) +{ + return(any_patterns((rflags & ROLE_MASK) | PAT_USE_CURRENT, pstate)); +} + + +/* + * Initializes pstate and parses and sets up appropriate pattern variables. + * May be called with more than one pattern type OR'd together in rflags. + * Pstate will keep track of that and next_pattern et. al. will increment + * through all of those pattern types. + */ +int +any_patterns(long int rflags, PAT_STATE *pstate) +{ + int ret = 0; + long canon_rflags; + + dprint((7, "any_patterns(0x%x)\n", rflags)); + + memset((void *)pstate, 0, sizeof(*pstate)); + pstate->rflags = rflags; + + canon_rflags = CANONICAL_RFLAGS(pstate->rflags); + + if(canon_rflags & ROLE_DO_INCOLS) + ret += sub_any_patterns(ROLE_DO_INCOLS, pstate); + if(canon_rflags & ROLE_DO_OTHER) + ret += sub_any_patterns(ROLE_DO_OTHER, pstate); + if(canon_rflags & ROLE_DO_FILTER) + ret += sub_any_patterns(ROLE_DO_FILTER, pstate); + if(canon_rflags & ROLE_DO_SCORES) + ret += sub_any_patterns(ROLE_DO_SCORES, pstate); + if(canon_rflags & ROLE_DO_ROLES) + ret += sub_any_patterns(ROLE_DO_ROLES, pstate); + if(canon_rflags & ROLE_DO_SRCH) + ret += sub_any_patterns(ROLE_DO_SRCH, pstate); + if(canon_rflags & ROLE_OLD_FILT) + ret += sub_any_patterns(ROLE_OLD_FILT, pstate); + if(canon_rflags & ROLE_OLD_SCORE) + ret += sub_any_patterns(ROLE_OLD_SCORE, pstate); + if(canon_rflags & ROLE_OLD_PAT) + ret += sub_any_patterns(ROLE_OLD_PAT, pstate); + + return(ret); +} + + +int +sub_any_patterns(long int rflags, PAT_STATE *pstate) +{ + SET_PATTYPE(rflags | (pstate->rflags & PAT_USE_MASK)); + + if(*cur_pat_h && + (((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT && + (*cur_pat_status & PAT_USE_MASK) != PAT_USE_CURRENT) || + ((pstate->rflags & PAT_USE_MASK) != PAT_USE_CURRENT && + ((*cur_pat_status & PAT_OPEN_MASK) != PAT_OPENED || + (*cur_pat_status & PAT_USE_MASK) != + (pstate->rflags & PAT_USE_MASK))))) + close_patterns(rflags | (pstate->rflags & PAT_USE_MASK)); + + /* open_any always succeeds */ + if(!*cur_pat_h && ((*cur_pat_status & PAT_OPEN_MASK) == PAT_CLOSED)) + open_any_patterns(rflags | (pstate->rflags & PAT_USE_MASK)); + + if(!*cur_pat_h){ /* impossible */ + *cur_pat_status = PAT_CLOSED; + return(0); + } + + /* + * Opening nonempty can fail. That just means there aren't any + * patterns of that type. + */ + if((pstate->rflags & PAT_USE_MASK) == PAT_USE_CURRENT && + !(*cur_pat_h)->patlinehead) + *cur_pat_status = (PAT_OPEN_FAILED | PAT_USE_CURRENT); + + return(((*cur_pat_status & PAT_OPEN_MASK) == PAT_OPENED) ? 1 : 0); +} + + +int +edit_pattern(PAT_S *newpat, int pos, long int rflags) +{ + PAT_S *oldpat; + PAT_LINE_S *tpatline; + int i; + PAT_STATE pstate; + + if(!any_patterns(rflags, &pstate)) return(1); + + for(i = 0, tpatline = (*cur_pat_h)->patlinehead; + i < pos && tpatline; tpatline = tpatline->next, i++); + if(i != pos) return(1); + oldpat = tpatline->first; + free_pat(&oldpat); + tpatline->first = tpatline->last = newpat; + newpat->patline = tpatline; + tpatline->dirty = 1; + + (*cur_pat_h)->dirtypinerc = 1; + write_patterns(rflags); + + return(0); +} + +int +add_pattern(PAT_S *newpat, long int rflags) +{ + PAT_LINE_S *tpatline, *newpatline; + PAT_STATE pstate; + + any_patterns(rflags, &pstate); + + for(tpatline = (*cur_pat_h)->patlinehead; + tpatline && tpatline->next ; tpatline = tpatline->next); + newpatline = (PAT_LINE_S *)fs_get(sizeof(PAT_LINE_S)); + if(tpatline) + tpatline->next = newpatline; + else + (*cur_pat_h)->patlinehead = newpatline; + memset((void *)newpatline, 0, sizeof(PAT_LINE_S)); + newpatline->prev = tpatline; + newpatline->first = newpatline->last = newpat; + newpatline->type = Literal; + newpat->patline = newpatline; + newpatline->dirty = 1; + + (*cur_pat_h)->dirtypinerc = 1; + write_patterns(rflags); + + return(0); +} + +int +delete_pattern(int pos, long int rflags) +{ + PAT_LINE_S *tpatline; + int i; + PAT_STATE pstate; + + if(!any_patterns(rflags, &pstate)) return(1); + + for(i = 0, tpatline = (*cur_pat_h)->patlinehead; + i < pos && tpatline; tpatline = tpatline->next, i++); + if(i != pos) return(1); + + if(tpatline == (*cur_pat_h)->patlinehead) + (*cur_pat_h)->patlinehead = tpatline->next; + if(tpatline->prev) tpatline->prev->next = tpatline->next; + if(tpatline->next) tpatline->next->prev = tpatline->prev; + tpatline->prev = NULL; + tpatline->next = NULL; + + free_patline(&tpatline); + + (*cur_pat_h)->dirtypinerc = 1; + write_patterns(rflags); + + return(0); +} + +int +shuffle_pattern(int pos, int up, long int rflags) +{ + PAT_LINE_S *tpatline, *shufpatline; + int i; + PAT_STATE pstate; + + if(!any_patterns(rflags, &pstate)) return(1); + + for(i = 0, tpatline = (*cur_pat_h)->patlinehead; + i < pos && tpatline; tpatline = tpatline->next, i++); + if(i != pos) return(1); + + if(up == 1){ + if(tpatline->prev == NULL) return(1); + shufpatline = tpatline->prev; + tpatline->prev = shufpatline->prev; + if(shufpatline->prev) + shufpatline->prev->next = tpatline; + if(tpatline->next) + tpatline->next->prev = shufpatline; + shufpatline->next = tpatline->next; + shufpatline->prev = tpatline; + tpatline->next = shufpatline; + if(shufpatline == (*cur_pat_h)->patlinehead) + (*cur_pat_h)->patlinehead = tpatline; + } + else if(up == -1){ + if(tpatline->next == NULL) return(1); + shufpatline = tpatline->next; + tpatline->next = shufpatline->next; + if(shufpatline->next) + shufpatline->next->prev = tpatline; + if(tpatline->prev) + tpatline->prev->next = shufpatline; + shufpatline->prev = tpatline->prev; + shufpatline->next = tpatline; + tpatline->prev = shufpatline; + if(tpatline == (*cur_pat_h)->patlinehead) + (*cur_pat_h)->patlinehead = shufpatline; + } + else return(1); + + shufpatline->dirty = 1; + tpatline->dirty = 1; + + (*cur_pat_h)->dirtypinerc = 1; + write_patterns(rflags); + + return(0); +} + +PAT_LINE_S * +parse_pat_lit(char *litpat) +{ + PAT_LINE_S *patline; + PAT_S *pat; + + patline = (PAT_LINE_S *)fs_get(sizeof(*patline)); + memset((void *)patline, 0, sizeof(*patline)); + patline->type = Literal; + + + if((pat = parse_pat(litpat)) != NULL){ + pat->patline = patline; + patline->first = pat; + patline->last = pat; + } + + return(patline); +} + + +/* + * This always returns a patline even if we can't read the file. The patline + * returned will say readonly in the worst case and there will be no patterns. + * If the file doesn't exist, this creates it if possible. + */ +PAT_LINE_S * +parse_pat_file(char *filename) +{ +#define BUF_SIZE 5000 + PAT_LINE_S *patline; + PAT_S *pat, *p; + char path[MAXPATH+1], buf[BUF_SIZE]; + char *dir, *q; + FILE *fp; + int ok = 0, some_pats = 0; + struct variable *vars = ps_global->vars; + + signature_path(filename, path, MAXPATH); + + if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, path)){ + q_status_message1(SM_ORDER | SM_DING, 3, 4, + "Can't use Roles file outside of %.200s", + VAR_OPER_DIR); + return(NULL); + } + + patline = (PAT_LINE_S *)fs_get(sizeof(*patline)); + memset((void *)patline, 0, sizeof(*patline)); + patline->type = File; + patline->filename = cpystr(filename); + patline->filepath = cpystr(path); + + if((q = last_cmpnt(path)) != NULL){ + int save; + + save = *--q; + *q = '\0'; + dir = cpystr(*path ? path : "/"); + *q = save; + } + else + dir = cpystr("."); + +#if defined(DOS) || defined(OS2) + /* + * If the dir has become a drive letter and : (e.g. "c:") + * then append a "\". The library function access() in the + * win 16 version of MSC seems to require this. + */ + if(isalpha((unsigned char) *dir) + && *(dir+1) == ':' && *(dir+2) == '\0'){ + *(dir+2) = '\\'; + *(dir+3) = '\0'; + } +#endif /* DOS || OS2 */ + + /* + * Even if we can edit the file itself, we aren't going + * to be able to change it unless we can also write in + * the directory that contains it (because we write into a + * temp file and then rename). + */ + if(can_access(dir, EDIT_ACCESS) != 0) + patline->readonly = 1; + + if(can_access(path, EDIT_ACCESS) == 0){ + if(patline->readonly) + q_status_message1(SM_ORDER, 0, 3, + "Pattern file directory (%.200s) is ReadOnly", dir); + } + else if(can_access(path, READ_ACCESS) == 0) + patline->readonly = 1; + + if(can_access(path, ACCESS_EXISTS) == 0){ + if((fp = our_fopen(path, "rb")) != NULL){ + /* Check to see if this is a valid patterns file */ + if(fp_file_size(fp) <= 0L) + ok++; + else{ + size_t len; + + len = strlen(PATTERN_MAGIC); + if(fread(buf, sizeof(char), len+3, fp) == len+3){ + buf[len+3] = '\0'; + buf[len] = '\0'; + if(strcmp(buf, PATTERN_MAGIC) == 0){ + if(atoi(PATTERN_FILE_VERS) < atoi(buf + len + 1)) + q_status_message1(SM_ORDER, 0, 4, + "Pattern file \"%.200s\" is made by newer Alpine, will try to use it anyway", + filename); + + ok++; + some_pats++; + /* toss rest of first line */ + (void)fgets(buf, BUF_SIZE, fp); + } + } + } + + if(!ok){ + patline->readonly = 1; + q_status_message1(SM_ORDER | SM_DING, 3, 4, + "\"%.200s\" is not a Pattern file", path); + } + + p = NULL; + while(some_pats && fgets(buf, BUF_SIZE, fp) != NULL){ + if((pat = parse_pat(buf)) != NULL){ + pat->patline = patline; + if(!patline->first) + patline->first = pat; + + patline->last = pat; + + if(p){ + p->next = pat; + pat->prev = p; + p = p->next; + } + else + p = pat; + } + } + + (void)fclose(fp); + } + else{ + patline->readonly = 1; + q_status_message2(SM_ORDER | SM_DING, 3, 4, + "Error \"%.200s\" reading pattern file \"%.200s\"", + error_description(errno), path); + } + } + else{ /* doesn't exist yet, try to create it */ + if(patline->readonly) + q_status_message1(SM_ORDER, 0, 3, + "Pattern file directory (%.200s) is ReadOnly", dir); + else{ + /* + * We try to create it by making up an empty patline and calling + * write_pattern_file. + */ + patline->dirty = 1; + if(write_pattern_file(NULL, patline) != 0){ + patline->readonly = 1; + patline->dirty = 0; + q_status_message1(SM_ORDER | SM_DING, 3, 4, + "Error creating pattern file \"%.200s\"", + path); + } + } + } + + if(dir) + fs_give((void **)&dir); + + return(patline); +} + + +PAT_LINE_S * +parse_pat_inherit(void) +{ + PAT_LINE_S *patline; + PAT_S *pat; + + patline = (PAT_LINE_S *)fs_get(sizeof(*patline)); + memset((void *)patline, 0, sizeof(*patline)); + patline->type = Inherit; + + pat = (PAT_S *)fs_get(sizeof(*pat)); + memset((void *)pat, 0, sizeof(*pat)); + pat->inherit = 1; + + pat->patline = patline; + patline->first = pat; + patline->last = pat; + + return(patline); +} + + +/* + * There are three forms that a PATTERN_S has at various times. There is + * the actual PATTERN_S struct which is used internally and is used whenever + * we are actually doing something with the pattern, like filtering or + * something. There is the version that goes in the config file. And there + * is the version the user edits. + * + * To go between these three forms we have the helper routines + * + * pattern_to_config + * config_to_pattern + * pattern_to_editlist + * editlist_to_pattern + * + * Here's what is supposed to be happening. A PATTERN_S is a linked list + * of strings with nothing escaped. That is, a backslash or a comma is + * just in there as a backslash or comma. + * + * The version the user edits is very similar. Because we have historically + * used commas as separators the user has always had to enter a \, in order + * to put a real comma in one of the items. That is the only difference + * between a PATTERN_S string and the editlist strings. Note that backslashes + * themselves are not escaped. A backslash which is not followed by a comma + * is a backslash. It doesn't escape the following character. That's a bit + * odd, it is that way because most people will never know about this + * backslash stuff but PC-Pine users may have backslashes in folder names. + * + * The version that goes in the config file has a few transformations made. + * PATTERN_S intermediate_form Config string + * , \, \x2C + * \ \\ \x5C + * / \/ + * " \" + * + * The commas are turned into hex commas so that we can tell the separators + * in the comma-separated lists from those commas. + * The backslashes are escaped because they escape commas. + * The /'s are escaped because they separate pattern pieces. + * The "'s are escaped because they are significant to parse_list when + * parsing the config file. + * hubert - 2004-04-01 + * (date is only coincidental!) + * + * Addendum. The not's are handled separately from all the strings. Not sure + * why that is or if there is a good reason. Nevertheless, now is not the + * time to figure it out so leave it that way. + * hubert - 2004-07-14 + */ +PAT_S * +parse_pat(char *str) +{ + PAT_S *pat = NULL; + char *p, *q, *astr, *pstr; + int backslashed; +#define PTRN "pattern=" +#define PTRNLEN 8 +#define ACTN "action=" +#define ACTNLEN 7 + + if(str) + removing_trailing_white_space(str); + + if(!str || !*str || *str == '#') + return(pat); + + pat = (PAT_S *)fs_get(sizeof(*pat)); + memset((void *)pat, 0, sizeof(*pat)); + + if((p = srchstr(str, PTRN)) != NULL){ + pat->patgrp = (PATGRP_S *)fs_get(sizeof(*pat->patgrp)); + memset((void *)pat->patgrp, 0, sizeof(*pat->patgrp)); + pat->patgrp->fldr_type = FLDR_DEFL; + pat->patgrp->inabook = IAB_DEFL; + pat->patgrp->cat_lim = -1L; + + if((pstr = copy_quoted_string_asis(p+PTRNLEN)) != NULL){ + /* move to next slash */ + for(q=pstr, backslashed=0; *q; q++){ + switch(*q){ + case '\\': + backslashed = !backslashed; + break; + + case '/': + if(!backslashed){ + parse_patgrp_slash(q, pat->patgrp); + if(pat->patgrp->bogus && !pat->raw) + pat->raw = cpystr(str); + } + + /* fall through */ + + default: + backslashed = 0; + break; + } + } + + /* we always force a nickname */ + if(!pat->patgrp->nick) + pat->patgrp->nick = cpystr("Alternate Role"); + + fs_give((void **)&pstr); + } + } + + if((p = srchstr(str, ACTN)) != NULL){ + pat->action = (ACTION_S *)fs_get(sizeof(*pat->action)); + memset((void *)pat->action, 0, sizeof(*pat->action)); + pat->action->startup_rule = IS_NOTSET; + pat->action->repl_type = ROLE_REPL_DEFL; + pat->action->forw_type = ROLE_FORW_DEFL; + pat->action->comp_type = ROLE_COMP_DEFL; + pat->action->nick = cpystr((pat->patgrp && pat->patgrp->nick + && pat->patgrp->nick[0]) + ? pat->patgrp->nick : "Alternate Role"); + + if((astr = copy_quoted_string_asis(p+ACTNLEN)) != NULL){ + /* move to next slash */ + for(q=astr, backslashed=0; *q; q++){ + switch(*q){ + case '\\': + backslashed = !backslashed; + break; + + case '/': + if(!backslashed){ + parse_action_slash(q, pat->action); + if(pat->action->bogus && !pat->raw) + pat->raw = cpystr(str); + } + + /* fall through */ + + default: + backslashed = 0; + break; + } + } + + fs_give((void **)&astr); + + if(!pat->action->is_a_score) + pat->action->scoreval = 0L; + + if(pat->action->is_a_filter) + pat->action->kill = (pat->action->folder + || pat->action->kill == -1) ? 0 : 1; + else{ + if(pat->action->folder) + free_pattern(&pat->action->folder); + } + + if(!pat->action->is_a_role){ + pat->action->repl_type = ROLE_NOTAROLE_DEFL; + pat->action->forw_type = ROLE_NOTAROLE_DEFL; + pat->action->comp_type = ROLE_NOTAROLE_DEFL; + if(pat->action->from) + mail_free_address(&pat->action->from); + if(pat->action->replyto) + mail_free_address(&pat->action->replyto); + if(pat->action->fcc) + fs_give((void **)&pat->action->fcc); + if(pat->action->litsig) + fs_give((void **)&pat->action->litsig); + if(pat->action->sig) + fs_give((void **)&pat->action->sig); + if(pat->action->template) + fs_give((void **)&pat->action->template); + if(pat->action->cstm) + free_list_array(&pat->action->cstm); + if(pat->action->smtp) + free_list_array(&pat->action->smtp); + if(pat->action->nntp) + free_list_array(&pat->action->nntp); + if(pat->action->inherit_nick) + fs_give((void **)&pat->action->inherit_nick); + } + + if(!pat->action->is_a_incol){ + if(pat->action->incol) + free_color_pair(&pat->action->incol); + } + + if(!pat->action->is_a_other){ + pat->action->sort_is_set = 0; + pat->action->sortorder = 0; + pat->action->revsort = 0; + pat->action->startup_rule = IS_NOTSET; + if(pat->action->index_format) + fs_give((void **)&pat->action->index_format); + } + } + } + + return(pat); +} + + +/* + * Fill in one member of patgrp from str. + * + * The multiple constant strings are lame but it evolved this way from + * previous versions and isn't worth fixing. + */ +void +parse_patgrp_slash(char *str, PATGRP_S *patgrp) +{ + char *p; + + if(!patgrp) + panic("NULL patgrp to parse_patgrp_slash"); + else if(!(str && *str)){ + panic("NULL or empty string to parse_patgrp_slash"); + patgrp->bogus = 1; + } + else if(!strncmp(str, "/NICK=", 6)) + patgrp->nick = remove_pat_escapes(str+6); + else if(!strncmp(str, "/COMM=", 6)) + patgrp->comment = remove_pat_escapes(str+6); + else if(!strncmp(str, "/TO=", 4) || !strncmp(str, "/!TO=", 5)) + patgrp->to = parse_pattern("TO", str, 1); + else if(!strncmp(str, "/CC=", 4) || !strncmp(str, "/!CC=", 5)) + patgrp->cc = parse_pattern("CC", str, 1); + else if(!strncmp(str, "/RECIP=", 7) || !strncmp(str, "/!RECIP=", 8)) + patgrp->recip = parse_pattern("RECIP", str, 1); + else if(!strncmp(str, "/PARTIC=", 8) || !strncmp(str, "/!PARTIC=", 9)) + patgrp->partic = parse_pattern("PARTIC", str, 1); + else if(!strncmp(str, "/FROM=", 6) || !strncmp(str, "/!FROM=", 7)) + patgrp->from = parse_pattern("FROM", str, 1); + else if(!strncmp(str, "/SENDER=", 8) || !strncmp(str, "/!SENDER=", 9)) + patgrp->sender = parse_pattern("SENDER", str, 1); + else if(!strncmp(str, "/NEWS=", 6) || !strncmp(str, "/!NEWS=", 7)) + patgrp->news = parse_pattern("NEWS", str, 1); + else if(!strncmp(str, "/SUBJ=", 6) || !strncmp(str, "/!SUBJ=", 7)) + patgrp->subj = parse_pattern("SUBJ", str, 1); + else if(!strncmp(str, "/ALL=", 5) || !strncmp(str, "/!ALL=", 6)) + patgrp->alltext = parse_pattern("ALL", str, 1); + else if(!strncmp(str, "/BODY=", 6) || !strncmp(str, "/!BODY=", 7)) + patgrp->bodytext = parse_pattern("BODY", str, 1); + else if(!strncmp(str, "/KEY=", 5) || !strncmp(str, "/!KEY=", 6)) + patgrp->keyword = parse_pattern("KEY", str, 1); + else if(!strncmp(str, "/CHAR=", 6) || !strncmp(str, "/!CHAR=", 7)) + patgrp->charsets = parse_pattern("CHAR", str, 1); + else if(!strncmp(str, "/FOLDER=", 8) || !strncmp(str, "/!FOLDER=", 9)) + patgrp->folder = parse_pattern("FOLDER", str, 1); + else if(!strncmp(str, "/ABOOKS=", 8) || !strncmp(str, "/!ABOOKS=", 9)) + patgrp->abooks = parse_pattern("ABOOKS", str, 1); + /* + * A problem with arbhdrs is that more than one of them can appear in + * the string. We come back here the second time, but we already took + * care of the whole thing on the first pass. Hence the check for + * arbhdr already set. + */ + else if(!strncmp(str, "/ARB", 4) || !strncmp(str, "/!ARB", 5) + || !strncmp(str, "/EARB", 5) || !strncmp(str, "/!EARB", 6)){ + if(!patgrp->arbhdr) + patgrp->arbhdr = parse_arbhdr(str); + /* else do nothing */ + } + else if(!strncmp(str, "/SENTDATE=", 10)) + patgrp->age_uses_sentdate = 1; + else if(!strncmp(str, "/SCOREI=", 8)){ + if((p = remove_pat_escapes(str+8)) != NULL){ + if((patgrp->score = parse_intvl(p)) != NULL) + patgrp->do_score = 1; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/AGE=", 5)){ + if((p = remove_pat_escapes(str+5)) != NULL){ + if((patgrp->age = parse_intvl(p)) != NULL) + patgrp->do_age = 1; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/SIZE=", 6)){ + if((p = remove_pat_escapes(str+6)) != NULL){ + if((patgrp->size = parse_intvl(p)) != NULL) + patgrp->do_size = 1; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/CATCMD=", 8)){ + if((p = remove_pat_escapes(str+8)) != NULL){ + int commas = 0; + char *q; + + /* count elements in list */ + for(q = p; q && *q; q++) + if(*q == ',') + commas++; + + patgrp->category_cmd = parse_list(p, commas+1, PL_REMSURRQUOT,NULL); + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/CATVAL=", 8)){ + if((p = remove_pat_escapes(str+8)) != NULL){ + if((patgrp->cat = parse_intvl(p)) != NULL) + patgrp->do_cat = 1; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/CATLIM=", 8)){ + if((p = remove_pat_escapes(str+8)) != NULL){ + long i; + + i = atol(p); + patgrp->cat_lim = i; + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/FLDTYPE=", 9)){ + if((p = remove_pat_escapes(str+9)) != NULL){ + int i; + NAMEVAL_S *v; + + for(i = 0; (v = pat_fldr_types(i)); i++) + if(!strucmp(p, v->shortname)){ + patgrp->fldr_type = v->value; + break; + } + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/AFROM=", 7)){ + if((p = remove_pat_escapes(str+7)) != NULL){ + int i; + NAMEVAL_S *v; + + for(i = 0; (v = inabook_fldr_types(i)); i++) + if(!strucmp(p, v->shortname)){ + patgrp->inabook |= v->value; + break; + } + + /* to match old semantics */ + patgrp->inabook |= IAB_FROM; + patgrp->inabook |= IAB_REPLYTO; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/AFROMA=", 8)){ + if((p = remove_pat_escapes(str+8)) != NULL){ + + /* make sure AFROMA comes after AFROM in config lines */ + patgrp->inabook &= ~IAB_ADDR_MASK; + + if(strchr(p, 'F')) + patgrp->inabook |= IAB_FROM; + if(strchr(p, 'R')) + patgrp->inabook |= IAB_REPLYTO; + if(strchr(p, 'S')) + patgrp->inabook |= IAB_SENDER; + if(strchr(p, 'T')) + patgrp->inabook |= IAB_TO; + if(strchr(p, 'C')) + patgrp->inabook |= IAB_CC; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/STATN=", 7)){ + SET_STATUS(str,"/STATN=",patgrp->stat_new); + } + else if(!strncmp(str, "/STATR=", 7)){ + SET_STATUS(str,"/STATR=",patgrp->stat_rec); + } + else if(!strncmp(str, "/STATI=", 7)){ + SET_STATUS(str,"/STATI=",patgrp->stat_imp); + } + else if(!strncmp(str, "/STATA=", 7)){ + SET_STATUS(str,"/STATA=",patgrp->stat_ans); + } + else if(!strncmp(str, "/STATD=", 7)){ + SET_STATUS(str,"/STATD=",patgrp->stat_del); + } + else if(!strncmp(str, "/8BITS=", 7)){ + SET_STATUS(str,"/8BITS=",patgrp->stat_8bitsubj); + } + else if(!strncmp(str, "/BOM=", 5)){ + SET_STATUS(str,"/BOM=",patgrp->stat_bom); + } + else if(!strncmp(str, "/BOY=", 5)){ + SET_STATUS(str,"/BOY=",patgrp->stat_boy); + } + else{ + char save; + + patgrp->bogus = 1; + + if((p = strindex(str, '=')) != NULL){ + save = *(p+1); + *(p+1) = '\0'; + } + + dprint((1, + "parse_patgrp_slash(%.20s): unrecognized in \"%s\"\n", + str ? str : "?", + (patgrp && patgrp->nick) ? patgrp->nick : "")); + q_status_message4(SM_ORDER, 1, 3, + "Warning: unrecognized pattern element \"%.20s\"%.20s%.20s%.20s", + str, patgrp->nick ? " in rule \"" : "", + patgrp->nick ? patgrp->nick : "", patgrp->nick ? "\"" : ""); + + if(p) + *(p+1) = save; + } +} + + +/* + * Fill in one member of action struct from str. + * + * The multiple constant strings are lame but it evolved this way from + * previous versions and isn't worth fixing. + */ +void +parse_action_slash(char *str, ACTION_S *action) +{ + char *p; + int stateval, i; + NAMEVAL_S *v; + + if(!action) + panic("NULL action to parse_action_slash"); + else if(!(str && *str)) + panic("NULL or empty string to parse_action_slash"); + else if(!strncmp(str, "/ROLE=1", 7)) + action->is_a_role = 1; + else if(!strncmp(str, "/OTHER=1", 8)) + action->is_a_other = 1; + else if(!strncmp(str, "/ISINCOL=1", 10)) + action->is_a_incol = 1; + else if(!strncmp(str, "/ISSRCH=1", 9)) + action->is_a_srch = 1; + /* + * This is unfortunate. If a new filter is set to only set + * state bits it will be interpreted by an older pine which + * doesn't have that feature like a filter that is set to Delete. + * So we change the filter indicator to FILTER=2 to disable the + * filter for older versions. + */ + else if(!strncmp(str, "/FILTER=1", 9) || !strncmp(str, "/FILTER=2", 9)) + action->is_a_filter = 1; + else if(!strncmp(str, "/ISSCORE=1", 10)) + action->is_a_score = 1; + else if(!strncmp(str, "/SCORE=", 7)){ + if((p = remove_pat_escapes(str+7)) != NULL){ + long i; + + i = atol(p); + if(i >= SCORE_MIN && i <= SCORE_MAX) + action->scoreval = i; + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/SCOREHDRTOK=", 13)) + action->scorevalhdrtok = config_to_hdrtok(str+13); + else if(!strncmp(str, "/FOLDER=", 8)) + action->folder = parse_pattern("FOLDER", str, 1); + else if(!strncmp(str, "/KEYSET=", 8)) + action->keyword_set = parse_pattern("KEYSET", str, 1); + else if(!strncmp(str, "/KEYCLR=", 8)) + action->keyword_clr = parse_pattern("KEYCLR", str, 1); + else if(!strncmp(str, "/NOKILL=", 8)) + action->kill = -1; + else if(!strncmp(str, "/NOTDEL=", 8)) + action->move_only_if_not_deleted = 1; + else if(!strncmp(str, "/NONTERM=", 9)) + action->non_terminating = 1; + else if(!strncmp(str, "/STATI=", 7)){ + stateval = ACT_STAT_LEAVE; + SET_MSGSTATE(str,"/STATI=",stateval); + switch(stateval){ + case ACT_STAT_LEAVE: + break; + case ACT_STAT_SET: + action->state_setting_bits |= F_FLAG; + break; + case ACT_STAT_CLEAR: + action->state_setting_bits |= F_UNFLAG; + break; + } + } + else if(!strncmp(str, "/STATD=", 7)){ + stateval = ACT_STAT_LEAVE; + SET_MSGSTATE(str,"/STATD=",stateval); + switch(stateval){ + case ACT_STAT_LEAVE: + break; + case ACT_STAT_SET: + action->state_setting_bits |= F_DEL; + break; + case ACT_STAT_CLEAR: + action->state_setting_bits |= F_UNDEL; + break; + } + } + else if(!strncmp(str, "/STATA=", 7)){ + stateval = ACT_STAT_LEAVE; + SET_MSGSTATE(str,"/STATA=",stateval); + switch(stateval){ + case ACT_STAT_LEAVE: + break; + case ACT_STAT_SET: + action->state_setting_bits |= F_ANS; + break; + case ACT_STAT_CLEAR: + action->state_setting_bits |= F_UNANS; + break; + } + } + else if(!strncmp(str, "/STATN=", 7)){ + stateval = ACT_STAT_LEAVE; + SET_MSGSTATE(str,"/STATN=",stateval); + switch(stateval){ + case ACT_STAT_LEAVE: + break; + case ACT_STAT_SET: + action->state_setting_bits |= F_UNSEEN; + break; + case ACT_STAT_CLEAR: + action->state_setting_bits |= F_SEEN; + break; + } + } + else if(!strncmp(str, "/RTYPE=", 7)){ + /* reply type */ + action->repl_type = ROLE_REPL_DEFL; + if((p = remove_pat_escapes(str+7)) != NULL){ + for(i = 0; (v = role_repl_types(i)); i++) + if(!strucmp(p, v->shortname)){ + action->repl_type = v->value; + break; + } + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/FTYPE=", 7)){ + /* forward type */ + action->forw_type = ROLE_FORW_DEFL; + if((p = remove_pat_escapes(str+7)) != NULL){ + for(i = 0; (v = role_forw_types(i)); i++) + if(!strucmp(p, v->shortname)){ + action->forw_type = v->value; + break; + } + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/CTYPE=", 7)){ + /* compose type */ + action->comp_type = ROLE_COMP_DEFL; + if((p = remove_pat_escapes(str+7)) != NULL){ + for(i = 0; (v = role_comp_types(i)); i++) + if(!strucmp(p, v->shortname)){ + action->comp_type = v->value; + break; + } + + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/FROM=", 6)){ + /* get the from */ + if((p = remove_pat_escapes(str+6)) != NULL){ + rfc822_parse_adrlist(&action->from, p, + ps_global->maildomain); + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/REPL=", 6)){ + /* get the reply-to */ + if((p = remove_pat_escapes(str+6)) != NULL){ + rfc822_parse_adrlist(&action->replyto, p, + ps_global->maildomain); + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/FCC=", 5)) + action->fcc = remove_pat_escapes(str+5); + else if(!strncmp(str, "/LSIG=", 6)) + action->litsig = remove_pat_escapes(str+6); + else if(!strncmp(str, "/SIG=", 5)) + action->sig = remove_pat_escapes(str+5); + else if(!strncmp(str, "/TEMPLATE=", 10)) + action->template = remove_pat_escapes(str+10); + /* get the custom headers */ + else if(!strncmp(str, "/CSTM=", 6)){ + if((p = remove_pat_escapes(str+6)) != NULL){ + int commas = 0; + char *q; + + /* count elements in list */ + for(q = p; q && *q; q++) + if(*q == ',') + commas++; + + action->cstm = parse_list(p, commas+1, 0, NULL); + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/SMTP=", 6)){ + if((p = remove_pat_escapes(str+6)) != NULL){ + int commas = 0; + char *q; + + /* count elements in list */ + for(q = p; q && *q; q++) + if(*q == ',') + commas++; + + action->smtp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL); + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/NNTP=", 6)){ + if((p = remove_pat_escapes(str+6)) != NULL){ + int commas = 0; + char *q; + + /* count elements in list */ + for(q = p; q && *q; q++) + if(*q == ',') + commas++; + + action->nntp = parse_list(p, commas+1, PL_REMSURRQUOT, NULL); + fs_give((void **)&p); + } + } + else if(!strncmp(str, "/INICK=", 7)) + action->inherit_nick = remove_pat_escapes(str+7); + else if(!strncmp(str, "/INCOL=", 7)){ + if((p = remove_pat_escapes(str+7)) != NULL){ + char *fg = NULL, *bg = NULL, *z; + + /* + * Color should look like + * /FG=white/BG=red + */ + if((z = srchstr(p, "/FG=")) != NULL) + fg = remove_pat_escapes(z+4); + if((z = srchstr(p, "/BG=")) != NULL) + bg = remove_pat_escapes(z+4); + + if(fg && *fg && bg && *bg) + action->incol = new_color_pair(fg, bg); + + if(fg) + fs_give((void **)&fg); + if(bg) + fs_give((void **)&bg); + fs_give((void **)&p); + } + } + /* per-folder sort */ + else if(!strncmp(str, "/SORT=", 6)){ + if((p = remove_pat_escapes(str+6)) != NULL){ + SortOrder def_sort; + int def_sort_rev; + + if(decode_sort(p, &def_sort, &def_sort_rev) != -1){ + action->sort_is_set = 1; + action->sortorder = def_sort; + action->revsort = (def_sort_rev ? 1 : 0); + } + + fs_give((void **)&p); + } + } + /* per-folder index-format */ + else if(!strncmp(str, "/IFORM=", 7)) + action->index_format = remove_pat_escapes(str+7); + /* per-folder startup-rule */ + else if(!strncmp(str, "/START=", 7)){ + if((p = remove_pat_escapes(str+7)) != NULL){ + for(i = 0; (v = startup_rules(i)); i++) + if(!strucmp(p, S_OR_L(v))){ + action->startup_rule = v->value; + break; + } + + fs_give((void **)&p); + } + } + else{ + char save; + + action->bogus = 1; + + if((p = strindex(str, '=')) != NULL){ + save = *(p+1); + *(p+1) = '\0'; + } + + dprint((1, + "parse_action_slash(%.20s): unrecognized in \"%s\"\n", + str ? str : "?", + (action && action->nick) ? action->nick : "")); + q_status_message4(SM_ORDER, 1, 3, + "Warning: unrecognized pattern action \"%.20s\"%.20s%.20s%.20s", + str, action->nick ? " in rule \"" : "", + action->nick ? action->nick : "", action->nick ? "\"" : ""); + + if(p) + *(p+1) = save; + } +} + + +/* + * Str looks like (min,max) or a comma-separated list of these. + * + * Parens are optional if unambiguous, whitespace is ignored. + * If min is left out it is -INF. If max is left out it is INF. + * If only one number and no comma number is min and max is INF. + * + * Returns the INTVL_S list. + */ +INTVL_S * +parse_intvl(char *str) +{ + char *q; + long left, right; + INTVL_S *ret = NULL, **next; + + if(!str) + return(ret); + + q = str; + + for(;;){ + left = right = INTVL_UNDEF; + + /* skip to first number */ + while(isspace((unsigned char) *q) || *q == LPAREN) + q++; + + /* min number */ + if(*q == COMMA || !struncmp(q, "-INF", 4)) + left = - INTVL_INF; + else if(*q == '-' || isdigit((unsigned char) *q)) + left = atol(q); + + if(left != INTVL_UNDEF){ + /* skip to second number */ + while(*q && *q != COMMA && *q != RPAREN) + q++; + if(*q == COMMA) + q++; + while(isspace((unsigned char) *q)) + q++; + + /* max number */ + if(*q == '\0' || *q == RPAREN || !struncmp(q, "INF", 3)) + right = INTVL_INF; + else if(*q == '-' || isdigit((unsigned char) *q)) + right = atol(q); + } + + if(left == INTVL_UNDEF || right == INTVL_UNDEF + || left > right){ + if(left != INTVL_UNDEF || right != INTVL_UNDEF || *q){ + if(left != INTVL_UNDEF && right != INTVL_UNDEF + && left > right) + q_status_message1(SM_ORDER, 3, 5, + _("Error: Interval \"%s\", min > max"), str); + else + q_status_message1(SM_ORDER, 3, 5, + _("Error: Interval \"%s\": syntax is (min,max)"), str); + + if(ret) + free_intvl(&ret); + + ret = NULL; + } + + break; + } + else{ + if(!ret){ + ret = (INTVL_S *) fs_get(sizeof(*ret)); + memset((void *) ret, 0, sizeof(*ret)); + ret->imin = left; + ret->imax = right; + next = &ret->next; + } + else{ + *next = (INTVL_S *) fs_get(sizeof(*ret)); + memset((void *) *next, 0, sizeof(*ret)); + (*next)->imin = left; + (*next)->imax = right; + next = &(*next)->next; + } + + /* skip to next interval in list */ + while(*q && *q != COMMA && *q != RPAREN) + q++; + if(*q == RPAREN) + q++; + while(*q && *q != COMMA) + q++; + if(*q == COMMA) + q++; + } + } + + return(ret); +} + + +/* + * Returns string that looks like "(left,right),(left2,right2)". + * Caller is responsible for freeing memory. + */ +char * +stringform_of_intvl(INTVL_S *intvl) +{ + char *res = NULL; + + if(intvl && intvl->imin != INTVL_UNDEF && intvl->imax != INTVL_UNDEF + && intvl->imin <= intvl->imax){ + char lbuf[20], rbuf[20], buf[45], *p; + INTVL_S *iv; + int count = 0; + size_t reslen; + + /* find a max size and allocate it for the result */ + for(iv = intvl; + (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF + && iv->imin <= iv->imax); + iv = iv->next) + count++; + + reslen = count * 50 * sizeof(char); + res = (char *) fs_get(reslen+1); + memset((void *) res, 0, reslen+1); + p = res; + + for(iv = intvl; + (iv && iv->imin != INTVL_UNDEF && iv->imax != INTVL_UNDEF + && iv->imin <= iv->imax); + iv = iv->next){ + + if(iv->imin == - INTVL_INF){ + strncpy(lbuf, "-INF", sizeof(lbuf)); + lbuf[sizeof(lbuf)-1] = '\0'; + } + else + snprintf(lbuf, sizeof(lbuf), "%ld", iv->imin); + + if(iv->imax == INTVL_INF){ + strncpy(rbuf, "INF", sizeof(rbuf)); + rbuf[sizeof(rbuf)-1] = '\0'; + } + else + snprintf(rbuf, sizeof(rbuf), "%ld", iv->imax); + + snprintf(buf, sizeof(buf), "%.1s(%.20s,%.20s)", (p == res) ? "" : ",", + lbuf, rbuf); + + sstrncpy(&p, buf, reslen+1 -(p-res)); + } + + res[reslen] = '\0'; + } + + return(res); +} + + +char * +hdrtok_to_stringform(HEADER_TOK_S *hdrtok) +{ + char buf[1024], nbuf[10]; + char *res = NULL; + char *p, *ptr; + + if(hdrtok){ + snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum); + ptr = buf; + sstrncpy(&ptr, hdrtok->hdrname ? hdrtok->hdrname : "", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, "(", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, nbuf, sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, ",\"", sizeof(buf)-(ptr-buf)); + p = hdrtok->fieldseps; + while(p && *p){ + if((*p == '\"' || *p == '\\') && ptr-buf < sizeof(buf)) + *ptr++ = '\\'; + + if(ptr-buf < sizeof(buf)) + *ptr++ = *p++; + } + + sstrncpy(&ptr, "\")", sizeof(buf)-(ptr-buf)); + + if(ptr-buf < sizeof(buf)) + *ptr = '\0'; + else + buf[sizeof(buf)-1] = '\0'; + + res = cpystr(buf); + } + + return(res); +} + + +HEADER_TOK_S * +stringform_to_hdrtok(char *str) +{ + char *p, *q, *w, hdrname[200]; + HEADER_TOK_S *hdrtok = NULL; + + if(str && *str){ + p = str; + hdrname[0] = '\0'; + w = hdrname; + + if(*p == '\"'){ /* quoted name */ + p++; + while(w < hdrname + sizeof(hdrname)-1 && *p != '\"'){ + if(*p == '\\') + p++; + + *w++ = *p++; + } + + *w = '\0'; + if(*p == '\"') + p++; + } + else{ + while(w < hdrname + sizeof(hdrname)-1 && + !(!(*p & 0x80) && isspace((unsigned char)*p)) && + *p != '(') + *w++ = *p++; + + *w = '\0'; + } + + if(hdrname[0]) + hdrtok = new_hdrtok(hdrname); + + if(hdrtok){ + if(*p == '('){ + p++; + + if(*p && isdigit((unsigned char) *p)){ + q = p; + while(*p && isdigit((unsigned char) *p)) + p++; + + hdrtok->fieldnum = atoi(q); + + if(*p == ','){ + int j; + + p++; + /* don't use default */ + if(*p && *p != ')' && hdrtok->fieldseps){ + hdrtok->fieldseps[0] = '\0'; + hdrtok->fieldsepcnt = 0; + } + + j = 0; + if(*p == '\"' && strchr(p+1, '\"')){ /* quoted */ + p++; + while(*p && *p != '\"'){ + if(hdrtok->fieldseps) + fs_resize((void **) &hdrtok->fieldseps, j+2); + + if(*p == '\\' && *(p+1)) + p++; + + if(hdrtok->fieldseps){ + hdrtok->fieldseps[j++] = *p++; + hdrtok->fieldseps[j] = '\0'; + hdrtok->fieldsepcnt = j; + } + } + } + else{ + while(*p && *p != ')'){ + if(hdrtok->fieldseps) + fs_resize((void **) &hdrtok->fieldseps, j+2); + + if(*p == '\\' && *(p+1)) + p++; + + if(hdrtok->fieldseps){ + hdrtok->fieldseps[j++] = *p++; + hdrtok->fieldseps[j] = '\0'; + hdrtok->fieldsepcnt = j; + } + } + } + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 3, "Missing 2nd argument should be field number, a non-negative digit"); + } + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 3, "1st argument should be field number, a non-negative digit"); + } + } + else{ + q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing left parenthesis in %s", str); + } + } + else{ + q_status_message1(SM_ORDER | SM_DING, 3, 3, "Missing header name in %s", str); + } + } + + return(hdrtok); +} + + +char * +hdrtok_to_config(HEADER_TOK_S *hdrtok) +{ + char *ptr, buf[1024], nbuf[10], *p1, *p2, *p3; + char *res = NULL; + + if(hdrtok){ + snprintf(nbuf, sizeof(nbuf), "%d", hdrtok->fieldnum); + memset(buf, 0, sizeof(buf)); + ptr = buf; + sstrncpy(&ptr, "/HN=", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, (p1=add_pat_escapes(hdrtok->hdrname ? hdrtok->hdrname : "")), sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, "/FN=", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, (p2=add_pat_escapes(nbuf)), sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, "/FS=", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, (p3=add_pat_escapes(hdrtok->fieldseps ? hdrtok->fieldseps : "")), sizeof(buf)-(ptr-buf)); + + buf[sizeof(buf)-1] = '\0'; + res = add_pat_escapes(buf); + + if(p1) + fs_give((void **)&p1); + + if(p2) + fs_give((void **)&p2); + + if(p3) + fs_give((void **)&p3); + } + + return(res); +} + + +HEADER_TOK_S * +config_to_hdrtok(char *str) +{ + HEADER_TOK_S *hdrtok = NULL; + char *p, *q; + int j; + + if(str && *str){ + if((q = remove_pat_escapes(str)) != NULL){ + char *hn = NULL, *fn = NULL, *fs = NULL, *z; + + if((z = srchstr(q, "/HN=")) != NULL) + hn = remove_pat_escapes(z+4); + if((z = srchstr(q, "/FN=")) != NULL) + fn = remove_pat_escapes(z+4); + if((z = srchstr(q, "/FS=")) != NULL) + fs = remove_pat_escapes(z+4); + + hdrtok = new_hdrtok(hn); + if(fn) + hdrtok->fieldnum = atoi(fn); + + if(fs && *fs){ + if(hdrtok->fieldseps){ + hdrtok->fieldseps[0] = '\0'; + hdrtok->fieldsepcnt = 0; + } + + p = fs; + j = 0; + if(*p == '\"' && strchr(p+1, '\"')){ + p++; + while(*p && *p != '\"'){ + if(hdrtok->fieldseps) + fs_resize((void **) &hdrtok->fieldseps, j+2); + + if(*p == '\\' && *(p+1)) + p++; + + if(hdrtok->fieldseps){ + hdrtok->fieldseps[j++] = *p++; + hdrtok->fieldseps[j] = '\0'; + hdrtok->fieldsepcnt = j; + } + } + } + else{ + while(*p){ + if(hdrtok->fieldseps) + fs_resize((void **) &hdrtok->fieldseps, j+2); + + if(*p == '\\' && *(p+1)) + p++; + + if(hdrtok->fieldseps){ + hdrtok->fieldseps[j++] = *p++; + hdrtok->fieldseps[j] = '\0'; + hdrtok->fieldsepcnt = j; + } + } + } + } + + if(hn) + fs_give((void **)&hn); + if(fn) + fs_give((void **)&fn); + if(fs) + fs_give((void **)&fs); + + fs_give((void **)&q); + } + } + + return(hdrtok); +} + + +/* + * Args -- flags - SCOREUSE_INVALID Mark scores_in_use invalid so that we'll + * recalculate if we want to use it again. + * - SCOREUSE_GET Return whether scores are being used or not. + * + * Returns -- 0 - Scores not being used at all. + * >0 - Scores are used. The return value consists of flag values + * OR'd together. Possible values are: + * + * SCOREUSE_INCOLS - scores needed for index line colors + * SCOREUSE_ROLES - scores needed for roles + * SCOREUSE_FILTERS - scores needed for filters + * SCOREUSE_OTHER - scores needed for other stuff + * SCOREUSE_INDEX - scores needed for index drawing + * + * SCOREUSE_STATEDEP - scores depend on message state + */ +int +scores_are_used(int flags) +{ + static int scores_in_use = -1; + long type1, type2; + int scores_are_defined, scores_are_used_somewhere = 0; + PAT_STATE pstate1, pstate2; + + if(flags & SCOREUSE_INVALID) /* mark invalid so we recalculate next time */ + scores_in_use = -1; + else if(scores_in_use == -1){ + + /* + * Check the patterns to see if scores are potentially + * being used. + * The first_pattern() in the if checks whether there are any + * non-zero scorevals. The loop checks whether any patterns + * use those non-zero scorevals. + */ + type1 = ROLE_SCORE; + type2 = (ROLE_REPLY | ROLE_FORWARD | ROLE_COMPOSE | + ROLE_INCOL | ROLE_DO_FILTER); + scores_are_defined = nonempty_patterns(type1, &pstate1) + && first_pattern(&pstate1); + if(scores_are_defined) + scores_are_used_somewhere = + ((nonempty_patterns(type2, &pstate2) && first_pattern(&pstate2)) + || ps_global->a_format_contains_score + || mn_get_sort(ps_global->msgmap) == SortScore); + + if(scores_are_used_somewhere){ + PAT_S *pat; + + /* + * Careful. nonempty_patterns() may call close_pattern() + * which will set scores_in_use to -1! So we have to be + * sure to reset it after we call nonempty_patterns(). + */ + scores_in_use = 0; + if(ps_global->a_format_contains_score + || mn_get_sort(ps_global->msgmap) == SortScore) + scores_in_use |= SCOREUSE_INDEX; + + if(nonempty_patterns(type2, &pstate2)) + for(pat = first_pattern(&pstate2); + pat; + pat = next_pattern(&pstate2)) + if(pat->patgrp && !pat->patgrp->bogus && pat->patgrp->do_score){ + if(pat->action && pat->action->is_a_incol) + scores_in_use |= SCOREUSE_INCOLS; + if(pat->action && pat->action->is_a_role) + scores_in_use |= SCOREUSE_ROLES; + if(pat->action && pat->action->is_a_filter) + scores_in_use |= SCOREUSE_FILTERS; + if(pat->action && pat->action->is_a_other) + scores_in_use |= SCOREUSE_OTHER; + } + + /* + * Note whether scores depend on message state or not. + */ + if(scores_in_use) + for(pat = first_pattern(&pstate1); + pat; + pat = next_pattern(&pstate1)) + if(patgrp_depends_on_active_state(pat->patgrp)){ + scores_in_use |= SCOREUSE_STATEDEP; + break; + } + + } + else + scores_in_use = 0; + } + + return((scores_in_use == -1) ? 0 : scores_in_use); +} + + +int +patgrp_depends_on_state(PATGRP_S *patgrp) +{ + return(patgrp && (patgrp_depends_on_active_state(patgrp) + || patgrp->stat_rec != PAT_STAT_EITHER)); +} + + +/* + * Recent doesn't count for this function because it doesn't change while + * the mailbox is open. + */ +int +patgrp_depends_on_active_state(PATGRP_S *patgrp) +{ + return(patgrp && !patgrp->bogus + && (patgrp->stat_new != PAT_STAT_EITHER || + patgrp->stat_del != PAT_STAT_EITHER || + patgrp->stat_imp != PAT_STAT_EITHER || + patgrp->stat_ans != PAT_STAT_EITHER || + patgrp->keyword)); +} + + +/* + * Look for label in str and return a pointer to parsed string. + * Actually, we look for "label=" or "!label=", the second means NOT. + * Converts from string from patterns file which looks like + * /NEWS=comp.mail.,comp.mail.pine/TO=... + * This is the string that came from pattern="string" with the pattern= + * and outer quotes removed. + * This converts the string to a PATTERN_S list and returns + * an allocated copy. + */ +PATTERN_S * +parse_pattern(char *label, char *str, int hex_to_backslashed) +{ + char copy[50]; /* local copy of label */ + char copynot[50]; /* local copy of label, NOT'ed */ + char *q, *labeled_str; + PATTERN_S *head = NULL; + + if(!label || !str) + return(NULL); + + q = copy; + sstrncpy(&q, "/", sizeof(copy)); + sstrncpy(&q, label, sizeof(copy) - (q-copy)); + sstrncpy(&q, "=", sizeof(copy) - (q-copy)); + copy[sizeof(copy)-1] = '\0'; + q = copynot; + sstrncpy(&q, "/!", sizeof(copynot)); + sstrncpy(&q, label, sizeof(copynot) - (q-copynot)); + sstrncpy(&q, "=", sizeof(copynot) - (q-copynot)); + copynot[sizeof(copynot)-1] = '\0'; + + if(hex_to_backslashed){ + if((q = srchstr(str, copy)) != NULL){ + head = config_to_pattern(q+strlen(copy)); + } + else if((q = srchstr(str, copynot)) != NULL){ + head = config_to_pattern(q+strlen(copynot)); + head->not = 1; + } + } + else{ + if((q = srchstr(str, copy)) != NULL){ + if((labeled_str = + remove_backslash_escapes(q+strlen(copy))) != NULL){ + head = string_to_pattern(labeled_str); + fs_give((void **)&labeled_str); + } + } + else if((q = srchstr(str, copynot)) != NULL){ + if((labeled_str = + remove_backslash_escapes(q+strlen(copynot))) != NULL){ + head = string_to_pattern(labeled_str); + head->not = 1; + fs_give((void **)&labeled_str); + } + } + } + + return(head); +} + + +/* + * Look for /ARB's in str and return a pointer to parsed ARBHDR_S. + * Actually, we look for /!ARB and /!EARB as well. Those mean NOT. + * Converts from string from patterns file which looks like + * /ARB<fieldname1>=pattern/.../ARB<fieldname2>=pattern... + * This is the string that came from pattern="string" with the pattern= + * and outer quotes removed. + * This converts the string to a ARBHDR_S list and returns + * an allocated copy. + */ +ARBHDR_S * +parse_arbhdr(char *str) +{ + char *q, *s, *equals, *noesc; + int not, empty, skip; + ARBHDR_S *ahdr = NULL, *a, *aa; + PATTERN_S *p = NULL; + + if(!str) + return(NULL); + + aa = NULL; + for(s = str; (q = next_arb(s)); s = q+1){ + not = (q[1] == '!') ? 1 : 0; + empty = (q[not+1] == 'E') ? 1 : 0; + skip = 4 + not + empty; + if((noesc = remove_pat_escapes(q+skip)) != NULL){ + if(*noesc != '=' && (equals = strindex(noesc, '=')) != NULL){ + a = (ARBHDR_S *)fs_get(sizeof(*a)); + memset((void *)a, 0, sizeof(*a)); + *equals = '\0'; + a->isemptyval = empty; + a->field = cpystr(noesc); + if(empty) + a->p = string_to_pattern(""); + else if(*(equals+1) && + (p = string_to_pattern(equals+1)) != NULL) + a->p = p; + + if(not && a->p) + a->p->not = 1; + + /* keep them in the same order */ + if(aa){ + aa->next = a; + aa = aa->next; + } + else{ + ahdr = a; + aa = ahdr; + } + } + + fs_give((void **)&noesc); + } + } + + return(ahdr); +} + + +char * +next_arb(char *start) +{ + char *q1, *q2, *q3, *q4, *p; + + q1 = srchstr(start, "/ARB"); + q2 = srchstr(start, "/!ARB"); + q3 = srchstr(start, "/EARB"); + q4 = srchstr(start, "/!EARB"); + + p = q1; + if(!p || (q2 && q2 < p)) + p = q2; + if(!p || (q3 && q3 < p)) + p = q3; + if(!p || (q4 && q4 < p)) + p = q4; + + return(p); +} + + +/* + * Converts a string to a PATTERN_S list and returns an + * allocated copy. The source string looks like + * string1,string2,... + * Commas and backslashes may be backslash-escaped in the original string + * in order to include actual commas and backslashes in the pattern. + * So \, is an actual comma and , is the separator character. + */ +PATTERN_S * +string_to_pattern(char *str) +{ + char *q, *s, *workspace; + PATTERN_S *p, *head = NULL, **nextp; + + if(!str) + return(head); + + /* + * We want an empty string to cause an empty substring in the pattern + * instead of returning a NULL pattern. That can be used as a way to + * match any header. For example, if all the patterns but the news + * pattern were null and the news pattern was a substring of "" then + * we use that to match any message with a newsgroups header. + */ + if(!*str){ + head = (PATTERN_S *)fs_get(sizeof(*p)); + memset((void *)head, 0, sizeof(*head)); + head->substring = cpystr(""); + } + else{ + nextp = &head; + workspace = (char *)fs_get((strlen(str)+1) * sizeof(char)); + s = workspace; + *s = '\0'; + q = str; + do { + switch(*q){ + case COMMA: + case '\0': + *s = '\0'; + removing_leading_and_trailing_white_space(workspace); + p = (PATTERN_S *)fs_get(sizeof(*p)); + memset((void *)p, 0, sizeof(*p)); + p->substring = cpystr(workspace); + + convert_possibly_encoded_str_to_utf8(&p->substring); + + *nextp = p; + nextp = &p->next; + s = workspace; + *s = '\0'; + break; + + case BSLASH: + if(*(q+1) == COMMA || *(q+1) == BSLASH) + *s++ = *(++q); + else + *s++ = *q; + + break; + + default: + *s++ = *q; + break; + } + } while(*q++); + + fs_give((void **)&workspace); + } + + return(head); +} + + +/* + * Converts a PATTERN_S list to a string. + * The resulting string is allocated here and looks like + * string1,string2,... + * Commas and backslashes in the original pattern + * end up backslash-escaped in the string. + */ +char * +pattern_to_string(PATTERN_S *pattern) +{ + PATTERN_S *p; + char *result = NULL, *q, *s; + size_t n; + + if(!pattern) + return(result); + + /* how much space is needed? */ + n = 0; + for(p = pattern; p; p = p->next){ + n += (p == pattern) ? 0 : 1; + for(s = p->substring; s && *s; s++){ + if(*s == COMMA || *s == BSLASH) + n++; + + n++; + } + } + + q = result = (char *)fs_get(++n); + for(p = pattern; p; p = p->next){ + if(p != pattern) + *q++ = COMMA; + + for(s = p->substring; s && *s; s++){ + if(*s == COMMA || *s == BSLASH) + *q++ = '\\'; + + *q++ = *s; + } + } + + *q = '\0'; + + return(result); +} + + +/* + * Do the escaping necessary to take a string for a pattern into a comma- + * separated string with escapes suitable for the config file. + * Returns an allocated copy of that string. + * In particular + * , -> \, -> \x2C + * \ -> \\ -> \x5C + * " -> \" + * / -> \/ + */ +char * +pattern_to_config(PATTERN_S *pat) +{ + char *s, *res = NULL; + + s = pattern_to_string(pat); + if(s){ + res = add_pat_escapes(s); + fs_give((void **) &s); + } + + return(res); +} + +/* + * Opposite of pattern_to_config. + */ +PATTERN_S * +config_to_pattern(char *str) +{ + char *s; + PATTERN_S *pat = NULL; + + s = remove_pat_escapes(str); + if(s){ + pat = string_to_pattern(s); + fs_give((void **) &s); + } + + return(pat); +} + + +/* + * Converts an array of strings to a PATTERN_S list and returns an + * allocated copy. + * The list strings may not contain commas directly, because the UI turns + * those into separate list members. Instead, the user types \, and + * that backslash comma is converted to a comma here. + * It is a bit odd. Backslash itself is not escaped. A backslash which is + * not followed by a comma is a literal backslash, a backslash followed by + * a comma is a comma. + */ +PATTERN_S * +editlist_to_pattern(char **list) +{ + PATTERN_S *head = NULL; + + if(!(list && *list)) + return(head); + + /* + * We want an empty string to cause an empty substring in the pattern + * instead of returning a NULL pattern. That can be used as a way to + * match any header. For example, if all the patterns but the news + * pattern were null and the news pattern was a substring of "" then + * we use that to match any message with a newsgroups header. + */ + if(!list[0][0]){ + head = (PATTERN_S *) fs_get(sizeof(*head)); + memset((void *) head, 0, sizeof(*head)); + head->substring = cpystr(""); + } + else{ + char *str, *s, *q, *workspace = NULL; + size_t l = 0; + PATTERN_S *p, **nextp; + int i; + + nextp = &head; + for(i = 0; (str = list[i]); i++){ + if(str[0]){ + if(!workspace){ + l = strlen(str) + 1; + workspace = (char *) fs_get(l * sizeof(char)); + } + else if(strlen(str) + 1 > l){ + l = strlen(str) + 1; + fs_give((void **) &workspace); + workspace = (char *) fs_get(l * sizeof(char)); + } + + s = workspace; + *s = '\0'; + q = str; + do { + switch(*q){ + case '\0': + *s = '\0'; + removing_leading_and_trailing_white_space(workspace); + p = (PATTERN_S *) fs_get(sizeof(*p)); + memset((void *) p, 0, sizeof(*p)); + p->substring = cpystr(workspace); + *nextp = p; + nextp = &p->next; + s = workspace; + *s = '\0'; + break; + + case BSLASH: + if(*(q+1) == COMMA) + *s++ = *(++q); + else + *s++ = *q; + + break; + + default: + *s++ = *q; + break; + } + } while(*q++); + } + } + } + + return(head); +} + + +/* + * Converts a PATTERN_S to an array of strings and returns an allocated copy. + * Commas are converted to backslash-comma, because the text_tool UI uses + * commas to separate items. + * It is a bit odd. Backslash itself is not escaped. A backslash which is + * not followed by a comma is a literal backslash, a backslash followed by + * a comma is a comma. + */ +char ** +pattern_to_editlist(PATTERN_S *pat) +{ + int cnt, i; + PATTERN_S *p; + char **list = NULL; + + if(!pat) + return(list); + + /* how many in list? */ + for(cnt = 0, p = pat; p; p = p->next) + cnt++; + + list = (char **) fs_get((cnt + 1) * sizeof(*list)); + memset((void *) list, 0, (cnt + 1) * sizeof(*list)); + + for(i = 0, p = pat; p; p = p->next, i++) + list[i] = add_comma_escapes(p->substring); + + return(list); +} + + +PATGRP_S * +nick_to_patgrp(char *nick, int rflags) +{ + PAT_S *pat; + PAT_STATE pstate; + PATGRP_S *patgrp = NULL; + + if(!(nick && *nick + && nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))) + return(patgrp); + + for(pat = first_pattern(&pstate); + !patgrp && pat; + pat = next_pattern(&pstate)) + if(pat->patgrp && pat->patgrp->nick && !strcmp(pat->patgrp->nick, nick)) + patgrp = copy_patgrp(pat->patgrp); + + return(patgrp); +} + + +/* + * Must be called with a pstate, we don't check for it. + * It respects the cur_rflag_num in pstate. That is, it doesn't start over + * at i=1, it starts at cur_rflag_num. + */ +PAT_S * +first_any_pattern(PAT_STATE *pstate) +{ + PAT_LINE_S *patline = NULL; + int i; + long local_rflag; + + /* + * The rest of pstate should be set before coming here. + * In particular, the rflags should be set by a call to nonempty_patterns + * or any_patterns, and cur_rflag_num should be set. + */ + pstate->patlinecurrent = NULL; + pstate->patcurrent = NULL; + + /* + * The order of these is important. It is the same as the order + * used for next_any_pattern and opposite of the order used by + * last and prev. For next_any's benefit, we allow cur_rflag_num to + * start us out past the first set. + */ + for(i = pstate->cur_rflag_num; i <= PATTERN_N; i++){ + + local_rflag = 0L; + + switch(i){ + case 1: + local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 2: + local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 3: + local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 4: + local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 5: + local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 6: + local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 7: + local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 8: + local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags); + break; + + case PATTERN_N: + local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags); + break; + } + + if(local_rflag){ + SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK)); + + if(*cur_pat_h){ + /* Find first patline with a pat */ + for(patline = (*cur_pat_h)->patlinehead; + patline && !patline->first; + patline = patline->next) + ; + } + + if(patline){ + pstate->cur_rflag_num = i; + pstate->patlinecurrent = patline; + pstate->patcurrent = patline->first; + } + } + + if(pstate->patcurrent) + break; + } + + return(pstate->patcurrent); +} + + +/* + * Return first pattern of the specified types. These types were set by a + * previous call to any_patterns or nonempty_patterns. + * + * Args -- pstate pattern state. This is set here and passed back for + * use by next_pattern. Must be non-null. + * It must have been initialized previously by a call to + * nonempty_patterns or any_patterns. + */ +PAT_S * +first_pattern(PAT_STATE *pstate) +{ + PAT_S *pat; + long rflags; + + pstate->cur_rflag_num = 1; + + rflags = pstate->rflags; + + for(pat = first_any_pattern(pstate); + pat && !((pat->action && + ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) || + (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) && + pat->action->is_a_incol) || + (rflags & ROLE_DO_OTHER && pat->action->is_a_other) || + (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) || + (rflags & ROLE_DO_SCORES && pat->action->is_a_score) || + (rflags & ROLE_SCORE && (pat->action->scoreval + || pat->action->scorevalhdrtok)) || + (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) || + (rflags & ROLE_REPLY && + (pat->action->repl_type == ROLE_REPL_YES || + pat->action->repl_type == ROLE_REPL_NOCONF)) || + (rflags & ROLE_FORWARD && + (pat->action->forw_type == ROLE_FORW_YES || + pat->action->forw_type == ROLE_FORW_NOCONF)) || + (rflags & ROLE_COMPOSE && + (pat->action->comp_type == ROLE_COMP_YES || + pat->action->comp_type == ROLE_COMP_NOCONF)) || + (rflags & ROLE_OLD_FILT) || + (rflags & ROLE_OLD_SCORE) || + (rflags & ROLE_OLD_PAT))) + || + pat->inherit); + pat = next_any_pattern(pstate)) + ; + + return(pat); +} + + +/* + * Just like first_any_pattern. + */ +PAT_S * +last_any_pattern(PAT_STATE *pstate) +{ + PAT_LINE_S *patline = NULL; + int i; + long local_rflag; + + /* + * The rest of pstate should be set before coming here. + * In particular, the rflags should be set by a call to nonempty_patterns + * or any_patterns, and cur_rflag_num should be set. + */ + pstate->patlinecurrent = NULL; + pstate->patcurrent = NULL; + + for(i = pstate->cur_rflag_num; i >= 1; i--){ + + local_rflag = 0L; + + switch(i){ + case 1: + local_rflag = ROLE_DO_SRCH & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 2: + local_rflag = ROLE_DO_INCOLS & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 3: + local_rflag = ROLE_DO_ROLES & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 4: + local_rflag = ROLE_DO_FILTER & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 5: + local_rflag = ROLE_DO_SCORES & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 6: + local_rflag = ROLE_DO_OTHER & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 7: + local_rflag = ROLE_OLD_FILT & CANONICAL_RFLAGS(pstate->rflags); + break; + + case 8: + local_rflag = ROLE_OLD_SCORE & CANONICAL_RFLAGS(pstate->rflags); + break; + + case PATTERN_N: + local_rflag = ROLE_OLD_PAT & CANONICAL_RFLAGS(pstate->rflags); + break; + } + + if(local_rflag){ + SET_PATTYPE(local_rflag | (pstate->rflags & PAT_USE_MASK)); + + pstate->patlinecurrent = NULL; + pstate->patcurrent = NULL; + + if(*cur_pat_h){ + /* Find last patline with a pat */ + for(patline = (*cur_pat_h)->patlinehead; + patline; + patline = patline->next) + if(patline->last) + pstate->patlinecurrent = patline; + + if(pstate->patlinecurrent) + pstate->patcurrent = pstate->patlinecurrent->last; + } + + if(pstate->patcurrent) + pstate->cur_rflag_num = i; + + if(pstate->patcurrent) + break; + } + } + + return(pstate->patcurrent); +} + + +/* + * Return last pattern of the specified types. These types were set by a + * previous call to any_patterns or nonempty_patterns. + * + * Args -- pstate pattern state. This is set here and passed back for + * use by prev_pattern. Must be non-null. + * It must have been initialized previously by a call to + * nonempty_patterns or any_patterns. + */ +PAT_S * +last_pattern(PAT_STATE *pstate) +{ + PAT_S *pat; + long rflags; + + pstate->cur_rflag_num = PATTERN_N; + + rflags = pstate->rflags; + + for(pat = last_any_pattern(pstate); + pat && !((pat->action && + ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) || + (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) && + pat->action->is_a_incol) || + (rflags & ROLE_DO_OTHER && pat->action->is_a_other) || + (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) || + (rflags & ROLE_DO_SCORES && pat->action->is_a_score) || + (rflags & ROLE_SCORE && (pat->action->scoreval + || pat->action->scorevalhdrtok)) || + (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) || + (rflags & ROLE_REPLY && + (pat->action->repl_type == ROLE_REPL_YES || + pat->action->repl_type == ROLE_REPL_NOCONF)) || + (rflags & ROLE_FORWARD && + (pat->action->forw_type == ROLE_FORW_YES || + pat->action->forw_type == ROLE_FORW_NOCONF)) || + (rflags & ROLE_COMPOSE && + (pat->action->comp_type == ROLE_COMP_YES || + pat->action->comp_type == ROLE_COMP_NOCONF)) || + (rflags & ROLE_OLD_FILT) || + (rflags & ROLE_OLD_SCORE) || + (rflags & ROLE_OLD_PAT))) + || + pat->inherit); + pat = prev_any_pattern(pstate)) + ; + + return(pat); +} + + +/* + * This assumes that pstate is valid. + */ +PAT_S * +next_any_pattern(PAT_STATE *pstate) +{ + PAT_LINE_S *patline; + + if(pstate->patlinecurrent){ + if(pstate->patcurrent && pstate->patcurrent->next) + pstate->patcurrent = pstate->patcurrent->next; + else{ + /* Find next patline with a pat */ + for(patline = pstate->patlinecurrent->next; + patline && !patline->first; + patline = patline->next) + ; + + if(patline){ + pstate->patlinecurrent = patline; + pstate->patcurrent = patline->first; + } + else{ + pstate->patlinecurrent = NULL; + pstate->patcurrent = NULL; + } + } + } + + /* we've reached the last, try the next rflag_num (the next pattern type) */ + if(!pstate->patcurrent){ + pstate->cur_rflag_num++; + pstate->patcurrent = first_any_pattern(pstate); + } + + return(pstate->patcurrent); +} + + +/* + * Return next pattern of the specified types. These types were set by a + * previous call to any_patterns or nonempty_patterns. + * + * Args -- pstate pattern state. This is set by first_pattern or last_pattern. + */ +PAT_S * +next_pattern(PAT_STATE *pstate) +{ + PAT_S *pat; + long rflags; + + rflags = pstate->rflags; + + for(pat = next_any_pattern(pstate); + pat && !((pat->action && + ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) || + (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) && + pat->action->is_a_incol) || + (rflags & ROLE_DO_OTHER && pat->action->is_a_other) || + (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) || + (rflags & ROLE_DO_SCORES && pat->action->is_a_score) || + (rflags & ROLE_SCORE && (pat->action->scoreval + || pat->action->scorevalhdrtok)) || + (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) || + (rflags & ROLE_REPLY && + (pat->action->repl_type == ROLE_REPL_YES || + pat->action->repl_type == ROLE_REPL_NOCONF)) || + (rflags & ROLE_FORWARD && + (pat->action->forw_type == ROLE_FORW_YES || + pat->action->forw_type == ROLE_FORW_NOCONF)) || + (rflags & ROLE_COMPOSE && + (pat->action->comp_type == ROLE_COMP_YES || + pat->action->comp_type == ROLE_COMP_NOCONF)) || + (rflags & ROLE_OLD_FILT) || + (rflags & ROLE_OLD_SCORE) || + (rflags & ROLE_OLD_PAT))) + || + pat->inherit); + pat = next_any_pattern(pstate)) + ; + + return(pat); +} + + +/* + * This assumes that pstate is valid. + */ +PAT_S * +prev_any_pattern(PAT_STATE *pstate) +{ + PAT_LINE_S *patline; + + if(pstate->patlinecurrent){ + if(pstate->patcurrent && pstate->patcurrent->prev) + pstate->patcurrent = pstate->patcurrent->prev; + else{ + /* Find prev patline with a pat */ + for(patline = pstate->patlinecurrent->prev; + patline && !patline->last; + patline = patline->prev) + ; + + if(patline){ + pstate->patlinecurrent = patline; + pstate->patcurrent = patline->last; + } + else{ + pstate->patlinecurrent = NULL; + pstate->patcurrent = NULL; + } + } + } + + if(!pstate->patcurrent){ + pstate->cur_rflag_num--; + pstate->patcurrent = last_any_pattern(pstate); + } + + return(pstate->patcurrent); +} + + +/* + * Return prev pattern of the specified types. These types were set by a + * previous call to any_patterns or nonempty_patterns. + * + * Args -- pstate pattern state. This is set by first_pattern or last_pattern. + */ +PAT_S * +prev_pattern(PAT_STATE *pstate) +{ + PAT_S *pat; + long rflags; + + rflags = pstate->rflags; + + for(pat = prev_any_pattern(pstate); + pat && !((pat->action && + ((rflags & ROLE_DO_ROLES && pat->action->is_a_role) || + (rflags & (ROLE_DO_INCOLS|ROLE_INCOL) && + pat->action->is_a_incol) || + (rflags & ROLE_DO_OTHER && pat->action->is_a_other) || + (rflags & ROLE_DO_SRCH && pat->action->is_a_srch) || + (rflags & ROLE_DO_SCORES && pat->action->is_a_score) || + (rflags & ROLE_SCORE && (pat->action->scoreval + || pat->action->scorevalhdrtok)) || + (rflags & ROLE_DO_FILTER && pat->action->is_a_filter) || + (rflags & ROLE_REPLY && + (pat->action->repl_type == ROLE_REPL_YES || + pat->action->repl_type == ROLE_REPL_NOCONF)) || + (rflags & ROLE_FORWARD && + (pat->action->forw_type == ROLE_FORW_YES || + pat->action->forw_type == ROLE_FORW_NOCONF)) || + (rflags & ROLE_COMPOSE && + (pat->action->comp_type == ROLE_COMP_YES || + pat->action->comp_type == ROLE_COMP_NOCONF)) || + (rflags & ROLE_OLD_FILT) || + (rflags & ROLE_OLD_SCORE) || + (rflags & ROLE_OLD_PAT))) + || + pat->inherit); + pat = prev_any_pattern(pstate)) + ; + + return(pat); +} + + +/* + * Rflags may be more than one pattern type OR'd together. + */ +int +write_patterns(long int rflags) +{ + int canon_rflags; + int err = 0; + + dprint((7, "write_patterns(0x%x)\n", rflags)); + + canon_rflags = CANONICAL_RFLAGS(rflags); + + if(canon_rflags & ROLE_DO_INCOLS) + err += sub_write_patterns(ROLE_DO_INCOLS | (rflags & PAT_USE_MASK)); + if(!err && canon_rflags & ROLE_DO_OTHER) + err += sub_write_patterns(ROLE_DO_OTHER | (rflags & PAT_USE_MASK)); + if(!err && canon_rflags & ROLE_DO_FILTER) + err += sub_write_patterns(ROLE_DO_FILTER | (rflags & PAT_USE_MASK)); + if(!err && canon_rflags & ROLE_DO_SCORES) + err += sub_write_patterns(ROLE_DO_SCORES | (rflags & PAT_USE_MASK)); + if(!err && canon_rflags & ROLE_DO_ROLES) + err += sub_write_patterns(ROLE_DO_ROLES | (rflags & PAT_USE_MASK)); + if(!err && canon_rflags & ROLE_DO_SRCH) + err += sub_write_patterns(ROLE_DO_SRCH | (rflags & PAT_USE_MASK)); + + if(!err && !(rflags & PAT_USE_CHANGED)) + write_pinerc(ps_global, (rflags & PAT_USE_MAIN) ? Main : Post, WRP_NONE); + + return(err); +} + + +int +sub_write_patterns(long int rflags) +{ + int err = 0, lineno = 0; + char **lvalue = NULL; + PAT_LINE_S *patline; + + SET_PATTYPE(rflags); + + if(!(*cur_pat_h)){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + "Unknown error saving patterns"); + return(-1); + } + + if((*cur_pat_h)->dirtypinerc){ + /* Count how many lines will be in patterns variable */ + for(patline = (*cur_pat_h)->patlinehead; + patline; + patline = patline->next) + lineno++; + + lvalue = (char **)fs_get((lineno+1)*sizeof(char *)); + memset(lvalue, 0, (lineno+1) * sizeof(char *)); + } + + for(patline = (*cur_pat_h)->patlinehead, lineno = 0; + !err && patline; + patline = patline->next, lineno++){ + if(patline->type == File) + err = write_pattern_file((*cur_pat_h)->dirtypinerc + ? &lvalue[lineno] : NULL, patline); + else if(patline->type == Literal && (*cur_pat_h)->dirtypinerc) + err = write_pattern_lit(&lvalue[lineno], patline); + else if(patline->type == Inherit) + err = write_pattern_inherit((*cur_pat_h)->dirtypinerc + ? &lvalue[lineno] : NULL, patline); + } + + if((*cur_pat_h)->dirtypinerc){ + if(err) + free_list_array(&lvalue); + else{ + char ***alval; + struct variable *var; + + if(rflags & ROLE_DO_ROLES) + var = &ps_global->vars[V_PAT_ROLES]; + else if(rflags & ROLE_DO_OTHER) + var = &ps_global->vars[V_PAT_OTHER]; + else if(rflags & ROLE_DO_FILTER) + var = &ps_global->vars[V_PAT_FILTS]; + else if(rflags & ROLE_DO_SCORES) + var = &ps_global->vars[V_PAT_SCORES]; + else if(rflags & ROLE_DO_INCOLS) + var = &ps_global->vars[V_PAT_INCOLS]; + else if(rflags & ROLE_DO_SRCH) + var = &ps_global->vars[V_PAT_SRCH]; + + alval = (rflags & PAT_USE_CHANGED) ? &(var->changed_val.l) + : ALVAL(var, (rflags & PAT_USE_MAIN) ? Main : Post); + if(*alval) + free_list_array(alval); + + if(rflags & PAT_USE_CHANGED) var->is_changed_val = 1; + + *alval = lvalue; + + if(!(rflags & PAT_USE_CHANGED)) + set_current_val(var, TRUE, TRUE); + } + } + + if(!err) + (*cur_pat_h)->dirtypinerc = 0; + + return(err); +} + + +/* + * Write pattern lines into a file. + * + * Args lvalue -- Pointer to char * to fill in variable value + * patline -- + * + * Returns 0 -- all is ok, lvalue has been filled in, file has been written + * else -- error, lvalue untouched, file not written + */ +int +write_pattern_file(char **lvalue, PAT_LINE_S *patline) +{ + char *p, *tfile; + int fd = -1, err = 0; + FILE *fp_new; + PAT_S *pat; + + dprint((7, "write_pattern_file(%s)\n", + (patline && patline->filepath) ? patline->filepath : "?")); + + if(lvalue){ + size_t l; + + l = strlen(patline->filename) + 5; + p = (char *) fs_get((l+1) * sizeof(char)); + strncpy(p, "FILE:", l+1); + p[l] = '\0'; + strncat(p, patline->filename, l+1-1-strlen(p)); + p[l] = '\0'; + *lvalue = p; + } + + if(patline->readonly || !patline->dirty) /* doesn't need writing */ + return(err); + + /* Get a tempfile to write the patterns into */ + if(((tfile = tempfile_in_same_dir(patline->filepath, ".pt", NULL)) == NULL) + || ((fd = our_open(tfile, O_TRUNC|O_WRONLY|O_CREAT|O_BINARY, 0600)) < 0) + || ((fp_new = fdopen(fd, "w")) == NULL)){ + q_status_message1(SM_ORDER | SM_DING, 3, 4, + "Can't write in directory containing file \"%.200s\"", + patline->filepath); + if(tfile){ + our_unlink(tfile); + fs_give((void **)&tfile); + } + + if(fd >= 0) + close(fd); + + return(-1); + } + + dprint((9, "write_pattern_file: writing into %s\n", + tfile ? tfile : "?")); + + if(fprintf(fp_new, "%s %s\n", PATTERN_MAGIC, PATTERN_FILE_VERS) == EOF) + err--; + + for(pat = patline->first; !err && pat; pat = pat->next){ + if((p = data_for_patline(pat)) != NULL){ + if(fprintf(fp_new, "%s\n", p) == EOF) + err--; + + fs_give((void **)&p); + } + } + + if(err || fclose(fp_new) == EOF){ + if(err) + (void)fclose(fp_new); + + err--; + q_status_message2(SM_ORDER | SM_DING, 3, 4, + "I/O error: \"%.200s\": %.200s", + tfile, error_description(errno)); + } + + if(!err && rename_file(tfile, patline->filepath) < 0){ + err--; + q_status_message3(SM_ORDER | SM_DING, 3, 4, + _("Error renaming \"%s\" to \"%s\": %s"), + tfile, patline->filepath, error_description(errno)); + dprint((2, + "write_pattern_file: Error renaming (%s,%s): %s\n", + tfile ? tfile : "?", + (patline && patline->filepath) ? patline->filepath : "?", + error_description(errno))); + } + + if(tfile){ + our_unlink(tfile); + fs_give((void **)&tfile); + } + + if(!err) + patline->dirty = 0; + + return(err); +} + + +/* + * Write literal pattern lines into lvalue (pinerc variable). + * + * Args lvalue -- Pointer to char * to fill in variable value + * patline -- + * + * Returns 0 -- all is ok, lvalue has been filled in, file has been written + * else -- error, lvalue untouched, file not written + */ +int +write_pattern_lit(char **lvalue, PAT_LINE_S *patline) +{ + char *p = NULL; + int err = 0; + PAT_S *pat; + + pat = patline ? patline->first : NULL; + + if(pat && lvalue && (p = data_for_patline(pat)) != NULL){ + size_t l; + + l = strlen(p) + 4; + *lvalue = (char *) fs_get((l+1) * sizeof(char)); + strncpy(*lvalue, "LIT:", l+1); + (*lvalue)[l] = '\0'; + strncat(*lvalue, p, l+1-1-strlen(*lvalue)); + (*lvalue)[l] = '\0'; + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Unknown error saving pattern variable")); + err--; + } + + if(p) + fs_give((void **)&p); + + return(err); +} + + +int +write_pattern_inherit(char **lvalue, PAT_LINE_S *patline) +{ + int err = 0; + + if(patline && patline->type == Inherit && lvalue) + *lvalue = cpystr(INHERIT); + else + err--; + + return(err); +} + + + +char * +data_for_patline(PAT_S *pat) +{ + char *p = NULL, *q, *to_pat = NULL, + *news_pat = NULL, *from_pat = NULL, + *sender_pat = NULL, *cc_pat = NULL, *subj_pat = NULL, + *arb_pat = NULL, *fldr_type_pat = NULL, *fldr_pat = NULL, + *afrom_type_pat = NULL, *abooks_pat = NULL, + *alltext_pat = NULL, *scorei_pat = NULL, *recip_pat = NULL, + *keyword_pat = NULL, *charset_pat = NULL, + *bodytext_pat = NULL, *age_pat = NULL, *sentdate = NULL, + *size_pat = NULL, + *category_cmd = NULL, *category_pat = NULL, + *category_lim = NULL, + *partic_pat = NULL, *stat_new_val = NULL, + *stat_rec_val = NULL, + *stat_imp_val = NULL, *stat_del_val = NULL, + *stat_ans_val = NULL, *stat_8bit_val = NULL, + *stat_bom_val = NULL, *stat_boy_val = NULL, + *from_act = NULL, *replyto_act = NULL, *fcc_act = NULL, + *sig_act = NULL, *nick = NULL, *templ_act = NULL, + *litsig_act = NULL, *cstm_act = NULL, *smtp_act = NULL, + *nntp_act = NULL, *comment = NULL, + *repl_val = NULL, *forw_val = NULL, *comp_val = NULL, + *incol_act = NULL, *inherit_nick = NULL, + *score_act = NULL, *hdrtok_act = NULL, + *sort_act = NULL, *iform_act = NULL, *start_act = NULL, + *folder_act = NULL, *filt_ifnotdel = NULL, + *filt_nokill = NULL, *filt_del_val = NULL, + *filt_imp_val = NULL, *filt_ans_val = NULL, + *filt_new_val = NULL, *filt_nonterm = NULL, + *keyword_set = NULL, *keyword_clr = NULL; + int to_not = 0, news_not = 0, from_not = 0, + sender_not = 0, cc_not = 0, subj_not = 0, + partic_not = 0, recip_not = 0, alltext_not, bodytext_not, + keyword_not = 0, charset_not = 0; + size_t l; + ACTION_S *action = NULL; + NAMEVAL_S *f; + + if(!pat) + return(p); + + if((pat->patgrp && pat->patgrp->bogus) + || (pat->action && pat->action->bogus)){ + if(pat->raw) + p = cpystr(pat->raw); + + return(p); + } + + if(pat->patgrp){ + if(pat->patgrp->nick) + if((nick = add_pat_escapes(pat->patgrp->nick)) && !*nick) + fs_give((void **) &nick); + + if(pat->patgrp->comment) + if((comment = add_pat_escapes(pat->patgrp->comment)) && !*comment) + fs_give((void **) &comment); + + if(pat->patgrp->to){ + to_pat = pattern_to_config(pat->patgrp->to); + to_not = pat->patgrp->to->not; + } + + if(pat->patgrp->from){ + from_pat = pattern_to_config(pat->patgrp->from); + from_not = pat->patgrp->from->not; + } + + if(pat->patgrp->sender){ + sender_pat = pattern_to_config(pat->patgrp->sender); + sender_not = pat->patgrp->sender->not; + } + + if(pat->patgrp->cc){ + cc_pat = pattern_to_config(pat->patgrp->cc); + cc_not = pat->patgrp->cc->not; + } + + if(pat->patgrp->recip){ + recip_pat = pattern_to_config(pat->patgrp->recip); + recip_not = pat->patgrp->recip->not; + } + + if(pat->patgrp->partic){ + partic_pat = pattern_to_config(pat->patgrp->partic); + partic_not = pat->patgrp->partic->not; + } + + if(pat->patgrp->news){ + news_pat = pattern_to_config(pat->patgrp->news); + news_not = pat->patgrp->news->not; + } + + if(pat->patgrp->subj){ + subj_pat = pattern_to_config(pat->patgrp->subj); + subj_not = pat->patgrp->subj->not; + } + + if(pat->patgrp->alltext){ + alltext_pat = pattern_to_config(pat->patgrp->alltext); + alltext_not = pat->patgrp->alltext->not; + } + + if(pat->patgrp->bodytext){ + bodytext_pat = pattern_to_config(pat->patgrp->bodytext); + bodytext_not = pat->patgrp->bodytext->not; + } + + if(pat->patgrp->keyword){ + keyword_pat = pattern_to_config(pat->patgrp->keyword); + keyword_not = pat->patgrp->keyword->not; + } + + if(pat->patgrp->charsets){ + charset_pat = pattern_to_config(pat->patgrp->charsets); + charset_not = pat->patgrp->charsets->not; + } + + if(pat->patgrp->arbhdr){ + ARBHDR_S *a; + char *p1 = NULL, *p2 = NULL, *p3 = NULL, *p4 = NULL; + int len = 0; + + /* This is brute force dumb, but who cares? */ + for(a = pat->patgrp->arbhdr; a; a = a->next){ + if(a->field && a->field[0]){ + p1 = pattern_to_string(a->p); + p1 = p1 ? p1 : cpystr(""); + l = strlen(a->field)+strlen(p1)+1; + p2 = (char *) fs_get((l+1) * sizeof(char)); + snprintf(p2, l+1, "%s=%s", a->field, p1); + p3 = add_pat_escapes(p2); + l = strlen(p3)+6; + p4 = (char *) fs_get((l+1) * sizeof(char)); + snprintf(p4, l+1, "/%s%sARB%s", + (a->p && a->p->not) ? "!" : "", + a->isemptyval ? "E" : "", p3); + len += strlen(p4); + + if(p1) + fs_give((void **)&p1); + if(p2) + fs_give((void **)&p2); + if(p3) + fs_give((void **)&p3); + if(p4) + fs_give((void **)&p4); + } + } + + p = arb_pat = (char *)fs_get((len + 1) * sizeof(char)); + + for(a = pat->patgrp->arbhdr; a; a = a->next){ + if(a->field && a->field[0]){ + p1 = pattern_to_string(a->p); + p1 = p1 ? p1 : cpystr(""); + l = strlen(a->field)+strlen(p1)+1; + p2 = (char *) fs_get((l+1) * sizeof(char)); + snprintf(p2, l+1, "%s=%s", a->field, p1); + p3 = add_pat_escapes(p2); + l = strlen(p3)+6; + p4 = (char *) fs_get((l+1) * sizeof(char)); + snprintf(p4, l+1, "/%s%sARB%s", + (a->p && a->p->not) ? "!" : "", + a->isemptyval ? "E" : "", p3); + sstrncpy(&p, p4, len+1-(p-arb_pat)); + + if(p1) + fs_give((void **)&p1); + if(p2) + fs_give((void **)&p2); + if(p3) + fs_give((void **)&p3); + if(p4) + fs_give((void **)&p4); + } + } + + arb_pat[len] = '\0'; + } + + if(pat->patgrp->age_uses_sentdate) + sentdate = cpystr("/SENTDATE=1"); + + if(pat->patgrp->do_score){ + p = stringform_of_intvl(pat->patgrp->score); + if(p){ + scorei_pat = add_pat_escapes(p); + fs_give((void **)&p); + } + } + + if(pat->patgrp->do_age){ + p = stringform_of_intvl(pat->patgrp->age); + if(p){ + age_pat = add_pat_escapes(p); + fs_give((void **)&p); + } + } + + if(pat->patgrp->do_size){ + p = stringform_of_intvl(pat->patgrp->size); + if(p){ + size_pat = add_pat_escapes(p); + fs_give((void **)&p); + } + } + + if(pat->patgrp->category_cmd && pat->patgrp->category_cmd[0]){ + size_t sz; + char **l, *q; + + /* concatenate into string with commas first */ + sz = 0; + for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++) + sz += strlen(l[0]) + 1; + + if(sz){ + char *p; + int first_one = 1; + + q = (char *)fs_get(sz); + memset(q, 0, sz); + p = q; + for(l = pat->patgrp->category_cmd; l[0] && l[0][0]; l++){ + if(!first_one) + sstrncpy(&p, ",", sz-(p-q)); + + first_one = 0; + sstrncpy(&p, l[0], sz-(p-q)); + } + + q[sz-1] = '\0'; + + category_cmd = add_pat_escapes(q); + fs_give((void **)&q); + } + } + + if(pat->patgrp->do_cat){ + p = stringform_of_intvl(pat->patgrp->cat); + if(p){ + category_pat = add_pat_escapes(p); + fs_give((void **)&p); + } + } + + if(pat->patgrp->cat_lim != -1L){ + category_lim = (char *) fs_get(20 * sizeof(char)); + snprintf(category_lim, 20, "%ld", pat->patgrp->cat_lim); + } + + if((f = pat_fldr_types(pat->patgrp->fldr_type)) != NULL) + fldr_type_pat = f->shortname; + + if(pat->patgrp->folder) + fldr_pat = pattern_to_config(pat->patgrp->folder); + + if((f = inabook_fldr_types(pat->patgrp->inabook)) != NULL + && f->value != IAB_DEFL) + afrom_type_pat = f->shortname; + + if(pat->patgrp->abooks) + abooks_pat = pattern_to_config(pat->patgrp->abooks); + + if(pat->patgrp->stat_new != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_new)) != NULL) + stat_new_val = f->shortname; + + if(pat->patgrp->stat_rec != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_rec)) != NULL) + stat_rec_val = f->shortname; + + if(pat->patgrp->stat_del != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_del)) != NULL) + stat_del_val = f->shortname; + + if(pat->patgrp->stat_ans != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_ans)) != NULL) + stat_ans_val = f->shortname; + + if(pat->patgrp->stat_imp != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_imp)) != NULL) + stat_imp_val = f->shortname; + + if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_8bitsubj)) != NULL) + stat_8bit_val = f->shortname; + + if(pat->patgrp->stat_bom != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_bom)) != NULL) + stat_bom_val = f->shortname; + + if(pat->patgrp->stat_boy != PAT_STAT_EITHER && + (f = role_status_types(pat->patgrp->stat_boy)) != NULL) + stat_boy_val = f->shortname; + } + + if(pat->action){ + action = pat->action; + + if(action->is_a_score){ + if(action->scoreval != 0L && + action->scoreval >= SCORE_MIN && action->scoreval <= SCORE_MAX){ + score_act = (char *) fs_get(5 * sizeof(char)); + snprintf(score_act, 5, "%ld", pat->action->scoreval); + } + + if(action->scorevalhdrtok) + hdrtok_act = hdrtok_to_config(action->scorevalhdrtok); + } + + if(action->is_a_role){ + if(action->inherit_nick) + inherit_nick = add_pat_escapes(action->inherit_nick); + if(action->fcc) + fcc_act = add_pat_escapes(action->fcc); + if(action->litsig) + litsig_act = add_pat_escapes(action->litsig); + if(action->sig) + sig_act = add_pat_escapes(action->sig); + if(action->template) + templ_act = add_pat_escapes(action->template); + + if(action->cstm){ + size_t sz; + char **l, *q; + + /* concatenate into string with commas first */ + sz = 0; + for(l = action->cstm; l[0] && l[0][0]; l++) + sz += strlen(l[0]) + 1; + + if(sz){ + char *p; + int first_one = 1; + + q = (char *)fs_get(sz); + memset(q, 0, sz); + p = q; + for(l = action->cstm; l[0] && l[0][0]; l++){ + if((!struncmp(l[0], "from", 4) && + (l[0][4] == ':' || l[0][4] == '\0')) || + (!struncmp(l[0], "reply-to", 8) && + (l[0][8] == ':' || l[0][8] == '\0'))) + continue; + + if(!first_one) + sstrncpy(&p, ",", sz-(p-q)); + + first_one = 0; + sstrncpy(&p, l[0], sz-(p-q)); + } + + q[sz-1] = '\0'; + + cstm_act = add_pat_escapes(q); + fs_give((void **)&q); + } + } + + if(action->smtp){ + size_t sz; + char **l, *q; + + /* concatenate into string with commas first */ + sz = 0; + for(l = action->smtp; l[0] && l[0][0]; l++) + sz += strlen(l[0]) + 1; + + if(sz){ + char *p; + int first_one = 1; + + q = (char *)fs_get(sz); + memset(q, 0, sz); + p = q; + for(l = action->smtp; l[0] && l[0][0]; l++){ + if(!first_one) + sstrncpy(&p, ",", sz-(p-q)); + + first_one = 0; + sstrncpy(&p, l[0], sz-(p-q)); + } + + q[sz-1] = '\0'; + + smtp_act = add_pat_escapes(q); + fs_give((void **)&q); + } + } + + if(action->nntp){ + size_t sz; + char **l, *q; + + /* concatenate into string with commas first */ + sz = 0; + for(l = action->nntp; l[0] && l[0][0]; l++) + sz += strlen(l[0]) + 1; + + if(sz){ + char *p; + int first_one = 1; + + q = (char *)fs_get(sz); + memset(q, 0, sz); + p = q; + for(l = action->nntp; l[0] && l[0][0]; l++){ + if(!first_one) + sstrncpy(&p, ",", sz-(p-q)); + + first_one = 0; + sstrncpy(&p, l[0], sz-(p-q)); + } + + q[sz-1] = '\0'; + + nntp_act = add_pat_escapes(q); + fs_give((void **)&q); + } + } + + if((f = role_repl_types(action->repl_type)) != NULL) + repl_val = f->shortname; + + if((f = role_forw_types(action->forw_type)) != NULL) + forw_val = f->shortname; + + if((f = role_comp_types(action->comp_type)) != NULL) + comp_val = f->shortname; + } + + if(action->is_a_incol && action->incol){ + char *ptr, buf[256], *p1, *p2; + + ptr = buf; + memset(buf, 0, sizeof(buf)); + sstrncpy(&ptr, "/FG=", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, (p1=add_pat_escapes(action->incol->fg)), sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, "/BG=", sizeof(buf)-(ptr-buf)); + sstrncpy(&ptr, (p2=add_pat_escapes(action->incol->bg)), sizeof(buf)-(ptr-buf)); + buf[sizeof(buf)-1] = '\0'; + /* the colors will be doubly escaped */ + incol_act = add_pat_escapes(buf); + if(p1) + fs_give((void **)&p1); + + if(p2) + fs_give((void **)&p2); + } + + if(action->is_a_other){ + char buf[256]; + + if(action->sort_is_set){ + snprintf(buf, sizeof(buf), "%.50s%.50s", + sort_name(action->sortorder), + action->revsort ? "/Reverse" : ""); + sort_act = add_pat_escapes(buf); + } + + if(action->index_format) + iform_act = add_pat_escapes(action->index_format); + + if(action->startup_rule != IS_NOTSET && + (f = startup_rules(action->startup_rule)) != NULL) + start_act = S_OR_L(f); + } + + if(action->is_a_role && action->from){ + char *bufp; + size_t len; + + len = est_size(action->from); + bufp = (char *) fs_get(len * sizeof(char)); + p = addr_string_mult(action->from, bufp, len); + if(p){ + from_act = add_pat_escapes(p); + fs_give((void **)&p); + } + } + + if(action->is_a_role && action->replyto){ + char *bufp; + size_t len; + + len = est_size(action->replyto); + bufp = (char *) fs_get(len * sizeof(char)); + p = addr_string_mult(action->replyto, bufp, len); + if(p){ + replyto_act = add_pat_escapes(p); + fs_give((void **)&p); + } + } + + if(action->is_a_filter){ + if(action->folder){ + if((folder_act = pattern_to_config(action->folder)) != NULL){ + if(action->move_only_if_not_deleted) + filt_ifnotdel = cpystr("/NOTDEL=1"); + } + } + + if(action->keyword_set) + keyword_set = pattern_to_config(action->keyword_set); + + if(action->keyword_clr) + keyword_clr = pattern_to_config(action->keyword_clr); + + if(!action->kill) + filt_nokill = cpystr("/NOKILL=1"); + + if(action->non_terminating) + filt_nonterm = cpystr("/NONTERM=1"); + + if(action->state_setting_bits){ + char buf[256]; + int dval, nval, ival, aval; + + buf[0] = '\0'; + p = buf; + + convert_statebits_to_vals(action->state_setting_bits, + &dval, &aval, &ival, &nval); + if(dval != ACT_STAT_LEAVE && + (f = msg_state_types(dval)) != NULL) + filt_del_val = f->shortname; + + if(aval != ACT_STAT_LEAVE && + (f = msg_state_types(aval)) != NULL) + filt_ans_val = f->shortname; + + if(ival != ACT_STAT_LEAVE && + (f = msg_state_types(ival)) != NULL) + filt_imp_val = f->shortname; + + if(nval != ACT_STAT_LEAVE && + (f = msg_state_types(nval)) != NULL) + filt_new_val = f->shortname; + } + } + } + + l = strlen(nick ? nick : "Alternate Role") + + strlen(comment ? comment : "") + + strlen(to_pat ? to_pat : "") + + strlen(from_pat ? from_pat : "") + + strlen(sender_pat ? sender_pat : "") + + strlen(cc_pat ? cc_pat : "") + + strlen(recip_pat ? recip_pat : "") + + strlen(partic_pat ? partic_pat : "") + + strlen(news_pat ? news_pat : "") + + strlen(subj_pat ? subj_pat : "") + + strlen(alltext_pat ? alltext_pat : "") + + strlen(bodytext_pat ? bodytext_pat : "") + + strlen(arb_pat ? arb_pat : "") + + strlen(scorei_pat ? scorei_pat : "") + + strlen(keyword_pat ? keyword_pat : "") + + strlen(charset_pat ? charset_pat : "") + + strlen(age_pat ? age_pat : "") + + strlen(size_pat ? size_pat : "") + + strlen(category_cmd ? category_cmd : "") + + strlen(category_pat ? category_pat : "") + + strlen(category_lim ? category_lim : "") + + strlen(fldr_pat ? fldr_pat : "") + + strlen(abooks_pat ? abooks_pat : "") + + strlen(sentdate ? sentdate : "") + + strlen(inherit_nick ? inherit_nick : "") + + strlen(score_act ? score_act : "") + + strlen(hdrtok_act ? hdrtok_act : "") + + strlen(from_act ? from_act : "") + + strlen(replyto_act ? replyto_act : "") + + strlen(fcc_act ? fcc_act : "") + + strlen(litsig_act ? litsig_act : "") + + strlen(cstm_act ? cstm_act : "") + + strlen(smtp_act ? smtp_act : "") + + strlen(nntp_act ? nntp_act : "") + + strlen(sig_act ? sig_act : "") + + strlen(incol_act ? incol_act : "") + + strlen(sort_act ? sort_act : "") + + strlen(iform_act ? iform_act : "") + + strlen(start_act ? start_act : "") + + strlen(filt_ifnotdel ? filt_ifnotdel : "") + + strlen(filt_nokill ? filt_nokill : "") + + strlen(filt_nonterm ? filt_nonterm : "") + + (folder_act ? (strlen(folder_act) + 8) : 0) + + strlen(keyword_set ? keyword_set : "") + + strlen(keyword_clr ? keyword_clr : "") + + strlen(templ_act ? templ_act : "") + 540; + /* + * The +540 above is larger than needed but not everything is accounted + * for with the strlens. + */ + p = (char *) fs_get(l * sizeof(char)); + + q = p; + sstrncpy(&q, "pattern=\"/NICK=", l-(q-p)); + + if(nick){ + sstrncpy(&q, nick, l-(q-p)); + fs_give((void **) &nick); + } + else + sstrncpy(&q, "Alternate Role", l-(q-p)); + + if(comment){ + sstrncpy(&q, "/", l-(q-p)); + sstrncpy(&q, "COMM=", l-(q-p)); + sstrncpy(&q, comment, l-(q-p)); + fs_give((void **) &comment); + } + + if(to_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(to_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "TO=", l-(q-p)); + sstrncpy(&q, to_pat, l-(q-p)); + fs_give((void **) &to_pat); + } + + if(from_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(from_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "FROM=", l-(q-p)); + sstrncpy(&q, from_pat, l-(q-p)); + fs_give((void **) &from_pat); + } + + if(sender_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(sender_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "SENDER=", l-(q-p)); + sstrncpy(&q, sender_pat, l-(q-p)); + fs_give((void **) &sender_pat); + } + + if(cc_pat){ + sstrncpy(&q,"/", l-(q-p)); + if(cc_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q,"CC=", l-(q-p)); + sstrncpy(&q, cc_pat, l-(q-p)); + fs_give((void **) &cc_pat); + } + + if(recip_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(recip_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "RECIP=", l-(q-p)); + sstrncpy(&q, recip_pat, l-(q-p)); + fs_give((void **) &recip_pat); + } + + if(partic_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(partic_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "PARTIC=", l-(q-p)); + sstrncpy(&q, partic_pat, l-(q-p)); + fs_give((void **) &partic_pat); + } + + if(news_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(news_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "NEWS=", l-(q-p)); + sstrncpy(&q, news_pat, l-(q-p)); + fs_give((void **) &news_pat); + } + + if(subj_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(subj_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "SUBJ=", l-(q-p)); + sstrncpy(&q, subj_pat, l-(q-p)); + fs_give((void **)&subj_pat); + } + + if(alltext_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(alltext_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "ALL=", l-(q-p)); + sstrncpy(&q, alltext_pat, l-(q-p)); + fs_give((void **) &alltext_pat); + } + + if(bodytext_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(bodytext_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "BODY=", l-(q-p)); + sstrncpy(&q, bodytext_pat, l-(q-p)); + fs_give((void **) &bodytext_pat); + } + + if(keyword_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(keyword_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "KEY=", l-(q-p)); + sstrncpy(&q, keyword_pat, l-(q-p)); + fs_give((void **) &keyword_pat); + } + + if(charset_pat){ + sstrncpy(&q, "/", l-(q-p)); + if(charset_not) + sstrncpy(&q, "!", l-(q-p)); + + sstrncpy(&q, "CHAR=", l-(q-p)); + sstrncpy(&q, charset_pat, l-(q-p)); + fs_give((void **) &charset_pat); + } + + if(arb_pat){ + sstrncpy(&q, arb_pat, l-(q-p)); + fs_give((void **)&arb_pat); + } + + if(scorei_pat){ + sstrncpy(&q, "/SCOREI=", l-(q-p)); + sstrncpy(&q, scorei_pat, l-(q-p)); + fs_give((void **) &scorei_pat); + } + + if(age_pat){ + sstrncpy(&q, "/AGE=", l-(q-p)); + sstrncpy(&q, age_pat, l-(q-p)); + fs_give((void **) &age_pat); + } + + if(size_pat){ + sstrncpy(&q, "/SIZE=", l-(q-p)); + sstrncpy(&q, size_pat, l-(q-p)); + fs_give((void **) &size_pat); + } + + if(category_cmd){ + sstrncpy(&q, "/CATCMD=", l-(q-p)); + sstrncpy(&q, category_cmd, l-(q-p)); + fs_give((void **) &category_cmd); + } + + if(category_pat){ + sstrncpy(&q, "/CATVAL=", l-(q-p)); + sstrncpy(&q, category_pat, l-(q-p)); + fs_give((void **) &category_pat); + } + + if(category_lim){ + sstrncpy(&q, "/CATLIM=", l-(q-p)); + sstrncpy(&q, category_lim, l-(q-p)); + fs_give((void **) &category_lim); + } + + if(sentdate){ + sstrncpy(&q, sentdate, l-(q-p)); + fs_give((void **) &sentdate); + } + + if(fldr_type_pat){ + sstrncpy(&q, "/FLDTYPE=", l-(q-p)); + sstrncpy(&q, fldr_type_pat, l-(q-p)); + } + + if(fldr_pat){ + sstrncpy(&q, "/FOLDER=", l-(q-p)); + sstrncpy(&q, fldr_pat, l-(q-p)); + fs_give((void **) &fldr_pat); + } + + if(afrom_type_pat){ + sstrncpy(&q, "/AFROM=", l-(q-p)); + sstrncpy(&q, afrom_type_pat, l-(q-p)); + + /* + * Add address types. If it is From or Reply-to + * leave this out so it will still work with pine. + */ + if((pat->patgrp->inabook & IAB_FROM + && pat->patgrp->inabook & IAB_REPLYTO + && !(pat->patgrp->inabook & IAB_SENDER) + && !(pat->patgrp->inabook & IAB_TO) + && !(pat->patgrp->inabook & IAB_CC)) + || + (!(pat->patgrp->inabook & IAB_FROM) + && !(pat->patgrp->inabook & IAB_REPLYTO) + && !(pat->patgrp->inabook & IAB_SENDER) + && !(pat->patgrp->inabook & IAB_TO) + && !(pat->patgrp->inabook & IAB_CC))){ + ; /* leave it out */ + } + else{ + sstrncpy(&q, "/AFROMA=", l-(q-p)); + if(pat->patgrp->inabook & IAB_FROM) + sstrncpy(&q, "F", l-(q-p)); + + if(pat->patgrp->inabook & IAB_REPLYTO) + sstrncpy(&q, "R", l-(q-p)); + + if(pat->patgrp->inabook & IAB_SENDER) + sstrncpy(&q, "S", l-(q-p)); + + if(pat->patgrp->inabook & IAB_TO) + sstrncpy(&q, "T", l-(q-p)); + + if(pat->patgrp->inabook & IAB_CC) + sstrncpy(&q, "C", l-(q-p)); + } + } + + if(abooks_pat){ + sstrncpy(&q, "/ABOOKS=", l-(q-p)); + sstrncpy(&q, abooks_pat, l-(q-p)); + fs_give((void **) &abooks_pat); + } + + if(stat_new_val){ + sstrncpy(&q, "/STATN=", l-(q-p)); + sstrncpy(&q, stat_new_val, l-(q-p)); + } + + if(stat_rec_val){ + sstrncpy(&q, "/STATR=", l-(q-p)); + sstrncpy(&q, stat_rec_val, l-(q-p)); + } + + if(stat_del_val){ + sstrncpy(&q, "/STATD=", l-(q-p)); + sstrncpy(&q, stat_del_val, l-(q-p)); + } + + if(stat_imp_val){ + sstrncpy(&q, "/STATI=", l-(q-p)); + sstrncpy(&q, stat_imp_val, l-(q-p)); + } + + if(stat_ans_val){ + sstrncpy(&q, "/STATA=", l-(q-p)); + sstrncpy(&q, stat_ans_val, l-(q-p)); + } + + if(stat_8bit_val){ + sstrncpy(&q, "/8BITS=", l-(q-p)); + sstrncpy(&q, stat_8bit_val, l-(q-p)); + } + + if(stat_bom_val){ + sstrncpy(&q, "/BOM=", l-(q-p)); + sstrncpy(&q, stat_bom_val, l-(q-p)); + } + + if(stat_boy_val){ + sstrncpy(&q, "/BOY=", l-(q-p)); + sstrncpy(&q, stat_boy_val, l-(q-p)); + } + + sstrncpy(&q, "\" action=\"", l-(q-p)); + + if(inherit_nick && *inherit_nick){ + sstrncpy(&q, "/INICK=", l-(q-p)); + sstrncpy(&q, inherit_nick, l-(q-p)); + fs_give((void **)&inherit_nick); + } + + if(action){ + if(action->is_a_role) + sstrncpy(&q, "/ROLE=1", l-(q-p)); + + if(action->is_a_incol) + sstrncpy(&q, "/ISINCOL=1", l-(q-p)); + + if(action->is_a_srch) + sstrncpy(&q, "/ISSRCH=1", l-(q-p)); + + if(action->is_a_score) + sstrncpy(&q, "/ISSCORE=1", l-(q-p)); + + if(action->is_a_filter){ + /* + * Older pine will interpret a filter that has no folder + * as a Delete, even if we set it up here to be a Just Set + * State filter. Disable the filter for older versions in that + * case. If kill is set then Delete is what is supposed to + * happen, so that's ok. If folder is set then Move is what is + * supposed to happen, so ok. + */ + if(!action->kill && !action->folder) + sstrncpy(&q, "/FILTER=2", l-(q-p)); + else + sstrncpy(&q, "/FILTER=1", l-(q-p)); + } + + if(action->is_a_other) + sstrncpy(&q, "/OTHER=1", l-(q-p)); + } + + if(score_act){ + sstrncpy(&q, "/SCORE=", l-(q-p)); + sstrncpy(&q, score_act, l-(q-p)); + fs_give((void **)&score_act); + } + + if(hdrtok_act){ + sstrncpy(&q, "/SCOREHDRTOK=", l-(q-p)); + sstrncpy(&q, hdrtok_act, l-(q-p)); + fs_give((void **)&hdrtok_act); + } + + if(from_act){ + sstrncpy(&q, "/FROM=", l-(q-p)); + sstrncpy(&q, from_act, l-(q-p)); + fs_give((void **) &from_act); + } + + if(replyto_act){ + sstrncpy(&q, "/REPL=", l-(q-p)); + sstrncpy(&q, replyto_act, l-(q-p)); + fs_give((void **)&replyto_act); + } + + if(fcc_act){ + sstrncpy(&q, "/FCC=", l-(q-p)); + sstrncpy(&q, fcc_act, l-(q-p)); + fs_give((void **)&fcc_act); + } + + if(litsig_act){ + sstrncpy(&q, "/LSIG=", l-(q-p)); + sstrncpy(&q, litsig_act, l-(q-p)); + fs_give((void **)&litsig_act); + } + + if(sig_act){ + sstrncpy(&q, "/SIG=", l-(q-p)); + sstrncpy(&q, sig_act, l-(q-p)); + fs_give((void **)&sig_act); + } + + if(templ_act){ + sstrncpy(&q, "/TEMPLATE=", l-(q-p)); + sstrncpy(&q, templ_act, l-(q-p)); + fs_give((void **)&templ_act); + } + + if(cstm_act){ + sstrncpy(&q, "/CSTM=", l-(q-p)); + sstrncpy(&q, cstm_act, l-(q-p)); + fs_give((void **)&cstm_act); + } + + if(smtp_act){ + sstrncpy(&q, "/SMTP=", l-(q-p)); + sstrncpy(&q, smtp_act, l-(q-p)); + fs_give((void **)&smtp_act); + } + + if(nntp_act){ + sstrncpy(&q, "/NNTP=", l-(q-p)); + sstrncpy(&q, nntp_act, l-(q-p)); + fs_give((void **)&nntp_act); + } + + if(repl_val){ + sstrncpy(&q, "/RTYPE=", l-(q-p)); + sstrncpy(&q, repl_val, l-(q-p)); + } + + if(forw_val){ + sstrncpy(&q, "/FTYPE=", l-(q-p)); + sstrncpy(&q, forw_val, l-(q-p)); + } + + if(comp_val){ + sstrncpy(&q, "/CTYPE=", l-(q-p)); + sstrncpy(&q, comp_val, l-(q-p)); + } + + if(incol_act){ + sstrncpy(&q, "/INCOL=", l-(q-p)); + sstrncpy(&q, incol_act, l-(q-p)); + fs_give((void **)&incol_act); + } + + if(sort_act){ + sstrncpy(&q, "/SORT=", l-(q-p)); + sstrncpy(&q, sort_act, l-(q-p)); + fs_give((void **)&sort_act); + } + + if(iform_act){ + sstrncpy(&q, "/IFORM=", l-(q-p)); + sstrncpy(&q, iform_act, l-(q-p)); + fs_give((void **)&iform_act); + } + + if(start_act){ + sstrncpy(&q, "/START=", l-(q-p)); + sstrncpy(&q, start_act, l-(q-p)); + } + + if(folder_act){ + sstrncpy(&q, "/FOLDER=", l-(q-p)); + sstrncpy(&q, folder_act, l-(q-p)); + fs_give((void **) &folder_act); + } + + if(filt_ifnotdel){ + sstrncpy(&q, filt_ifnotdel, l-(q-p)); + fs_give((void **) &filt_ifnotdel); + } + + if(filt_nonterm){ + sstrncpy(&q, filt_nonterm, l-(q-p)); + fs_give((void **) &filt_nonterm); + } + + if(filt_nokill){ + sstrncpy(&q, filt_nokill, l-(q-p)); + fs_give((void **) &filt_nokill); + } + + if(filt_new_val){ + sstrncpy(&q, "/STATN=", l-(q-p)); + sstrncpy(&q, filt_new_val, l-(q-p)); + } + + if(filt_del_val){ + sstrncpy(&q, "/STATD=", l-(q-p)); + sstrncpy(&q, filt_del_val, l-(q-p)); + } + + if(filt_imp_val){ + sstrncpy(&q, "/STATI=", l-(q-p)); + sstrncpy(&q, filt_imp_val, l-(q-p)); + } + + if(filt_ans_val){ + sstrncpy(&q, "/STATA=", l-(q-p)); + sstrncpy(&q, filt_ans_val, l-(q-p)); + } + + if(keyword_set){ + sstrncpy(&q, "/KEYSET=", l-(q-p)); + sstrncpy(&q, keyword_set, l-(q-p)); + fs_give((void **) &keyword_set); + } + + if(keyword_clr){ + sstrncpy(&q, "/KEYCLR=", l-(q-p)); + sstrncpy(&q, keyword_clr, l-(q-p)); + fs_give((void **) &keyword_clr); + } + + if(q-p < l) + *q++ = '\"'; + + if(q-p < l) + *q = '\0'; + + p[l-1] = '\0'; + + return(p); +} + + +void +convert_statebits_to_vals(long int bits, int *dval, int *aval, int *ival, int *nval) +{ + if(dval) + *dval = ACT_STAT_LEAVE; + if(aval) + *aval = ACT_STAT_LEAVE; + if(ival) + *ival = ACT_STAT_LEAVE; + if(nval) + *nval = ACT_STAT_LEAVE; + + if(ival){ + if(bits & F_FLAG) + *ival = ACT_STAT_SET; + else if(bits & F_UNFLAG) + *ival = ACT_STAT_CLEAR; + } + + if(aval){ + if(bits & F_ANS) + *aval = ACT_STAT_SET; + else if(bits & F_UNANS) + *aval = ACT_STAT_CLEAR; + } + + if(dval){ + if(bits & F_DEL) + *dval = ACT_STAT_SET; + else if(bits & F_UNDEL) + *dval = ACT_STAT_CLEAR; + } + + if(nval){ + if(bits & F_UNSEEN) + *nval = ACT_STAT_SET; + else if(bits & F_SEEN) + *nval = ACT_STAT_CLEAR; + } +} + + +/* + * The "searched" bit will be set for each message which matches. + * + * Args: patgrp -- Pattern to search with + * stream -- + * searchset -- Restrict search to this set + * section -- Searching a section of the message, not the whole thing + * get_score -- Function to return the score for a message + * flags -- Most of these are flags to mail_search_full. However, we + * overload the flags namespace and pass some flags of our + * own in here that we pick off before calling mail_search. + * Danger, danger, don't overlap with flag values defined + * for c-client (that we want to use). Flags that we will + * use here are: + * MP_IN_CCLIENT_CB + * If this is set we are in a callback from c-client + * because some imap data arrived. We don't want to + * call c-client again because it isn't re-entrant safe. + * This is only a problem if we need to get the text of + * a message to do the search, the envelope is cached + * already. + * MP_NOT + * We want a ! of the patgrp in the search. + * We also throw in SE_FREE for free, since we create + * the search program here. + * + * Returns: 1 if any message in the searchset matches this pattern + * 0 if no matches + * -1 if couldn't perform search because of no_fetch restriction + */ +int +match_pattern(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset, + char *section, long int (*get_score)(MAILSTREAM *, long int), + long int flags) +{ + SEARCHPGM *pgm; + SEARCHSET *s; + MESSAGECACHE *mc; + long i, msgno = 0L; + int in_client_callback = 0, not = 0; + + dprint((7, "match_pattern\n")); + + /* + * Is the current folder the right type and possibly the right specific + * folder for a match? + */ + if(!(patgrp && !patgrp->bogus && match_pattern_folder(patgrp, stream))) + return(0); + + /* + * NULL searchset means that there is no message to compare against. + * This is a match if the folder type matches above (that gets + * us here), and there are no patterns to match against. + * + * It is not totally clear what should be done in the case of an empty + * search set. If there is search criteria, and someone does something + * that is not specific to any messages (composing from scratch, + * forwarding an attachment), then we can't be sure what a user would + * expect. The original way was to just use the role, which we'll + * preserve here. + */ + if(!searchset) + return(1); + + /* + * change by sderr : match_pattern_folder will sometimes + * accept NULL streams, but if we are not in a folder-type-only + * match test, we don't + */ + if(!stream) + return(0); + + if(flags & MP_IN_CCLIENT_CB){ + in_client_callback++; + flags &= ~MP_IN_CCLIENT_CB; + } + + if(flags & MP_NOT){ + not++; + flags &= ~MP_NOT; + } + + flags |= SE_FREE; + + if(patgrp->stat_bom != PAT_STAT_EITHER){ + if(patgrp->stat_bom == PAT_STAT_YES){ + if(!ps_global->beginning_of_month){ + return(0); + } + } + else if(patgrp->stat_bom == PAT_STAT_NO){ + if(ps_global->beginning_of_month){ + return(0); + } + } + } + + if(patgrp->stat_boy != PAT_STAT_EITHER){ + if(patgrp->stat_boy == PAT_STAT_YES){ + if(!ps_global->beginning_of_year){ + return(0); + } + } + else if(patgrp->stat_boy == PAT_STAT_NO){ + if(ps_global->beginning_of_year){ + return(0); + } + } + } + + if(in_client_callback && is_imap_stream(stream) + && (patgrp->alltext || patgrp->bodytext)) + return(-1); + + pgm = match_pattern_srchpgm(patgrp, stream, searchset); + if(not && !(is_imap_stream(stream) && !modern_imap_stream(stream))){ + SEARCHPGM *srchpgm; + + srchpgm = pgm; + pgm = mail_newsearchpgm(); + pgm->not = mail_newsearchpgmlist(); + pgm->not->pgm = srchpgm; + } + + if((patgrp->alltext || patgrp->bodytext) + && (!is_imap_stream(stream) || modern_imap_stream(stream))) + /* + * Cache isn't going to work. Search on server. + * Except that is likely to not work on an old imap server because + * the OR criteria won't work and we are likely to have some ORs. + * So turn off the NOSERVER flag (and search on server if remote) + * unless the server is an old server. It doesn't matter if we + * turn if off if it's not an imap stream, but we do it anyway. + */ + flags &= ~SE_NOSERVER; + + if(section){ + /* + * Mail_search_full only searches the top-level msg. We want to + * search an attached msg instead. First do the stuff + * that mail_search_full would have done before calling + * mail_search_msg, then call mail_search_msg with a section number. + * Mail_search_msg does take a section number even though + * mail_search_full doesn't. + */ + + /* + * We'll only ever set section if the searchset is a single message. + */ + if(pgm->msgno->next == NULL && pgm->msgno->first == pgm->msgno->last) + msgno = pgm->msgno->first; + + for(i = 1L; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) != NULL) + mc->searched = NIL; + + if(mail_search_msg(stream,msgno,section,pgm) + && msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno))) + mc->searched = T; + + if(flags & SE_FREE) + mail_free_searchpgm(&pgm); + } + else{ + /* + * Here we could be checking on the return value to see if + * the search was "successful" or not. It may be the case + * that we'd want to stop trying filtering if we got some + * sort of error, but for now we would just continue on + * to the next filter. + */ + pine_mail_search_full(stream, "UTF-8", pgm, flags); + } + + /* we searched without the not, reverse it */ + if(not && is_imap_stream(stream) && !modern_imap_stream(stream)){ + for(msgno = 1L; msgno < mn_get_total(sp_msgmap(stream)); msgno++) + if(stream && msgno && msgno <= stream->nmsgs + && (mc=mail_elt(stream,msgno)) && mc->searched) + mc->searched = NIL; + else + mc->searched = T; + } + + /* check scores */ + if(get_score && scores_are_used(SCOREUSE_GET) && patgrp->do_score){ + char *savebits; + SEARCHSET *ss; + + /* + * Get_score may call build_header_line recursively (we may + * be in build_header_line now) so we have to preserve and + * restore the sequence bits. + */ + savebits = (char *)fs_get((stream->nmsgs+1) * sizeof(char)); + + for(i = 1L; i <= stream->nmsgs; i++){ + if((mc = mail_elt(stream, i)) != NULL){ + savebits[i] = mc->sequence; + mc->sequence = 0; + } + } + + /* + * Build a searchset which will get all the scores that we + * need but not more. + */ + for(s = searchset; s; s = s->next) + for(msgno = s->first; msgno <= s->last; msgno++) + if(msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno)) && mc->searched + && get_msg_score(stream, msgno) == SCORE_UNDEF) + mc->sequence = 1; + + if((ss = build_searchset(stream)) != NULL){ + (void)calculate_some_scores(stream, ss, in_client_callback); + mail_free_searchset(&ss); + } + + /* + * Now check the scores versus the score intervals to see if + * any of the messages which have matched up to this point can + * be tossed because they don't match the score interval. + */ + for(s = searchset; s; s = s->next) + for(msgno = s->first; msgno <= s->last; msgno++) + if(msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno)) && mc->searched){ + long score; + + score = (*get_score)(stream, msgno); + + /* + * If the score is outside all of the intervals, + * turn off the searched bit. + * So that means we check each interval and if + * it is inside any interval we stop and leave + * the bit set. If it is outside we keep checking. + */ + if(score != SCORE_UNDEF){ + INTVL_S *iv; + + for(iv = patgrp->score; iv; iv = iv->next) + if(score >= iv->imin && score <= iv->imax) + break; + + if(!iv) + mc->searched = NIL; + } + } + + for(i = 1L; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) != NULL) + mc->sequence = savebits[i]; + + fs_give((void **)&savebits); + } + + /* if there are still matches, check for 8bit subject match */ + if(patgrp->stat_8bitsubj != PAT_STAT_EITHER) + find_8bitsubj_in_messages(stream, searchset, patgrp->stat_8bitsubj, 1); + + /* if there are still matches, check for charset matches */ + if(patgrp->charsets) + find_charsets_in_messages(stream, searchset, patgrp, 1); + + /* Still matches, check addrbook */ + if(patgrp->inabook != IAB_EITHER) + address_in_abook(stream, searchset, patgrp->inabook, patgrp->abooks); + + /* Still matches? Run the categorization command on each msg. */ + if(pith_opt_filter_pattern_cmd) + (*pith_opt_filter_pattern_cmd)(patgrp->category_cmd, searchset, stream, patgrp->cat_lim, patgrp->cat); + + for(s = searchset; s; s = s->next) + for(msgno = s->first; msgno > 0L && msgno <= s->last; msgno++) + if(msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno)) && mc->searched) + return(1); + + return(0); +} + + +/* + * Look through messages in searchset to see if they contain 8bit + * characters in their subjects. All of the messages in + * searchset should initially have the searched bit set. Turn off the + * searched bit where appropriate. + */ +void +find_8bitsubj_in_messages(MAILSTREAM *stream, SEARCHSET *searchset, + int stat_8bitsubj, int saveseqbits) +{ + char *savebits = NULL; + SEARCHSET *s, *ss = NULL; + MESSAGECACHE *mc; + long count = 0L; + unsigned long msgno; + + /* + * If we are being called while in build_header_line we may + * call build_header_line recursively. So save and restore the + * sequence bits. + */ + if(saveseqbits) + savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char)); + + for(msgno = 1L; msgno <= stream->nmsgs; msgno++){ + if((mc = mail_elt(stream, msgno)) != NULL){ + if(savebits) + savebits[msgno] = mc->sequence; + + mc->sequence = 0; + } + } + + /* + * Build a searchset so we can look at all the envelopes + * we need to look at but only those we need to look at. + * Everything with the searched bit set is still a + * possibility, so restrict to that set. + */ + + for(s = searchset; s; s = s->next) + for(msgno = s->first; msgno <= s->last; msgno++) + if(msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno)) && mc->searched){ + mc->sequence = 1; + count++; + } + + ss = build_searchset(stream); + + if(count){ + SEARCHSET **sset; + + mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count); + + /* + * This causes the lookahead to fetch precisely + * the messages we want (in the searchset) instead + * of just fetching the next 20 sequential + * messages. If the searching so far has caused + * a sparse searchset in a large mailbox, the + * difference can be substantial. + * This resets automatically after the first fetch. + */ + sset = (SEARCHSET **) mail_parameters(stream, + GET_FETCHLOOKAHEAD, + (void *) stream); + if(sset) + *sset = ss; + } + + for(s = ss; s; s = s->next){ + for(msgno = s->first; msgno <= s->last; msgno++){ + ENVELOPE *e; + + if(!stream || msgno <= 0L || msgno > stream->nmsgs) + continue; + + e = pine_mail_fetchenvelope(stream, msgno); + if(stat_8bitsubj == PAT_STAT_YES){ + if(e && e->subject){ + char *p; + + for(p = e->subject; *p; p++) + if(*p & 0x80) + break; + + if(!*p && msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno))) + mc->searched = NIL; + } + else if(msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno))) + mc->searched = NIL; + } + else if(stat_8bitsubj == PAT_STAT_NO){ + if(e && e->subject){ + char *p; + + for(p = e->subject; *p; p++) + if(*p & 0x80) + break; + + if(*p && msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno))) + mc->searched = NIL; + } + } + } + } + + if(savebits){ + for(msgno = 1L; msgno <= stream->nmsgs; msgno++) + if((mc = mail_elt(stream, msgno)) != NULL) + mc->sequence = savebits[msgno]; + + fs_give((void **) &savebits); + } + + if(ss) + mail_free_searchset(&ss); +} + + +/* + * Look through messages in searchset to see if they contain any of the + * charsets or scripts listed in charsets pattern. All of the messages in + * searchset should initially have the searched bit set. Turn off the + * searched bit where appropriate. + */ +void +find_charsets_in_messages(MAILSTREAM *stream, SEARCHSET *searchset, + PATGRP_S *patgrp, int saveseqbits) +{ + char *savebits = NULL; + unsigned long msgno; + long count = 0L; + MESSAGECACHE *mc; + SEARCHSET *s, *ss; + + if(!stream || !patgrp) + return; + + /* + * When we actually want to use charsets, we convert it into a list + * of charsets instead of the mixed list of scripts and charsets and + * we eliminate duplicates. This is more efficient when we actually + * do the lookups and compares. + */ + if(!patgrp->charsets_list){ + PATTERN_S *cs; + const CHARSET *cset; + STRLIST_S *sl = NULL, *newsl; + unsigned long scripts = 0L; + SCRIPT *script; + + for(cs = patgrp->charsets; cs; cs = cs->next){ + /* + * Run through the charsets pattern looking for + * scripts and set the corresponding script bits. + * If it isn't a script, it is a character set. + */ + if(cs->substring && (script = utf8_script(cs->substring))) + scripts |= script->script; + else{ + /* add it to list as a specific character set */ + newsl = new_strlist(cs->substring); + if(compare_strlists_for_match(sl, newsl)) /* already in list */ + free_strlist(&newsl); + else{ + newsl->next = sl; + sl = newsl; + } + } + } + + /* + * Now scripts has a bit set for each script the user + * specified in the charsets pattern. Go through all of + * the known charsets and include ones in these scripts. + */ + if(scripts){ + for(cset = utf8_charset(NIL); cset && cset->name; cset++){ + if(cset->script & scripts){ + + /* filter this out of each script, not very useful */ + if(!strucmp("ISO-2022-JP-2", cset->name) + || !strucmp("UTF-7", cset->name) + || !strucmp("UTF-8", cset->name)) + continue; + + /* add cset->name to the list */ + newsl = new_strlist(cset->name); + if(compare_strlists_for_match(sl, newsl)) + free_strlist(&newsl); + else{ + newsl->next = sl; + sl = newsl; + } + } + } + } + + patgrp->charsets_list = sl; + } + + /* + * This may call build_header_line recursively because we may be in + * build_header_line now. So we have to preserve and restore the + * sequence bits since we want to use them here. + */ + if(saveseqbits) + savebits = (char *) fs_get((stream->nmsgs+1) * sizeof(char)); + + for(msgno = 1L; msgno <= stream->nmsgs; msgno++){ + if((mc = mail_elt(stream, msgno)) != NULL){ + if(savebits) + savebits[msgno] = mc->sequence; + + mc->sequence = 0; + } + } + + + /* + * Build a searchset so we can look at all the bodies + * we need to look at but only those we need to look at. + * Everything with the searched bit set is still a + * possibility, so restrict to that set. + */ + + for(s = searchset; s; s = s->next) + for(msgno = s->first; msgno <= s->last; msgno++) + if(msgno > 0L && msgno <= stream->nmsgs + && (mc = mail_elt(stream, msgno)) && mc->searched){ + mc->sequence = 1; + count++; + } + + ss = build_searchset(stream); + + if(count){ + SEARCHSET **sset; + + mail_parameters(NULL, SET_FETCHLOOKAHEADLIMIT, (void *) count); + + /* + * This causes the lookahead to fetch precisely + * the messages we want (in the searchset) instead + * of just fetching the next 20 sequential + * messages. If the searching so far has caused + * a sparse searchset in a large mailbox, the + * difference can be substantial. + * This resets automatically after the first fetch. + */ + sset = (SEARCHSET **) mail_parameters(stream, + GET_FETCHLOOKAHEAD, + (void *) stream); + if(sset) + *sset = ss; + } + + for(s = ss; s; s = s->next){ + for(msgno = s->first; msgno <= s->last; msgno++){ + + if(msgno <= 0L || msgno > stream->nmsgs) + continue; + + if(patgrp->charsets_list + && charsets_present_in_msg(stream,msgno,patgrp->charsets_list)){ + if(patgrp->charsets->not){ + if((mc = mail_elt(stream, msgno))) + mc->searched = NIL; + } + /* else leave it */ + } + else{ /* charset isn't in message */ + if(!patgrp->charsets->not){ + if((mc = mail_elt(stream, msgno))) + mc->searched = NIL; + } + /* else leave it */ + } + } + } + + if(savebits){ + for(msgno = 1L; msgno <= stream->nmsgs; msgno++) + if((mc = mail_elt(stream, msgno)) != NULL) + mc->sequence = savebits[msgno]; + + fs_give((void **) &savebits); + } + + if(ss) + mail_free_searchset(&ss); +} + + +/* + * Look for any of the charsets in this particular message. + * + * Returns 1 if there is a match, 0 otherwise. + */ +int +charsets_present_in_msg(MAILSTREAM *stream, long unsigned int rawmsgno, STRLIST_S *charsets) +{ + BODY *body = NULL; + ENVELOPE *env = NULL; + STRLIST_S *msg_charsets = NULL; + int ret = 0; + + if(charsets && stream && rawmsgno > 0L && rawmsgno <= stream->nmsgs){ + env = pine_mail_fetchstructure(stream, rawmsgno, &body); + collect_charsets_from_subj(env, &msg_charsets); + collect_charsets_from_body(body, &msg_charsets); + if(msg_charsets){ + ret = compare_strlists_for_match(msg_charsets, charsets); + free_strlist(&msg_charsets); + } + } + + return(ret); +} + + +void +collect_charsets_from_subj(ENVELOPE *env, STRLIST_S **listptr) +{ + STRLIST_S *newsl; + char *text, *e; + + if(listptr && env && env->subject){ + /* find encoded word */ + for(text = env->subject; *text; text++){ + if((*text == '=') && (text[1] == '?') && isalpha(text[2]) && + (e = strchr(text+2,'?'))){ + *e = '\0'; /* tie off charset name */ + + newsl = new_strlist(text+2); + *e = '?'; + + if(compare_strlists_for_match(*listptr, newsl)) + free_strlist(&newsl); + else{ + newsl->next = *listptr; + *listptr = newsl; + } + } + } + } +} + + +/* + * Check for any of the charsets in any of the charset params in + * any of the text parts of the body of a message. Put them in the list + * pointed to by listptr. + */ +void +collect_charsets_from_body(struct mail_bodystruct *body, STRLIST_S **listptr) +{ + PART *part; + char *cset; + + if(listptr && body){ + switch(body->type){ + case TYPEMULTIPART: + for(part = body->nested.part; part; part = part->next) + collect_charsets_from_body(&part->body, listptr); + + break; + + case TYPEMESSAGE: + if(!strucmp(body->subtype, "RFC822")){ + collect_charsets_from_subj(body->nested.msg->env, listptr); + collect_charsets_from_body(body->nested.msg->body, listptr); + break; + } + /* else fall through to text case */ + + case TYPETEXT: + cset = parameter_val(body->parameter, "charset"); + if(cset){ + STRLIST_S *newsl; + + newsl = new_strlist(cset); + + if(compare_strlists_for_match(*listptr, newsl)) + free_strlist(&newsl); + else{ + newsl->next = *listptr; + *listptr = newsl; + } + + fs_give((void **) &cset); + } + + break; + + default: /* non-text terminal mode */ + break; + } + } +} + + +/* + * If any of the names in list1 is the same as any of the names in list2 + * then return 1, else return 0. Comparison is case independent. + */ +int +compare_strlists_for_match(STRLIST_S *list1, STRLIST_S *list2) +{ + int ret = 0; + STRLIST_S *cs1, *cs2; + + for(cs1 = list1; !ret && cs1; cs1 = cs1->next) + for(cs2 = list2; !ret && cs2; cs2 = cs2->next) + if(cs1->name && cs2->name && !strucmp(cs1->name, cs2->name)) + ret = 1; + + return(ret); +} + + +int +match_pattern_folder(PATGRP_S *patgrp, MAILSTREAM *stream) +{ + int is_news; + + /* change by sderr : we match FLDR_ANY even if stream is NULL */ + return((patgrp->fldr_type == FLDR_ANY) + || (stream + && (((is_news = IS_NEWS(stream)) + && patgrp->fldr_type == FLDR_NEWS) + || (!is_news && patgrp->fldr_type == FLDR_EMAIL) + || (patgrp->fldr_type == FLDR_SPECIFIC + && match_pattern_folder_specific(patgrp->folder, + stream, FOR_PATTERN))))); +} + + +/* + * Returns positive if this stream is open on one of the folders in the + * folders argument, 0 otherwise. + * + * If FOR_PATTERN is set, this interprets simple names as nicknames in + * the incoming collection, otherwise it treats simple names as being in + * the primary collection. + * If FOR_FILT is set, the folder names are detokenized before being used. + */ +int +match_pattern_folder_specific(PATTERN_S *folders, MAILSTREAM *stream, int flags) +{ + PATTERN_S *p; + int match = 0; + char *patfolder, *free_this = NULL; + + dprint((8, "match_pattern_folder_specific\n")); + + if(!(stream && stream->mailbox && stream->mailbox[0])) + return(0); + + /* + * For each of the folders in the pattern, see if we get + * a match. We're just looking for any match. If none match, + * we return 0, otherwise we fall through and check the rest + * of the pattern. The fact that the string is called "substring" + * is not meaningful. We're just using the convenient pattern + * structure to store a list of folder names. They aren't + * substrings of names, they are the whole name. + */ + for(p = folders; !match && p; p = p->next){ + free_this = NULL; + if(flags & FOR_FILTER) + patfolder = free_this = detoken_src(p->substring, FOR_FILT, NULL, + NULL, NULL, NULL); + else + patfolder = p->substring; + + if(patfolder + && (!strucmp(patfolder, ps_global->inbox_name) + || !strcmp(patfolder, ps_global->VAR_INBOX_PATH))){ + if(sp_flagged(stream, SP_INBOX)) + match++; + } + else{ + char *fname; + char *t, *streamfolder; + char tmp1[MAILTMPLEN], tmp2[MAX(MAILTMPLEN,NETMAXMBX)]; + CONTEXT_S *cntxt = NULL; + + if(flags & FOR_PATTERN){ + /* + * See if patfolder is a nickname in the incoming collection. + * If so, use its real name instead. + */ + if(patfolder[0] && + (ps_global->context_list->use & CNTXT_INCMNG) && + (fname = (folder_is_nick(patfolder, + FOLDERS(ps_global->context_list), + 0)))) + patfolder = fname; + } + else{ + char *save_ref = NULL; + + /* + * If it's an absolute pathname, we treat is as a local file + * instead of interpreting it in the primary context. + */ + if(!is_absolute_path(patfolder) + && !(cntxt = default_save_context(ps_global->context_list))) + cntxt = ps_global->context_list; + + /* + * Because this check is independent of where the user is + * in the folder hierarchy and has nothing to do with that, + * we want to ignore the reference field built into the + * context. Zero it out temporarily here. + */ + if(cntxt && cntxt->dir){ + save_ref = cntxt->dir->ref; + cntxt->dir->ref = NULL; + } + + patfolder = context_apply(tmp1, cntxt, patfolder, sizeof(tmp1)); + if(save_ref) + cntxt->dir->ref = save_ref; + } + + switch(patfolder[0]){ + case '{': + if(stream->mailbox[0] == '{' && + same_stream(patfolder, stream) && + (streamfolder = strindex(&stream->mailbox[1], '}')) && + (t = strindex(&patfolder[1], '}')) && + (!strcmp(t+1, streamfolder+1) || + (*(t+1) == '\0' && !strcmp("INBOX", streamfolder+1)))) + match++; + + break; + + case '#': + if(!strcmp(patfolder, stream->mailbox)) + match++; + + break; + + default: + t = (strlen(patfolder) < (MAILTMPLEN/2)) + ? mailboxfile(tmp2, patfolder) : NULL; + if(t && *t && !strcmp(t, stream->mailbox)) + match++; + + break; + } + } + + if(free_this) + fs_give((void **) &free_this); + } + + return(match); +} + + +/* + * generate a search program corresponding to the provided patgrp + */ +SEARCHPGM * +match_pattern_srchpgm(PATGRP_S *patgrp, MAILSTREAM *stream, SEARCHSET *searchset) +{ + SEARCHPGM *pgm, *tmppgm; + SEARCHOR *or; + SEARCHSET **sp; + + pgm = mail_newsearchpgm(); + + sp = &pgm->msgno; + /* copy the searchset */ + while(searchset){ + SEARCHSET *s; + + s = mail_newsearchset(); + s->first = searchset->first; + s->last = searchset->last; + searchset = searchset->next; + *sp = s; + sp = &s->next; + } + + if(!patgrp) + return(pgm); + + if(patgrp->subj){ + if(patgrp->subj->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("subject", patgrp->subj, tmppgm); + } + + if(patgrp->cc){ + if(patgrp->cc->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("cc", patgrp->cc, tmppgm); + } + + if(patgrp->from){ + if(patgrp->from->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("from", patgrp->from, tmppgm); + } + + if(patgrp->to){ + if(patgrp->to->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("to", patgrp->to, tmppgm); + } + + if(patgrp->sender){ + if(patgrp->sender->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("sender", patgrp->sender, tmppgm); + } + + if(patgrp->news){ + if(patgrp->news->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("newsgroups", patgrp->news, tmppgm); + } + + /* To OR Cc */ + if(patgrp->recip){ + if(patgrp->recip->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + or = next_or(&tmppgm->or); + + set_up_search_pgm("to", patgrp->recip, or->first); + set_up_search_pgm("cc", patgrp->recip, or->second); + } + + /* To OR Cc OR From */ + if(patgrp->partic){ + if(patgrp->partic->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + or = next_or(&tmppgm->or); + + set_up_search_pgm("to", patgrp->partic, or->first); + + or->second->or = mail_newsearchor(); + set_up_search_pgm("cc", patgrp->partic, or->second->or->first); + set_up_search_pgm("from", patgrp->partic, or->second->or->second); + } + + if(patgrp->arbhdr){ + ARBHDR_S *a; + + for(a = patgrp->arbhdr; a; a = a->next) + if(a->field && a->field[0] && a->p){ + if(a->p->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm(a->field, a->p, tmppgm); + } + } + + if(patgrp->alltext){ + if(patgrp->alltext->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("alltext", patgrp->alltext, tmppgm); + } + + if(patgrp->bodytext){ + if(patgrp->bodytext->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + set_up_search_pgm("bodytext", patgrp->bodytext, tmppgm); + } + + if(patgrp->keyword){ + PATTERN_S *p_old, *p_new, *new_pattern = NULL, **nextp; + char *q; + + if(patgrp->keyword->not) + tmppgm = next_not(pgm); + else + tmppgm = pgm; + + /* + * The keyword entries may be nicknames instead of the actual + * keywords, so those need to be converted to actual keywords. + * + * If we search for keywords that are not defined for a folder + * we may get error messages back that we don't want instead of + * just no match. We will build a replacement pattern here which + * contains only the defined subset of the keywords. + */ + + nextp = &new_pattern; + + for(p_old = patgrp->keyword; p_old; p_old = p_old->next){ + q = nick_to_keyword(p_old->substring); + if(user_flag_index(stream, q) >= 0){ + p_new = (PATTERN_S *) fs_get(sizeof(*p_new)); + memset(p_new, 0, sizeof(*p_new)); + p_new->substring = cpystr(q); + *nextp = p_new; + nextp = &p_new->next; + } + } + + /* + * If there are some matching keywords that are defined in + * the folder, then we are ok because we will match only if + * we match one of those. However, if the list is empty, then + * we can't just leave this part of the search program empty. + * That would result in a match instead of not a match. + * We can fake our way around the problem with NOT. If the + * list is empty we want the opposite, so we insert a NOT in + * front of an empty program. We may end up with NOT NOT if + * this was already NOT'd, but that's ok, too. Alternatively, + * we could undo the first NOT instead. + */ + + if(new_pattern){ + set_up_search_pgm("keyword", new_pattern, tmppgm); + free_pattern(&new_pattern); + } + else + (void) next_not(tmppgm); /* add NOT of something that matches, + so the NOT thing doesn't match */ + } + + if(patgrp->do_age && patgrp->age){ + INTVL_S *iv; + SEARCHOR *or; + + tmppgm = pgm; + + for(iv = patgrp->age; iv; iv = iv->next){ + if(iv->next){ + or = next_or(&tmppgm->or); + set_search_by_age(iv, or->first, patgrp->age_uses_sentdate); + tmppgm = or->second; + } + else + set_search_by_age(iv, tmppgm, patgrp->age_uses_sentdate); + } + } + + if(patgrp->do_size && patgrp->size){ + INTVL_S *iv; + SEARCHOR *or; + + tmppgm = pgm; + + for(iv = patgrp->size; iv; iv = iv->next){ + if(iv->next){ + or = next_or(&tmppgm->or); + set_search_by_size(iv, or->first); + tmppgm = or->second; + } + else + set_search_by_size(iv, tmppgm); + } + } + + SETPGMSTATUS(patgrp->stat_new,pgm->unseen,pgm->seen); + SETPGMSTATUS(patgrp->stat_rec,pgm->recent,pgm->old); + SETPGMSTATUS(patgrp->stat_del,pgm->deleted,pgm->undeleted); + SETPGMSTATUS(patgrp->stat_imp,pgm->flagged,pgm->unflagged); + SETPGMSTATUS(patgrp->stat_ans,pgm->answered,pgm->unanswered); + + return(pgm); +} + + +SEARCHPGM * +next_not(SEARCHPGM *pgm) +{ + SEARCHPGMLIST *not, **not_ptr; + + if(!pgm) + return(NULL); + + /* find next unused not slot */ + for(not = pgm->not; not && not->next; not = not->next) + ; + + if(not) + not_ptr = ¬->next; + else + not_ptr = &pgm->not; + + /* allocate */ + *not_ptr = mail_newsearchpgmlist(); + + return((*not_ptr)->pgm); +} + + +SEARCHOR * +next_or(struct search_or **startingor) +{ + SEARCHOR *or, **or_ptr; + + /* find next unused or slot */ + for(or = (*startingor); or && or->next; or = or->next) + ; + + if(or) + or_ptr = &or->next; + else + or_ptr = startingor; + + /* allocate */ + *or_ptr = mail_newsearchor(); + + return(*or_ptr); +} + + +void +set_up_search_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm) +{ + SEARCHOR *or; + + if(field && pattern && pgm){ + + /* + * To is special because we want to use the ReSent-To header instead + * of the To header if it exists. We set up something like: + * + * if((resent-to matches pat1 or pat2...) + * OR + * (<resent-to doesn't exist> AND (to matches pat1 or pat2...))) + * + * Some servers (Exchange, apparently) seem to have trouble with + * the search for the empty string to decide if the header exists + * or not. So, we will search for either the empty string OR the + * header with a SPACE in it. Some still have trouble with this + * so we are changing it to be off by default. + */ + if(!strucmp(field, "to") && F_ON(F_USE_RESENTTO, ps_global)){ + or = next_or(&pgm->or); + + add_type_to_pgm("resent-to", pattern, or->first); + + /* check for resent-to doesn't exist */ + or->second->not = mail_newsearchpgmlist(); + + or->second->not->pgm->or = mail_newsearchor(); + set_srch("resent-to", " ", or->second->not->pgm->or->first); + set_srch("resent-to", "", or->second->not->pgm->or->second); + + /* now add the real To search to second */ + add_type_to_pgm(field, pattern, or->second); + } + else + add_type_to_pgm(field, pattern, pgm); + } +} + + +void +add_type_to_pgm(char *field, PATTERN_S *pattern, SEARCHPGM *pgm) +{ + PATTERN_S *p; + SEARCHOR *or; + SEARCHPGM *notpgm, *tpgm; + int cnt = 0; + + if(field && pattern && pgm){ + /* + * Here is a weird bit of logic. What we want here is simply + * A or B or C or D + * for all of the elements of pattern. Ors are a bit complicated. + * The list of ORs in the SEARCHPGM structure are ANDed together, + * not ORd together. It's for things like + * Subject A or B AND From C or D + * The Subject part would be one member of the OR list and the From + * part would be another member of the OR list. Instead we want + * a big OR which may have more than two members (first and second) + * but the structure just has two members. So we have to build an + * OR tree and we build it by going down one branch of the tree + * instead of by balancing the branches. + * + * or + * / \ + * first==A second + * / \ + * first==B second + * / \ + * first==C second==D + * + * There is an additional problem. Some servers don't like deeply + * nested logic in the SEARCH command. The tree above produces a + * fairly deeply nested command if the user wants to match on + * several different From addresses or Subjects... + * We use the tried and true equation + * + * (A or B) == !(!A and !B) + * + * to change the deeply nested OR tree into ANDs which aren't nested. + * Right now we're only doing that if the nesting is fairly deep. + * We can think of some reasons to do that. First, we know that the + * OR thing works, that's what we've been using for a while and the + * only problem is the deep nesting. 2nd, it is easier to understand. + * 3rd, it looks dumb to use NOT NOT A instead of A. + * It is probably dumb to mix the two, but what the heck. + * Hubert 2003-04-02 + */ + for(p = pattern; p; p = p->next) + cnt++; + + if(cnt < 10){ /* use ORs if count is low */ + for(p = pattern; p; p = p->next){ + if(p->next){ + or = next_or(&pgm->or); + + set_srch(field, p->substring ? p->substring : "", or->first); + pgm = or->second; + } + else + set_srch(field, p->substring ? p->substring : "", pgm); + } + } + else{ /* else use ANDs */ + /* ( A or B or C ) <=> ! ( !A and !B and !C ) */ + + /* first, NOT of the whole thing */ + notpgm = next_not(pgm); + + /* then the not list is ANDed together */ + for(p = pattern; p; p = p->next){ + tpgm = next_not(notpgm); + set_srch(field, p->substring ? p->substring : "", tpgm); + } + } + } +} + + +void +set_srch(char *field, char *value, SEARCHPGM *pgm) +{ + char *decoded; + STRINGLIST **list; + + if(!(field && value && pgm)) + return; + + if(!strucmp(field, "subject")) + list = &pgm->subject; + else if(!strucmp(field, "from")) + list = &pgm->from; + else if(!strucmp(field, "to")) + list = &pgm->to; + else if(!strucmp(field, "cc")) + list = &pgm->cc; + else if(!strucmp(field, "sender")) + list = &pgm->sender; + else if(!strucmp(field, "reply-to")) + list = &pgm->reply_to; + else if(!strucmp(field, "in-reply-to")) + list = &pgm->in_reply_to; + else if(!strucmp(field, "message-id")) + list = &pgm->message_id; + else if(!strucmp(field, "newsgroups")) + list = &pgm->newsgroups; + else if(!strucmp(field, "followup-to")) + list = &pgm->followup_to; + else if(!strucmp(field, "alltext")) + list = &pgm->text; + else if(!strucmp(field, "bodytext")) + list = &pgm->body; + else if(!strucmp(field, "keyword")) + list = &pgm->keyword; + else{ + set_srch_hdr(field, value, pgm); + return; + } + + if(!list) + return; + + *list = mail_newstringlist(); + decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, value); + + (*list)->text.data = (unsigned char *)cpystr(decoded); + (*list)->text.size = strlen(decoded); +} + + +void +set_srch_hdr(char *field, char *value, SEARCHPGM *pgm) +{ + char *decoded; + SEARCHHEADER **hdr; + + if(!(field && value && pgm)) + return; + + hdr = &pgm->header; + if(!hdr) + return; + + decoded = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, + SIZEOF_20KBUF, value); + while(*hdr && (*hdr)->next) + *hdr = (*hdr)->next; + + if(*hdr) + (*hdr)->next = mail_newsearchheader(field, decoded); + else + *hdr = mail_newsearchheader(field, decoded); +} + + +void +set_search_by_age(INTVL_S *age, SEARCHPGM *pgm, int age_uses_sentdate) +{ + time_t now, comparetime; + struct tm *tm; + unsigned short i; + + if(!(age && pgm)) + return; + + now = time(0); + + if(age->imin >= 0L && age->imin == age->imax){ + comparetime = now; + comparetime -= (age->imin * 86400L); + tm = localtime(&comparetime); + if(tm && tm->tm_year >= 70){ + i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1, + tm->tm_mday); + if(age_uses_sentdate) + pgm->senton = i; + else + pgm->on = i; + } + } + else{ + /* + * The 20000's are just protecting against overflows. + * That's back past the start of email time, anyway. + */ + if(age->imin > 0L && age->imin < 20000L){ + comparetime = now; + comparetime -= ((age->imin - 1L) * 86400L); + tm = localtime(&comparetime); + if(tm && tm->tm_year >= 70){ + i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1, + tm->tm_mday); + if(age_uses_sentdate) + pgm->sentbefore = i; + else + pgm->before = i; + } + } + + if(age->imax >= 0L && age->imax < 20000L){ + comparetime = now; + comparetime -= (age->imax * 86400L); + tm = localtime(&comparetime); + if(tm && tm->tm_year >= 70){ + i = mail_shortdate(tm->tm_year - 70, tm->tm_mon + 1, + tm->tm_mday); + if(age_uses_sentdate) + pgm->sentsince = i; + else + pgm->since = i; + } + } + } +} + + +void +set_search_by_size(INTVL_S *size, SEARCHPGM *pgm) +{ + if(!(size && pgm)) + return; + + /* + * INTVL_S intervals include the endpoints, pgm larger and smaller + * do not include the endpoints. + */ + if(size->imin != INTVL_UNDEF && size->imin > 0L) + pgm->larger = size->imin - 1L; + + if(size->imax != INTVL_UNDEF && size->imax >= 0L && size->imax != INTVL_INF) + pgm->smaller = size->imax + 1L; +} + + +static char *extra_hdrs; + +/* + * Run through the patterns and note which headers we'll need to ask for + * which aren't normally asked for and so won't be cached. + */ +void +calc_extra_hdrs(void) +{ + PAT_S *pat = NULL; + int alloced_size; + long type = (ROLE_INCOL | ROLE_SCORE); + ARBHDR_S *a; + PAT_STATE pstate; + char *q, *p = NULL, *hdrs[MLCMD_COUNT + 1], **pp; + INDEX_COL_S *cdesc; +#define INITIALSIZE 1000 + + q = (char *)fs_get((INITIALSIZE+1) * sizeof(char)); + q[0] = '\0'; + alloced_size = INITIALSIZE; + p = q; + + /* + * *ALWAYS* make sure Resent-To is in the set of + * extra headers getting fetched. + * + * This is because we *will* reference it when we're + * building header lines and thus want it fetched with + * the standard envelope data. Worse, in the IMAP case + * we're called back from c-client with the envelope data + * so we can format and display the index lines as they + * arrive, so we have to ensure the resent-to field + * is in the cache so we don't reenter c-client + * to look for it from the callback. Yeouch. + */ + add_eh(&q, &p, "resent-to", &alloced_size); + add_eh(&q, &p, "resent-date", &alloced_size); + add_eh(&q, &p, "resent-from", &alloced_size); + add_eh(&q, &p, "resent-cc", &alloced_size); + add_eh(&q, &p, "resent-subject", &alloced_size); + + /* + * Sniff at viewer-hdrs too so we can include them + * if there are any... + */ + for(pp = ps_global->VAR_VIEW_HEADERS; pp && *pp; pp++) + if(non_eh(*pp)) + add_eh(&q, &p, *pp, &alloced_size); + + /* + * Be sure to ask for List management headers too + * since we'll offer their use in the message view + */ + for(pp = rfc2369_hdrs(hdrs); *pp; pp++) + add_eh(&q, &p, *pp, &alloced_size); + + if(nonempty_patterns(type, &pstate)) + for(pat = first_pattern(&pstate); + pat; + pat = next_pattern(&pstate)){ + /* + * This section wouldn't be necessary if sender was retreived + * from the envelope. But if not, we do need to add it. + */ + if(pat->patgrp && pat->patgrp->sender) + add_eh(&q, &p, "sender", &alloced_size); + + if(pat->patgrp && pat->patgrp->arbhdr) + for(a = pat->patgrp->arbhdr; a; a = a->next) + if(a->field && a->field[0] && a->p && non_eh(a->field)) + add_eh(&q, &p, a->field, &alloced_size); + } + + /* + * Check for use of HEADER or X-Priority in index-format. + */ + for(cdesc = ps_global->index_disp_format; cdesc->ctype != iNothing; cdesc++){ + if(cdesc->ctype == iHeader && cdesc->hdrtok && cdesc->hdrtok->hdrname + && cdesc->hdrtok->hdrname[0] && non_eh(cdesc->hdrtok->hdrname)) + add_eh(&q, &p, cdesc->hdrtok->hdrname, &alloced_size); + else if(cdesc->ctype == iPrio + || cdesc->ctype == iPrioAlpha + || cdesc->ctype == iPrioBang) + add_eh(&q, &p, PRIORITYNAME, &alloced_size); + } + + /* + * Check for use of scorevalhdrtok in scoring patterns. + */ + type = ROLE_SCORE; + if(nonempty_patterns(type, &pstate)) + for(pat = first_pattern(&pstate); + pat; + pat = next_pattern(&pstate)){ + /* + * This section wouldn't be necessary if sender was retreived + * from the envelope. But if not, we do need to add it. + */ + if(pat->action && pat->action->scorevalhdrtok + && pat->action->scorevalhdrtok->hdrname + && pat->action->scorevalhdrtok->hdrname[0] + && non_eh(pat->action->scorevalhdrtok->hdrname)) + add_eh(&q, &p, pat->action->scorevalhdrtok->hdrname, &alloced_size); + } + + set_extra_hdrs(q); + if(q) + fs_give((void **)&q); +} + + +int +non_eh(char *field) +{ + char **t; + static char *existing[] = {"subject", "from", "to", "cc", "sender", + "reply-to", "in-reply-to", "message-id", + "path", "newsgroups", "followup-to", + "references", NULL}; + + /* + * If it is one of these, we should already have it + * from the envelope or from the extra headers c-client + * already adds to the list (hdrheader and hdrtrailer + * in imap4r1.c, Aug 99, slh). + */ + for(t = existing; *t; t++) + if(!strucmp(field, *t)) + return(FALSE); + + return(TRUE); +} + + +/* + * Add field to extra headers string if not already there. + */ +void +add_eh(char **start, char **ptr, char *field, int *asize) +{ + char *s; + + /* already there? */ + for(s = *start; (s = srchstr(s, field)) != NULL; s++) + if(s[strlen(field)] == SPACE || s[strlen(field)] == '\0') + return; + + /* enough space for it? */ + while(strlen(field) + (*ptr - *start) + 1 > *asize){ + (*asize) *= 2; + fs_resize((void **)start, (*asize)+1); + *ptr = *start + strlen(*start); + } + + if(*ptr > *start) + sstrncpy(ptr, " ", *asize-(*ptr - *start)); + + sstrncpy(ptr, field, *asize-(*ptr - *start)); + + (*start)[*asize] = '\0'; +} + + +void +set_extra_hdrs(char *hdrs) +{ + free_extra_hdrs(); + if(hdrs && *hdrs) + extra_hdrs = cpystr(hdrs); +} + + +char * +get_extra_hdrs(void) +{ + return(extra_hdrs); +} + + +void +free_extra_hdrs(void) +{ + if(extra_hdrs) + fs_give((void **)&extra_hdrs); +} + + +int +is_ascii_string(char *str) +{ + if(!str) + return(0); + + while(*str && isascii(*str)) + str++; + + return(*str == '\0'); +} + + +void +free_patline(PAT_LINE_S **patline) +{ + if(patline && *patline){ + free_patline(&(*patline)->next); + if((*patline)->filename) + fs_give((void **)&(*patline)->filename); + if((*patline)->filepath) + fs_give((void **)&(*patline)->filepath); + free_pat(&(*patline)->first); + fs_give((void **)patline); + } +} + + +void +free_pat(PAT_S **pat) +{ + if(pat && *pat){ + free_pat(&(*pat)->next); + free_patgrp(&(*pat)->patgrp); + free_action(&(*pat)->action); + if((*pat)->raw) + fs_give((void **)&(*pat)->raw); + + fs_give((void **)pat); + } +} + + +void +free_patgrp(PATGRP_S **patgrp) +{ + if(patgrp && *patgrp){ + if((*patgrp)->nick) + fs_give((void **) &(*patgrp)->nick); + + if((*patgrp)->comment) + fs_give((void **) &(*patgrp)->comment); + + if((*patgrp)->category_cmd) + free_list_array(&(*patgrp)->category_cmd); + + if((*patgrp)->charsets_list) + free_strlist(&(*patgrp)->charsets_list); + + free_pattern(&(*patgrp)->to); + free_pattern(&(*patgrp)->cc); + free_pattern(&(*patgrp)->recip); + free_pattern(&(*patgrp)->partic); + free_pattern(&(*patgrp)->from); + free_pattern(&(*patgrp)->sender); + free_pattern(&(*patgrp)->news); + free_pattern(&(*patgrp)->subj); + free_pattern(&(*patgrp)->alltext); + free_pattern(&(*patgrp)->bodytext); + free_pattern(&(*patgrp)->keyword); + free_pattern(&(*patgrp)->charsets); + free_pattern(&(*patgrp)->folder); + free_arbhdr(&(*patgrp)->arbhdr); + free_intvl(&(*patgrp)->score); + free_intvl(&(*patgrp)->age); + fs_give((void **) patgrp); + } +} + + +void +free_pattern(PATTERN_S **pattern) +{ + if(pattern && *pattern){ + free_pattern(&(*pattern)->next); + if((*pattern)->substring) + fs_give((void **)&(*pattern)->substring); + fs_give((void **)pattern); + } +} + + +void +free_arbhdr(ARBHDR_S **arbhdr) +{ + if(arbhdr && *arbhdr){ + free_arbhdr(&(*arbhdr)->next); + if((*arbhdr)->field) + fs_give((void **)&(*arbhdr)->field); + free_pattern(&(*arbhdr)->p); + fs_give((void **)arbhdr); + } +} + + +void +free_intvl(INTVL_S **intvl) +{ + if(intvl && *intvl){ + free_intvl(&(*intvl)->next); + fs_give((void **) intvl); + } +} + + +void +free_action(ACTION_S **action) +{ + if(action && *action){ + if((*action)->from) + mail_free_address(&(*action)->from); + if((*action)->replyto) + mail_free_address(&(*action)->replyto); + if((*action)->fcc) + fs_give((void **)&(*action)->fcc); + if((*action)->litsig) + fs_give((void **)&(*action)->litsig); + if((*action)->sig) + fs_give((void **)&(*action)->sig); + if((*action)->template) + fs_give((void **)&(*action)->template); + if((*action)->scorevalhdrtok) + free_hdrtok(&(*action)->scorevalhdrtok); + if((*action)->cstm) + free_list_array(&(*action)->cstm); + if((*action)->smtp) + free_list_array(&(*action)->smtp); + if((*action)->nntp) + free_list_array(&(*action)->nntp); + if((*action)->nick) + fs_give((void **)&(*action)->nick); + if((*action)->inherit_nick) + fs_give((void **)&(*action)->inherit_nick); + if((*action)->incol) + free_color_pair(&(*action)->incol); + if((*action)->folder) + free_pattern(&(*action)->folder); + if((*action)->index_format) + fs_give((void **)&(*action)->index_format); + if((*action)->keyword_set) + free_pattern(&(*action)->keyword_set); + if((*action)->keyword_clr) + free_pattern(&(*action)->keyword_clr); + + fs_give((void **)action); + } +} + + +/* + * Returns an allocated copy of the pat. + * + * Args pat -- the source pat + * + * Returns a copy of pat. + */ +PAT_S * +copy_pat(PAT_S *pat) +{ + PAT_S *new_pat = NULL; + + if(pat){ + new_pat = (PAT_S *)fs_get(sizeof(*new_pat)); + memset((void *)new_pat, 0, sizeof(*new_pat)); + + new_pat->patgrp = copy_patgrp(pat->patgrp); + new_pat->action = copy_action(pat->action); + } + + return(new_pat); +} + + +/* + * Returns an allocated copy of the patgrp. + * + * Args patgrp -- the source patgrp + * + * Returns a copy of patgrp. + */ +PATGRP_S * +copy_patgrp(PATGRP_S *patgrp) +{ + char *p; + PATGRP_S *new_patgrp = NULL; + + if(patgrp){ + new_patgrp = (PATGRP_S *)fs_get(sizeof(*new_patgrp)); + memset((void *)new_patgrp, 0, sizeof(*new_patgrp)); + + if(patgrp->nick) + new_patgrp->nick = cpystr(patgrp->nick); + + if(patgrp->comment) + new_patgrp->comment = cpystr(patgrp->comment); + + if(patgrp->to){ + p = pattern_to_string(patgrp->to); + new_patgrp->to = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->to->not = patgrp->to->not; + } + + if(patgrp->from){ + p = pattern_to_string(patgrp->from); + new_patgrp->from = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->from->not = patgrp->from->not; + } + + if(patgrp->sender){ + p = pattern_to_string(patgrp->sender); + new_patgrp->sender = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->sender->not = patgrp->sender->not; + } + + if(patgrp->cc){ + p = pattern_to_string(patgrp->cc); + new_patgrp->cc = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->cc->not = patgrp->cc->not; + } + + if(patgrp->recip){ + p = pattern_to_string(patgrp->recip); + new_patgrp->recip = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->recip->not = patgrp->recip->not; + } + + if(patgrp->partic){ + p = pattern_to_string(patgrp->partic); + new_patgrp->partic = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->partic->not = patgrp->partic->not; + } + + if(patgrp->news){ + p = pattern_to_string(patgrp->news); + new_patgrp->news = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->news->not = patgrp->news->not; + } + + if(patgrp->subj){ + p = pattern_to_string(patgrp->subj); + new_patgrp->subj = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->subj->not = patgrp->subj->not; + } + + if(patgrp->alltext){ + p = pattern_to_string(patgrp->alltext); + new_patgrp->alltext = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->alltext->not = patgrp->alltext->not; + } + + if(patgrp->bodytext){ + p = pattern_to_string(patgrp->bodytext); + new_patgrp->bodytext = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->bodytext->not = patgrp->bodytext->not; + } + + if(patgrp->keyword){ + p = pattern_to_string(patgrp->keyword); + new_patgrp->keyword = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->keyword->not = patgrp->keyword->not; + } + + if(patgrp->charsets){ + p = pattern_to_string(patgrp->charsets); + new_patgrp->charsets = string_to_pattern(p); + fs_give((void **)&p); + new_patgrp->charsets->not = patgrp->charsets->not; + } + + if(patgrp->charsets_list) + new_patgrp->charsets_list = copy_strlist(patgrp->charsets_list); + + if(patgrp->arbhdr){ + ARBHDR_S *aa, *a, *new_a; + + aa = NULL; + for(a = patgrp->arbhdr; a; a = a->next){ + new_a = (ARBHDR_S *)fs_get(sizeof(*new_a)); + memset((void *)new_a, 0, sizeof(*new_a)); + + if(a->field) + new_a->field = cpystr(a->field); + + if(a->p){ + p = pattern_to_string(a->p); + new_a->p = string_to_pattern(p); + fs_give((void **)&p); + new_a->p->not = a->p->not; + } + + new_a->isemptyval = a->isemptyval; + + if(aa){ + aa->next = new_a; + aa = aa->next; + } + else{ + new_patgrp->arbhdr = new_a; + aa = new_patgrp->arbhdr; + } + } + } + + new_patgrp->fldr_type = patgrp->fldr_type; + + if(patgrp->folder){ + p = pattern_to_string(patgrp->folder); + new_patgrp->folder = string_to_pattern(p); + fs_give((void **)&p); + } + + new_patgrp->inabook = patgrp->inabook; + + if(patgrp->abooks){ + p = pattern_to_string(patgrp->abooks); + new_patgrp->abooks = string_to_pattern(p); + fs_give((void **)&p); + } + + new_patgrp->do_score = patgrp->do_score; + if(patgrp->score){ + INTVL_S *intvl, *iv, *new_iv; + + intvl = NULL; + for(iv = patgrp->score; iv; iv = iv->next){ + new_iv = (INTVL_S *) fs_get(sizeof(*new_iv)); + memset((void *) new_iv, 0, sizeof(*new_iv)); + + new_iv->imin = iv->imin; + new_iv->imax = iv->imax; + + if(intvl){ + intvl->next = new_iv; + intvl = intvl->next; + } + else{ + new_patgrp->score = new_iv; + intvl = new_patgrp->score; + } + } + } + + new_patgrp->do_age = patgrp->do_age; + if(patgrp->age){ + INTVL_S *intvl, *iv, *new_iv; + + intvl = NULL; + for(iv = patgrp->age; iv; iv = iv->next){ + new_iv = (INTVL_S *) fs_get(sizeof(*new_iv)); + memset((void *) new_iv, 0, sizeof(*new_iv)); + + new_iv->imin = iv->imin; + new_iv->imax = iv->imax; + + if(intvl){ + intvl->next = new_iv; + intvl = intvl->next; + } + else{ + new_patgrp->age = new_iv; + intvl = new_patgrp->age; + } + } + } + + new_patgrp->age_uses_sentdate = patgrp->age_uses_sentdate; + + new_patgrp->do_size = patgrp->do_size; + if(patgrp->size){ + INTVL_S *intvl, *iv, *new_iv; + + intvl = NULL; + for(iv = patgrp->size; iv; iv = iv->next){ + new_iv = (INTVL_S *) fs_get(sizeof(*new_iv)); + memset((void *) new_iv, 0, sizeof(*new_iv)); + + new_iv->imin = iv->imin; + new_iv->imax = iv->imax; + + if(intvl){ + intvl->next = new_iv; + intvl = intvl->next; + } + else{ + new_patgrp->size = new_iv; + intvl = new_patgrp->size; + } + } + } + + new_patgrp->stat_new = patgrp->stat_new; + new_patgrp->stat_rec = patgrp->stat_rec; + new_patgrp->stat_del = patgrp->stat_del; + new_patgrp->stat_imp = patgrp->stat_imp; + new_patgrp->stat_ans = patgrp->stat_ans; + + new_patgrp->stat_8bitsubj = patgrp->stat_8bitsubj; + new_patgrp->stat_bom = patgrp->stat_bom; + new_patgrp->stat_boy = patgrp->stat_boy; + + new_patgrp->do_cat = patgrp->do_cat; + if(patgrp->cat){ + INTVL_S *intvl, *iv, *new_iv; + + intvl = NULL; + for(iv = patgrp->cat; iv; iv = iv->next){ + new_iv = (INTVL_S *) fs_get(sizeof(*new_iv)); + memset((void *) new_iv, 0, sizeof(*new_iv)); + + new_iv->imin = iv->imin; + new_iv->imax = iv->imax; + + if(intvl){ + intvl->next = new_iv; + intvl = intvl->next; + } + else{ + new_patgrp->cat = new_iv; + intvl = new_patgrp->cat; + } + } + } + + if(patgrp->category_cmd) + new_patgrp->category_cmd = copy_list_array(patgrp->category_cmd); + } + + return(new_patgrp); +} + + +/* + * Returns an allocated copy of the action. + * + * Args action -- the source action + * + * Returns a copy of action. + */ +ACTION_S * +copy_action(ACTION_S *action) +{ + ACTION_S *newaction = NULL; + char *p; + + if(action){ + newaction = (ACTION_S *)fs_get(sizeof(*newaction)); + memset((void *)newaction, 0, sizeof(*newaction)); + + newaction->is_a_role = action->is_a_role; + newaction->is_a_incol = action->is_a_incol; + newaction->is_a_score = action->is_a_score; + newaction->is_a_filter = action->is_a_filter; + newaction->is_a_other = action->is_a_other; + newaction->is_a_srch = action->is_a_srch; + newaction->repl_type = action->repl_type; + newaction->forw_type = action->forw_type; + newaction->comp_type = action->comp_type; + newaction->scoreval = action->scoreval; + newaction->kill = action->kill; + newaction->state_setting_bits = action->state_setting_bits; + newaction->move_only_if_not_deleted = action->move_only_if_not_deleted; + newaction->non_terminating = action->non_terminating; + newaction->sort_is_set = action->sort_is_set; + newaction->sortorder = action->sortorder; + newaction->revsort = action->revsort; + newaction->startup_rule = action->startup_rule; + + if(action->from) + newaction->from = copyaddrlist(action->from); + if(action->replyto) + newaction->replyto = copyaddrlist(action->replyto); + if(action->cstm) + newaction->cstm = copy_list_array(action->cstm); + if(action->smtp) + newaction->smtp = copy_list_array(action->smtp); + if(action->nntp) + newaction->nntp = copy_list_array(action->nntp); + if(action->fcc) + newaction->fcc = cpystr(action->fcc); + if(action->litsig) + newaction->litsig = cpystr(action->litsig); + if(action->sig) + newaction->sig = cpystr(action->sig); + if(action->template) + newaction->template = cpystr(action->template); + if(action->nick) + newaction->nick = cpystr(action->nick); + if(action->inherit_nick) + newaction->inherit_nick = cpystr(action->inherit_nick); + if(action->incol) + newaction->incol = new_color_pair(action->incol->fg, + action->incol->bg); + if(action->scorevalhdrtok){ + newaction->scorevalhdrtok = new_hdrtok(action->scorevalhdrtok->hdrname); + if(action->scorevalhdrtok && action->scorevalhdrtok->fieldseps){ + if(newaction->scorevalhdrtok->fieldseps) + fs_give((void **) &newaction->scorevalhdrtok->fieldseps); + + newaction->scorevalhdrtok->fieldseps = cpystr(action->scorevalhdrtok->fieldseps); + } + } + + if(action->folder){ + p = pattern_to_string(action->folder); + newaction->folder = string_to_pattern(p); + fs_give((void **) &p); + } + + if(action->keyword_set){ + p = pattern_to_string(action->keyword_set); + newaction->keyword_set = string_to_pattern(p); + fs_give((void **) &p); + } + + if(action->keyword_clr){ + p = pattern_to_string(action->keyword_clr); + newaction->keyword_clr = string_to_pattern(p); + fs_give((void **) &p); + } + + if(action->index_format) + newaction->index_format = cpystr(action->index_format); + } + + return(newaction); +} + + +/* + * Given a role, return an allocated role. If this role inherits from + * another role, then do the correct inheriting so that the result is + * the role we want to use. The inheriting that is done is just the set + * of set- actions. This is for role stuff, no inheriting happens for scores + * or for colors. + * + * Args role -- The source role + * + * Returns a role. + */ +ACTION_S * +combine_inherited_role(ACTION_S *role) +{ + PAT_STATE pstate; + PAT_S *pat; + + /* + * Protect against loops in the role inheritance. + */ + if(role && role->is_a_role && nonempty_patterns(ROLE_DO_ROLES, &pstate)) + for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) + if(pat->action){ + if(pat->action == role) + pat->action->been_here_before = 1; + else + pat->action->been_here_before = 0; + } + + return(combine_inherited_role_guts(role)); +} + + +ACTION_S * +combine_inherited_role_guts(ACTION_S *role) +{ + ACTION_S *newrole = NULL, *inherit_role = NULL; + PAT_STATE pstate; + + if(role && role->is_a_role){ + newrole = (ACTION_S *)fs_get(sizeof(*newrole)); + memset((void *)newrole, 0, sizeof(*newrole)); + + newrole->repl_type = role->repl_type; + newrole->forw_type = role->forw_type; + newrole->comp_type = role->comp_type; + newrole->is_a_role = role->is_a_role; + + if(role->inherit_nick && role->inherit_nick[0] && + nonempty_patterns(ROLE_DO_ROLES, &pstate)){ + PAT_S *pat; + + /* find the inherit_nick pattern */ + for(pat = first_pattern(&pstate); + pat; + pat = next_pattern(&pstate)){ + if(pat->patgrp && + pat->patgrp->nick && + !strucmp(role->inherit_nick, pat->patgrp->nick)){ + /* found it, if it has a role, use it */ + if(!pat->action->been_here_before){ + pat->action->been_here_before = 1; + inherit_role = pat->action; + } + + break; + } + } + + /* + * inherit_role might inherit further from other roles. + * In any case, we copy it so that we'll consistently have + * an allocated copy. + */ + if(inherit_role){ + if(inherit_role->inherit_nick && inherit_role->inherit_nick[0]) + inherit_role = combine_inherited_role_guts(inherit_role); + else + inherit_role = copy_action(inherit_role); + } + } + + if(role->from) + newrole->from = copyaddrlist(role->from); + else if(inherit_role && inherit_role->from) + newrole->from = copyaddrlist(inherit_role->from); + + if(role->replyto) + newrole->replyto = copyaddrlist(role->replyto); + else if(inherit_role && inherit_role->replyto) + newrole->replyto = copyaddrlist(inherit_role->replyto); + + if(role->fcc) + newrole->fcc = cpystr(role->fcc); + else if(inherit_role && inherit_role->fcc) + newrole->fcc = cpystr(inherit_role->fcc); + + if(role->litsig) + newrole->litsig = cpystr(role->litsig); + else if(inherit_role && inherit_role->litsig) + newrole->litsig = cpystr(inherit_role->litsig); + + if(role->sig) + newrole->sig = cpystr(role->sig); + else if(inherit_role && inherit_role->sig) + newrole->sig = cpystr(inherit_role->sig); + + if(role->template) + newrole->template = cpystr(role->template); + else if(inherit_role && inherit_role->template) + newrole->template = cpystr(inherit_role->template); + + if(role->cstm) + newrole->cstm = copy_list_array(role->cstm); + else if(inherit_role && inherit_role->cstm) + newrole->cstm = copy_list_array(inherit_role->cstm); + + if(role->smtp) + newrole->smtp = copy_list_array(role->smtp); + else if(inherit_role && inherit_role->smtp) + newrole->smtp = copy_list_array(inherit_role->smtp); + + if(role->nntp) + newrole->nntp = copy_list_array(role->nntp); + else if(inherit_role && inherit_role->nntp) + newrole->nntp = copy_list_array(inherit_role->nntp); + + if(role->nick) + newrole->nick = cpystr(role->nick); + + if(inherit_role) + free_action(&inherit_role); + } + + return(newrole); +} + + +void +mail_expunge_prefilter(MAILSTREAM *stream, int flags) +{ + int sfdo_state = 0, /* Some Filter Depends On or Sets State */ + sfdo_scores = 0, /* Some Filter Depends On Scores */ + ssdo_state = 0; /* Some Score Depends On State */ + + if(!stream || !sp_flagged(stream, SP_LOCKED)) + return; + + /* + * An Expunge causes a re-examination of the filters to + * see if any state changes have caused new matches. + */ + + sfdo_scores = (scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS); + if(sfdo_scores) + ssdo_state = (scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP); + + if(!(sfdo_scores && ssdo_state)) + sfdo_state = some_filter_depends_on_active_state(); + + + if(sfdo_state || (sfdo_scores && ssdo_state)){ + if(sfdo_scores && ssdo_state) + clear_folder_scores(stream); + + reprocess_filter_patterns(stream, sp_msgmap(stream), + (flags & MI_CLOSING) | + MI_REFILTERING | MI_STATECHGONLY); + } +} + + +/*---------------------------------------------------------------------- + Dispatch messages matching FILTER patterns. + + Args: + stream -- mail stream serving messages + msgmap -- sequence to msgno mapping table + recent -- number of recent messages to check (but really only its + nonzeroness is used) + + When we're done, any filtered messages are filtered and the message + mapping table has any filtered messages removed. + ---*/ +void +process_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, long int recent) +{ + long i, n, raw; + imapuid_t uid; + int we_cancel = 0, any_msgs = 0, any_to_filter = 0; + int exbits, nt = 0, pending_actions = 0, for_debugging = 0; + int cleared_index_cache = 0; + long rflags = ROLE_DO_FILTER; + char *nick = NULL; + char busymsg[80]; + MSGNO_S *tmpmap = NULL; + MESSAGECACHE *mc; + PAT_S *pat, *nextpat = NULL; + SEARCHPGM *pgm = NULL; + SEARCHSET *srchset = NULL; + long flags = (SE_NOPREFETCH|SE_FREE); + PAT_STATE pstate; + + dprint((5, "process_filter_patterns(stream=%s, recent=%ld)\n", + !stream ? "<null>" : + sp_flagged(stream, SP_INBOX) ? "inbox" : + stream->original_mailbox ? stream->original_mailbox : + stream->mailbox ? stream->mailbox : + "?", + recent)); + + if(!msgmap || !stream) + return; + + if(!recent) + sp_set_flags(stream, sp_flags(stream) | SP_FILTERED); + + while(stream && stream->nmsgs && nonempty_patterns(rflags, &pstate)){ + + for_debugging++; + pending_actions = 0; + nextpat = NULL; + + uid = mail_uid(stream, stream->nmsgs); + + /* + * Some of the search stuff won't work on old servers so we + * get the data and search locally. Big performance hit. + */ + if(is_imap_stream(stream) && !modern_imap_stream(stream)) + flags |= SE_NOSERVER; + + /* + * ignore all previously filtered messages + * and, if requested, anything not a recent + * arrival... + * + * Here we're using spare6 (MN_STMP), meaning we'll only + * search the ones with spare6 marked, new messages coming + * in will not be considered. There used to be orig_nmsgs, + * which kept track of this, but if a message gets expunged, + * then a new message could be lower than orig_nmsgs. + */ + for(i = 1; i <= stream->nmsgs; i++) + if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_FILTERED){ + if((mc = mail_elt(stream, i)) != NULL) + mc->spare6 = 0; + } + else if(!recent || !(exbits & MSG_EX_TESTED)){ + if((mc = mail_elt(stream, i)) != NULL) + mc->spare6 = 1; + + any_to_filter++; + } + else if((mc = mail_elt(stream, i)) != NULL) + mc->spare6 = 0; + } + else{ + if((mc = mail_elt(stream, i)) != NULL) + mc->spare6 = !recent; + + any_to_filter += !recent; + } + + if(!any_to_filter){ + dprint((5, "No messages need filtering\n")); + } + + /* Then start searching */ + for(pat = first_pattern(&pstate); any_to_filter && pat; pat = nextpat){ + nextpat = next_pattern(&pstate); + dprint((5, + "Trying filter \"%s\"\n", + (pat->patgrp && pat->patgrp->nick) + ? pat->patgrp->nick : "?")); + if(pat->patgrp && !pat->patgrp->bogus + && pat->action && !pat->action->bogus + && !trivial_patgrp(pat->patgrp) + && match_pattern_folder(pat->patgrp, stream) + && !match_pattern_folder_specific(pat->action->folder, + stream, FOR_FILTER)){ + + /* + * We could just keep track of spare6 accurately when + * we change the msgno_exceptions flags, but... + */ + for(i = 1; i <= stream->nmsgs; i++){ + if((mc=mail_elt(stream, i)) && mc->spare6){ + if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_FILTERED) + mc->sequence = 0; + else if(!recent || !(exbits & MSG_EX_TESTED)) + mc->sequence = 1; + else + mc->sequence = 0; + } + else + mc->sequence = !recent; + } + else + mc->sequence = 0; + } + + if(!(srchset = build_searchset(stream))){ + dprint((5, "Empty searchset\n")); + continue; /* nothing to search, move on */ + } + +#ifdef DEBUG + {SEARCHSET *s; + dprint((5, "searchset=")); + for(s = srchset; s; s = s->next){ + if(s->first == s->last || s->last == 0L){ + dprint((5, " %ld", s->first)); + } + else{ + dprint((5, " %ld-%ld", s->first, s->last)); + } + } + dprint((5, "\n")); + } +#endif + nick = (pat && pat->patgrp && pat->patgrp->nick + && pat->patgrp->nick[0]) ? pat->patgrp->nick : NULL; + snprintf(busymsg, sizeof(busymsg), _("Processing filter \"%s\""), + nick ? nick : "?"); + + /* + * The strange last argument is so that the busy message + * won't come out until after a second if the user sets + * the feature to quell "filtering done". That's because + * they are presumably interested in the filtering actions + * themselves more than what is happening, so they'd + * rather see the action messages instead of the processing + * message. That's my theory anyway. + */ + if(F_OFF(F_QUELL_FILTER_MSGS, ps_global)) + any_msgs = we_cancel = busy_cue(busymsg, NULL, + F_ON(F_QUELL_FILTER_DONE_MSG, ps_global) + ? 1 : 0); + + if(pat->patgrp->stat_bom != PAT_STAT_EITHER){ + if(pat->patgrp->stat_bom == PAT_STAT_YES){ + if(!ps_global->beginning_of_month){ + dprint((5, + "Filter %s wants beginning of month and it isn't bom\n", + nick ? nick : "?")); + continue; + } + } + else if(pat->patgrp->stat_bom == PAT_STAT_NO){ + if(ps_global->beginning_of_month){ + dprint((5, + "Filter %s does not want beginning of month and it is bom\n", + nick ? nick : "?")); + continue; + } + } + } + + if(pat->patgrp->stat_boy != PAT_STAT_EITHER){ + if(pat->patgrp->stat_boy == PAT_STAT_YES){ + if(!ps_global->beginning_of_year){ + dprint((5, + "Filter %s wants beginning of year and it isn't boy\n", + nick ? nick : "?")); + continue; + } + } + else if(pat->patgrp->stat_boy == PAT_STAT_NO){ + if(ps_global->beginning_of_year){ + dprint((5, + "Filter %s does not want beginning of year and it is boy\n", + nick ? nick : "?")); + continue; + } + } + } + + pgm = match_pattern_srchpgm(pat->patgrp, stream, srchset); + + pine_mail_search_full(stream, "UTF-8", pgm, flags); + + /* check scores */ + if(scores_are_used(SCOREUSE_GET) & SCOREUSE_FILTERS && + pat->patgrp->do_score){ + SEARCHSET *s, *ss; + + /* + * Build a searchset so we can get all the scores we + * need and only the scores we need efficiently. + */ + + for(i = 1; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) != NULL) + mc->sequence = 0; + + for(s = srchset; s; s = s->next) + for(i = s->first; i <= s->last; i++) + if(i > 0L && stream && i <= stream->nmsgs + && (mc=mail_elt(stream, i)) && mc->searched && + get_msg_score(stream, i) == SCORE_UNDEF) + mc->sequence = 1; + + if((ss = build_searchset(stream)) != NULL){ + (void)calculate_some_scores(stream, ss, 0); + mail_free_searchset(&ss); + } + + /* + * Now check the patterns which have matched so far + * to see if their score is in the score interval. + */ + for(s = srchset; s; s = s->next) + for(i = s->first; i <= s->last; i++) + if(i > 0L && stream && i <= stream->nmsgs + && (mc=mail_elt(stream, i)) && mc->searched){ + long score; + + score = get_msg_score(stream, i); + + /* + * If the score is outside all of the intervals, + * turn off the searched bit. + * So that means we check each interval and if + * it is inside any interval we stop and leave + * the bit set. If it is outside we keep checking. + */ + if(score != SCORE_UNDEF){ + INTVL_S *iv; + + for(iv = pat->patgrp->score; iv; iv = iv->next) + if(score >= iv->imin && score <= iv->imax) + break; + + if(!iv) + mc->searched = NIL; + } + } + } + + /* check for 8bit subject match or not */ + if(pat->patgrp->stat_8bitsubj != PAT_STAT_EITHER) + find_8bitsubj_in_messages(stream, srchset, + pat->patgrp->stat_8bitsubj, 0); + + /* if there are still matches, check for charset matches */ + if(pat->patgrp->charsets) + find_charsets_in_messages(stream, srchset, pat->patgrp, 0); + + if(pat->patgrp->inabook != IAB_EITHER) + address_in_abook(stream, srchset, pat->patgrp->inabook, pat->patgrp->abooks); + + /* Still matches? Run the categorization command on each msg. */ + if(pith_opt_filter_pattern_cmd) + (*pith_opt_filter_pattern_cmd)(pat->patgrp->category_cmd, srchset, stream, pat->patgrp->cat_lim, pat->patgrp->cat); + + if(we_cancel){ + cancel_busy_cue(-1); + we_cancel = 0; + } + + nt = pat->action->non_terminating; + pending_actions = MAX(nt, pending_actions); + + /* + * Change some state bits. + * This used to only happen if kill was not set, but + * it can be useful to Delete a message even if killing. + * That way, it will show up in another pine that isn't + * running the same filter as Deleted, so the user won't + * bother looking at it. Hubert 2004-11-16 + */ + if(pat->action->state_setting_bits + || pat->action->keyword_set + || pat->action->keyword_clr){ + tmpmap = NULL; + mn_init(&tmpmap, stream->nmsgs); + + for(i = 1L, n = 0L; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) && mc->searched + && !(msgno_exceptions(stream, i, "0", &exbits, FALSE) + && (exbits & MSG_EX_FILTERED))){ + if(!n++){ + mn_set_cur(tmpmap, i); + } + else{ + mn_add_cur(tmpmap, i); + } + } + + if(n){ + long flagbits; + char **keywords_to_set = NULL, + **keywords_to_clr = NULL; + PATTERN_S *pp; + int cnt; + + flagbits = pat->action->state_setting_bits; + + if(pat->action->keyword_set){ + for(cnt = 0, pp = pat->action->keyword_set; + pp; pp = pp->next) + cnt++; + + keywords_to_set = (char **) fs_get((cnt+1) * + sizeof(*keywords_to_set)); + memset(keywords_to_set, 0, + (cnt+1) * sizeof(*keywords_to_set)); + for(cnt = 0, pp = pat->action->keyword_set; + pp; pp = pp->next){ + char *q; + + q = nick_to_keyword(pp->substring); + if(q && q[0]) + keywords_to_set[cnt++] = cpystr(q); + } + + flagbits |= F_KEYWORD; + } + + if(pat->action->keyword_clr){ + for(cnt = 0, pp = pat->action->keyword_clr; + pp; pp = pp->next) + cnt++; + + keywords_to_clr = (char **) fs_get((cnt+1) * + sizeof(*keywords_to_clr)); + memset(keywords_to_clr, 0, + (cnt+1) * sizeof(*keywords_to_clr)); + for(cnt = 0, pp = pat->action->keyword_clr; + pp; pp = pp->next){ + char *q; + + q = nick_to_keyword(pp->substring); + if(q && q[0]) + keywords_to_clr[cnt++] = cpystr(q); + } + + flagbits |= F_UNKEYWORD; + } + + set_some_flags(stream, tmpmap, flagbits, + keywords_to_set, keywords_to_clr, 1, + nick); + } + + mn_give(&tmpmap); + } + + /* + * The two halves of the if-else are almost the same and + * could probably be combined cleverly. The if clause + * is simply setting the MSG_EX_FILTERED bit, and leaving + * n set to zero. The msgno_exclude is not done in this case. + * The else clause excludes each message (because it is + * either filtered into nothing or moved to folder). The + * exclude messes with the msgmap and that changes max_msgno, + * so the loop control is a little tricky. + */ + if(!(pat->action->kill || pat->action->folder)){ + n = 0L; + for(i = 1L; i <= mn_get_total(msgmap); i++) + if((raw = mn_m2raw(msgmap, i)) > 0L + && stream && raw <= stream->nmsgs + && (mc = mail_elt(stream, raw)) && mc->searched){ + dprint((5, + "FILTER matching \"%s\": msg %ld%s\n", + nick ? nick : "unnamed", + raw, nt ? " (dont stop)" : "")); + if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)) + exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED); + else + exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED); + + /* + * If this matched an earlier non-terminating rule + * we've been keeping track of that so that we can + * turn it into a permanent match at the end. + * However, now we've matched another rule that is + * terminating so we don't have to worry about it + * anymore. Turn off the flag. + */ + if(!nt && exbits & MSG_EX_FILTONCE) + exbits ^= MSG_EX_FILTONCE; + + exbits &= ~MSG_EX_STATECHG; + + msgno_exceptions(stream, raw, "0", &exbits, TRUE); + } + } + else{ + for(i = 1L, n = 0L; i <= mn_get_total(msgmap); ) + if((raw = mn_m2raw(msgmap, i)) + && raw > 0L && stream && raw <= stream->nmsgs + && (mc = mail_elt(stream, raw)) && mc->searched){ + dprint((5, + "FILTER matching \"%s\": msg %ld %s%s\n", + nick ? nick : "unnamed", + raw, pat->action->folder ? "filed" : "killed", + nt ? " (dont stop)" : "")); + if(nt) + i++; + else{ + if(!cleared_index_cache + && stream == ps_global->mail_stream){ + cleared_index_cache = 1; + clear_index_cache(stream, 0); + } + + msgno_exclude(stream, msgmap, i, 1); + /* + * If this message is new, decrement + * new_mail_count. Previously, the caller would + * do this by counting MN_EXCLUDE before and after, + * but the results weren't accurate in the case + * where new messages arrived while filtering, + * or the filtered message could have gotten + * expunged. + */ + if(msgno_exceptions(stream, raw, "0", &exbits, + FALSE) + && (exbits & MSG_EX_RECENT)){ + long l, ll; + + l = sp_new_mail_count(stream); + ll = sp_recent_since_visited(stream); + dprint((5, "New message being filtered, decrement new_mail_count: %ld -> %ld\n", l, l-1L)); + if(l > 0L) + sp_set_new_mail_count(stream, l-1L); + if(ll > 0L) + sp_set_recent_since_visited(stream, ll-1L); + } + } + + if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)) + exbits |= (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED); + else + exbits = (nt ? MSG_EX_FILTONCE : MSG_EX_FILTERED); + + /* set pending exclusion for later */ + if(nt) + exbits |= MSG_EX_PEND_EXLD; + + /* + * If this matched an earlier non-terminating rule + * we've been keeping track of that so that we can + * turn it into a permanent match at the end. + * However, now we've matched another rule that is + * terminating so we don't have to worry about it + * anymore. Turn off the flags. + */ + if(!nt && exbits & MSG_EX_FILTONCE){ + exbits ^= MSG_EX_FILTONCE; + + /* we've already excluded it, too */ + if(exbits & MSG_EX_PEND_EXLD) + exbits ^= MSG_EX_PEND_EXLD; + } + + exbits &= ~MSG_EX_STATECHG; + + msgno_exceptions(stream, raw, "0", &exbits, TRUE); + n++; + } + else + i++; + } + + if(n && pat->action->folder){ + PATTERN_S *p; + int err = 0; + + tmpmap = NULL; + mn_init(&tmpmap, stream->nmsgs); + + /* + * For everything matching msg that hasn't + * already been saved somewhere, do it... + */ + for(i = 1L, n = 0L; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) && mc->searched + && !(msgno_exceptions(stream, i, "0", &exbits, FALSE) + && (exbits & MSG_EX_FILED))){ + if(!n++){ + mn_set_cur(tmpmap, i); + } + else{ + mn_add_cur(tmpmap, i); + } + } + + /* + * Remove already deleted messages from the tmp + * message map. + * There is a bug with this. If a filter moves a + * message to another folder _and_ sets the deleted + * status, then the setting of the deleted status + * will already have happened above in set_some_flags. + * So if the move_only_if_not_deleted bit is set that + * message will never be moved. A workaround for the + * user is to not set the move-only-if-not-deleted + * option. + */ + if(n && pat->action->move_only_if_not_deleted){ + char *seq; + MSGNO_S *tmpmap2 = NULL; + long nn = 0L; + MESSAGECACHE *mc; + + mn_init(&tmpmap2, stream->nmsgs); + + /* + * First, make sure elts are valid for all the + * interesting messages. + */ + if((seq = invalid_elt_sequence(stream, tmpmap)) != NULL){ + pine_mail_fetch_flags(stream, seq, NIL); + fs_give((void **) &seq); + } + + for(i = mn_first_cur(tmpmap); i > 0L; + i = mn_next_cur(tmpmap)){ + mc = ((raw = mn_m2raw(tmpmap, i)) > 0L + && stream && raw <= stream->nmsgs) + ? mail_elt(stream, raw) : NULL; + if(mc && !mc->deleted){ + if(!nn++){ + mn_set_cur(tmpmap2, i); + } + else{ + mn_add_cur(tmpmap2, i); + } + } + } + + mn_give(&tmpmap); + tmpmap = tmpmap2; + n = nn; + } + + if(n){ + for(p = pat->action->folder; p; p = p->next){ + int dval; + int flags_for_save; + char *processed_name; + + /* does this filter set delete bit? ... */ + convert_statebits_to_vals(pat->action->state_setting_bits, &dval, NULL, NULL, NULL); + /* ... if so, tell save not to fix it before copy */ + flags_for_save = SV_FOR_FILT | SV_INBOXWOCNTXT | + (nt ? 0 : SV_DELETE) | + ((dval != ACT_STAT_SET) ? SV_FIX_DELS : 0); + processed_name = detoken_src(p->substring, + FOR_FILT, NULL, + NULL, NULL, NULL); + if(move_filtered_msgs(stream, tmpmap, + (processed_name && *processed_name) + ? processed_name : p->substring, + flags_for_save, nick)){ + /* + * If we filtered into the current + * folder, chuck a ping down the + * stream so the user can notice it + * before the next new mail check... + */ + if(ps_global->mail_stream + && ps_global->mail_stream != stream + && match_pattern_folder_specific( + pat->action->folder, + ps_global->mail_stream, + FOR_FILTER)){ + (void) pine_mail_ping(ps_global->mail_stream); + } + } + else{ + err = 1; + break; + } + + if(processed_name) + fs_give((void **) &processed_name); + } + + if(!err) + for(n = mn_first_cur(tmpmap); + n > 0L; + n = mn_next_cur(tmpmap)){ + + if(msgno_exceptions(stream, mn_m2raw(tmpmap, n), + "0", &exbits, FALSE)) + exbits |= (nt ? MSG_EX_FILEONCE : MSG_EX_FILED); + else + exbits = (nt ? MSG_EX_FILEONCE : MSG_EX_FILED); + + exbits &= ~MSG_EX_STATECHG; + + msgno_exceptions(stream, mn_m2raw(tmpmap, n), + "0", &exbits, TRUE); + } + } + + mn_give(&tmpmap); + } + + mail_free_searchset(&srchset); + } + + /* + * If this is the last rule, + * we make sure we delete messages that we delayed deleting + * in the save. We delayed so that the deletion wouldn't have + * an effect on later rules. We convert any temporary + * FILED (FILEONCE) and FILTERED (FILTONCE) flags + * (which were set by an earlier non-terminating rule) + * to permanent. We also exclude some messages from the view. + */ + if(pending_actions && !nextpat){ + + pending_actions = 0; + tmpmap = NULL; + mn_init(&tmpmap, stream->nmsgs); + + for(i = 1L, n = 0L; i <= mn_get_total(msgmap); i++){ + + raw = mn_m2raw(msgmap, i); + if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_FILEONCE){ + if(!n++){ + mn_set_cur(tmpmap, raw); + } + else{ + mn_add_cur(tmpmap, raw); + } + } + } + } + + if(n) + set_some_flags(stream, tmpmap, F_DEL, NULL, NULL, 0, NULL); + + mn_give(&tmpmap); + + for(i = 1L; i <= mn_get_total(msgmap); i++){ + raw = mn_m2raw(msgmap, i); + if(msgno_exceptions(stream, raw, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_PEND_EXLD){ + if(!cleared_index_cache + && stream == ps_global->mail_stream){ + cleared_index_cache = 1; + clear_index_cache(stream, 0); + } + + msgno_exclude(stream, msgmap, i, 1); + if(msgno_exceptions(stream, raw, "0", + &exbits, FALSE) + && (exbits & MSG_EX_RECENT)){ + long l, ll; + + /* + * If this message is new, decrement + * new_mail_count. See the above + * call to msgno_exclude. + */ + l = sp_new_mail_count(stream); + ll = sp_recent_since_visited(stream); + dprint((5, "New message being filtered. Decrement new_mail_count: %ld -> %ld\n", l, l-1L)); + if(l > 0L) + sp_set_new_mail_count(stream, l - 1L); + if(ll > 0L) + sp_set_recent_since_visited(stream, ll - 1L); + } + + i--; /* to compensate for loop's i++ */ + } + + /* get rid of temporary flags */ + if(exbits & (MSG_EX_FILTONCE | MSG_EX_FILEONCE | + MSG_EX_PEND_EXLD)){ + if(exbits & MSG_EX_FILTONCE){ + /* convert to permament */ + exbits ^= MSG_EX_FILTONCE; + exbits |= MSG_EX_FILTERED; + } + + /* convert to permament */ + if(exbits & MSG_EX_FILEONCE){ + exbits ^= MSG_EX_FILEONCE; + exbits |= MSG_EX_FILED; + } + + if(exbits & MSG_EX_PEND_EXLD) + exbits ^= MSG_EX_PEND_EXLD; + + exbits &= ~MSG_EX_STATECHG; + + msgno_exceptions(stream, raw, "0", &exbits,TRUE); + } + } + } + } + } + + /* New mail arrival means start over */ + if(mail_uid(stream, stream->nmsgs) == uid) + break; + /* else, go again */ + + recent = 1; /* only check recent ones now */ + } + + if(!recent){ + /* clear status change flags */ + for(i = 1; i <= stream->nmsgs; i++){ + if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_STATECHG){ + exbits &= ~MSG_EX_STATECHG; + msgno_exceptions(stream, i, "0", &exbits, TRUE); + } + } + } + } + + /* clear any private "recent" flags and add TESTED flag */ + for(i = 1; i <= stream->nmsgs; i++){ + if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_RECENT + || !(exbits & MSG_EX_TESTED) + || (!recent && exbits & MSG_EX_STATECHG)){ + exbits &= ~MSG_EX_RECENT; + exbits |= MSG_EX_TESTED; + if(!recent) + exbits &= ~MSG_EX_STATECHG; + + msgno_exceptions(stream, i, "0", &exbits, TRUE); + } + } + else{ + exbits = MSG_EX_TESTED; + msgno_exceptions(stream, i, "0", &exbits, TRUE); + } + + /* clear any stmp flags just in case */ + if((mc = mail_elt(stream, i)) != NULL) + mc->spare6 = 0; + } + + msgmap->flagged_stmp = 0L; + + if(any_msgs && F_OFF(F_QUELL_FILTER_MSGS, ps_global) + && F_OFF(F_QUELL_FILTER_DONE_MSG, ps_global)){ + q_status_message(SM_ORDER, 0, 1, _("filtering done")); + display_message('x'); + } +} + + +/* + * Re-check the filters for matches because a change of message state may + * have changed the results. + */ +void +reprocess_filter_patterns(MAILSTREAM *stream, MSGNO_S *msgmap, int flags) +{ + if(stream){ + long i; + int exbits; + + if(msgno_include(stream, msgmap, flags) + && stream == ps_global->mail_stream + && !(flags & MI_CLOSING)){ + clear_index_cache(stream, 0); + refresh_sort(stream, msgmap, SRT_NON); + ps_global->mangled_header = 1; + } + + /* + * Passing 1 in the last argument causes it to only look at the + * messages we included above, which should be only the ones we + * need to look at. + */ + process_filter_patterns(stream, msgmap, + (flags & MI_STATECHGONLY) ? 1L : 0); + + /* clear status change flags */ + for(i = 1; i <= stream->nmsgs; i++){ + if(msgno_exceptions(stream, i, "0", &exbits, FALSE)){ + if(exbits & MSG_EX_STATECHG){ + exbits &= ~MSG_EX_STATECHG; + msgno_exceptions(stream, i, "0", &exbits, TRUE); + } + } + } + } +} + + +/* + * When killing or filtering we don't want to match by mistake. So if + * a pattern has nothing set except the Current Folder Type (which is always + * set to something) we'll consider it to be trivial and not a match. + * match_pattern uses this to determine if there is a match, where it is + * just triggered on the Current Folder Type. + */ +int +trivial_patgrp(PATGRP_S *patgrp) +{ + int ret = 1; + + if(patgrp){ + if(patgrp->subj || patgrp->cc || patgrp->from || patgrp->to || + patgrp->sender || patgrp->news || patgrp->recip || patgrp->partic || + patgrp->alltext || patgrp->bodytext) + ret = 0; + + if(ret && patgrp->do_age) + ret = 0; + + if(ret && patgrp->do_size) + ret = 0; + + if(ret && patgrp->do_score) + ret = 0; + + if(ret && patgrp->category_cmd && patgrp->category_cmd[0]) + ret = 0; + + if(ret && patgrp_depends_on_state(patgrp)) + ret = 0; + + if(ret && patgrp->stat_8bitsubj != PAT_STAT_EITHER) + ret = 0; + + if(ret && patgrp->charsets) + ret = 0; + + if(ret && patgrp->stat_bom != PAT_STAT_EITHER) + ret = 0; + + if(ret && patgrp->stat_boy != PAT_STAT_EITHER) + ret = 0; + + if(ret && patgrp->inabook != IAB_EITHER) + ret = 0; + + if(ret && patgrp->arbhdr){ + ARBHDR_S *a; + + for(a = patgrp->arbhdr; a && ret; a = a->next) + if(a->field && a->field[0] && a->p) + ret = 0; + } + } + + return(ret); +} + + +int +some_filter_depends_on_active_state(void) +{ + long rflags = ROLE_DO_FILTER; + PAT_S *pat; + PAT_STATE pstate; + int ret = 0; + + if(nonempty_patterns(rflags, &pstate)){ + + for(pat = first_pattern(&pstate); + pat && !ret; + pat = next_pattern(&pstate)) + if(patgrp_depends_on_active_state(pat->patgrp)) + ret++; + } + + return(ret); +} + + +/*---------------------------------------------------------------------- + Move all messages with sequence bit lit to dstfldr + + Args: stream -- stream to use + msgmap -- map of messages to be moved + dstfldr -- folder to receive moved messages + flags_for_save + + Returns: nonzero on success or on readonly stream + ----*/ +int +move_filtered_msgs(MAILSTREAM *stream, MSGNO_S *msgmap, char *dstfldr, + int flags_for_save, char *nick) +{ + long n; + int we_cancel = 0, width; + CONTEXT_S *save_context = NULL; + char buf[MAX_SCREEN_COLS+1], sbuf[MAX_SCREEN_COLS+1]; + char *save_ref = NULL; +#define FILTMSG_MAX 30 + + if(!stream) + return 0; + + if(READONLY_FOLDER(stream)){ + dprint((1, + "Can't delete messages in readonly folder \"%s\"\n", + STREAMNAME(stream))); + q_status_message1(SM_ORDER, 1, 3, + _("Can't delete messages in readonly folder \"%s\""), + STREAMNAME(stream)); + return 1; + } + + buf[0] = '\0'; + + width = MAX(10, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80); + snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"\"", + nick ? nick : "", nick ? ": " : "", + comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap))); + /* 2 is for brackets, 5 is for " DONE" in busy alarm */ + width -= (strlen(buf) + 2 + 5); + snprintf(buf, sizeof(buf), "%.30s%.2sMoving %.10s filtered message%.2s to \"%s\"", + nick ? nick : "", nick ? ": " : "", + comatose(mn_total_cur(msgmap)), plural(mn_total_cur(msgmap)), + short_str(dstfldr, sbuf, sizeof(sbuf), width, FrontDots)); + + dprint((5, "%s\n", buf)); + + if(F_OFF(F_QUELL_FILTER_MSGS, ps_global)) + we_cancel = busy_cue(buf, NULL, 0); + + if(!is_absolute_path(dstfldr) + && !(save_context = default_save_context(ps_global->context_list))) + save_context = ps_global->context_list; + + /* + * Because this save is happening independent of where the user is + * in the folder hierarchy and has nothing to do with that, we want + * to ignore the reference field built into the context. Zero it out + * temporarily here so it won't affect the results of context_apply + * in save. + * + * This might be a problem elsewhere, as well. The same thing as this + * is also done in match_pattern_folder_specific, which is also only + * called from within process_filter_patterns. But there could be + * others. We could have a separate function, something like + * copy_default_save_context(), that automatically zeroes out the + * reference field in the copy. However, some of the uses of + * default_save_context() require that a pointer into the actual + * context list is returned, so this would have to be done carefully. + * Besides, we don't know of any other problems so we'll just change + * these known cases for now. + */ + if(save_context && save_context->dir){ + save_ref = save_context->dir->ref; + save_context->dir->ref = NULL; + } + + n = save(ps_global, stream, save_context, dstfldr, msgmap, flags_for_save); + + if(save_ref) + save_context->dir->ref = save_ref; + + if(n != mn_total_cur(msgmap)){ + int exbits; + long x; + + buf[0] = '\0'; + + /* Clear "filtered" flags for failed messages */ + for(x = mn_first_cur(msgmap); x > 0L; x = mn_next_cur(msgmap)) + if(n-- <= 0 && msgno_exceptions(stream, mn_m2raw(msgmap, x), + "0", &exbits, FALSE)){ + exbits &= ~(MSG_EX_FILTONCE | MSG_EX_FILEONCE | + MSG_EX_FILTERED | MSG_EX_FILED); + msgno_exceptions(stream, mn_m2raw(msgmap, x), + "0", &exbits, TRUE); + } + + /* then re-incorporate them into folder they belong */ + (void) msgno_include(stream, sp_msgmap(stream), MI_NONE); + clear_index_cache(stream, 0); + refresh_sort(stream, sp_msgmap(stream), SRT_NON); + ps_global->mangled_header = 1; + } + else{ + snprintf(buf, sizeof(buf), _("Filtered all %s message to \"%s\""), + comatose(n), dstfldr); + dprint((5, "%s\n", buf)); + } + + if(we_cancel) + cancel_busy_cue(buf[0] ? 0 : -1); + + return(buf[0] != '\0'); +} + + +/*---------------------------------------------------------------------- + Move all messages with sequence bit lit to dstfldr + + Args: stream -- stream to use + msgmap -- which messages to set + flagbits -- which flags to set or clear + kw_on -- keywords to set + kw_off -- keywords to clear + verbose -- 1 => busy alarm after 1 second + 2 => forced busy alarm + ----*/ +void +set_some_flags(MAILSTREAM *stream, MSGNO_S *msgmap, long int flagbits, + char **kw_on, char **kw_off, int verbose, char *nick) +{ + long count = 0L, flipped_flags; + int we_cancel = 0; + char buf[150], *seq; + + if(!stream) + return; + + if(READONLY_FOLDER(stream)){ + dprint((1, "Can't set flags in readonly folder \"%s\"\n", + STREAMNAME(stream))); + q_status_message1(SM_ORDER, 1, 3, + _("Can't set flags in readonly folder \"%s\""), + STREAMNAME(stream)); + return; + } + + /* use this to determine if anything needs to be done */ + flipped_flags = ((flagbits & F_ANS) ? F_UNANS : 0) | + ((flagbits & F_UNANS) ? F_ANS : 0) | + ((flagbits & F_FLAG) ? F_UNFLAG : 0) | + ((flagbits & F_UNFLAG) ? F_FLAG : 0) | + ((flagbits & F_DEL) ? F_UNDEL : 0) | + ((flagbits & F_UNDEL) ? F_DEL : 0) | + ((flagbits & F_SEEN) ? F_UNSEEN : 0) | + ((flagbits & F_UNSEEN) ? F_SEEN : 0) | + ((flagbits & F_KEYWORD) ? F_UNKEYWORD : 0) | + ((flagbits & F_UNKEYWORD) ? F_KEYWORD : 0); + if((seq = currentf_sequence(stream, msgmap, flipped_flags, &count, 0, + kw_off, kw_on)) != NULL){ + char *sets = NULL, *clears = NULL; + char *ps, *pc, **t; + size_t clen, slen; + + /* allocate enough space for mail_flags arguments */ + for(slen=100, t = kw_on; t && *t; t++) + slen += (strlen(*t) + 1); + + sets = (char *) fs_get(slen * sizeof(*sets)); + + for(clen=100, t = kw_off; t && *t; t++) + clen += (strlen(*t) + 1); + + clears = (char *) fs_get(clen * sizeof(*clears)); + + sets[0] = clears[0] = '\0'; + ps = sets; + pc = clears; + + snprintf(buf, sizeof(buf), "%.30s%.2sSetting flags in %.10s message%.10s", + nick ? nick : "", nick ? ": " : "", + comatose(count), plural(count)); + + if(F_OFF(F_QUELL_FILTER_MSGS, ps_global)) + we_cancel = busy_cue(buf, NULL, verbose ? 0 : 1); + + /* + * What's going on here? If we want to set more than one flag + * we can do it with a single roundtrip by combining the arguments + * into a single call and separating them with spaces. + */ + if(flagbits & F_ANS) + sstrncpy(&ps, "\\ANSWERED", slen-(ps-sets)); + if(flagbits & F_FLAG){ + if(ps > sets) + sstrncpy(&ps, " ", slen-(ps-sets)); + + sstrncpy(&ps, "\\FLAGGED", slen-(ps-sets)); + } + if(flagbits & F_DEL){ + if(ps > sets) + sstrncpy(&ps, " ", slen-(ps-sets)); + + sstrncpy(&ps, "\\DELETED", slen-(ps-sets)); + } + if(flagbits & F_SEEN){ + if(ps > sets) + sstrncpy(&ps, " ", slen-(ps-sets)); + + sstrncpy(&ps, "\\SEEN", slen-(ps-sets)); + } + if(flagbits & F_KEYWORD){ + for(t = kw_on; t && *t; t++){ + int i; + + /* + * We may be able to tell that this will fail before + * we actually try it. + */ + if(stream->kwd_create || + (((i=user_flag_index(stream, *t)) >= 0) && i < NUSERFLAGS)){ + if(ps > sets) + sstrncpy(&ps, " ", slen-(ps-sets)); + + sstrncpy(&ps, *t, slen-(ps-sets)); + } + else{ + int some_defined = 0; + static int msg_delivered = 0; + + some_defined = some_user_flags_defined(stream); + + if(msg_delivered++ < 2){ + char b[200], c[200], *p; + int w; + + if(some_defined){ + snprintf(b, sizeof(b), "Can't set \"%.30s\". No more keywords in ", keyword_to_nick(*t)); + w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1); + p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots); + q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p); + } + else{ + snprintf(b, sizeof(b), "Can't set \"%.30s\". Can't add keywords in ", keyword_to_nick(*t)); + w = MIN((ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - strlen(b) - 1 - 2, sizeof(c)-1); + p = short_str(STREAMNAME(stream), c, sizeof(c), w, FrontDots); + q_status_message2(SM_ORDER, 3, 3, "%s%s!", b, p); + } + } + + if(some_defined){ + dprint((1, "Can't set keyword \"%s\". No more keywords allowed in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder")); + } + else{ + dprint((1, "Can't set keyword \"%s\". Can't add keywords in %s\n", *t, stream->mailbox ? stream->mailbox : "target folder")); + } + } + } + } + + /* need a separate call for the clears */ + if(flagbits & F_UNANS) + sstrncpy(&pc, "\\ANSWERED", clen-(pc-clears)); + if(flagbits & F_UNFLAG){ + if(pc > clears) + sstrncpy(&pc, " ", clen-(pc-clears)); + + sstrncpy(&pc, "\\FLAGGED", clen-(pc-clears)); + } + if(flagbits & F_UNDEL){ + if(pc > clears) + sstrncpy(&pc, " ", clen-(pc-clears)); + + sstrncpy(&pc, "\\DELETED", clen-(pc-clears)); + } + if(flagbits & F_UNSEEN){ + if(pc > clears) + sstrncpy(&pc, " ", clen-(pc-clears)); + + sstrncpy(&pc, "\\SEEN", clen-(pc-clears)); + } + if(flagbits & F_UNKEYWORD){ + for(t = kw_off; t && *t; t++){ + if(pc > clears) + sstrncpy(&pc, " ", clen-(pc-clears)); + + sstrncpy(&pc, *t, clen-(pc-clears)); + } + } + + + if(sets[0]) + mail_flag(stream, seq, sets, ST_SET); + + if(clears[0]) + mail_flag(stream, seq, clears, 0L); + + fs_give((void **) &sets); + fs_give((void **) &clears); + fs_give((void **) &seq); + + if(we_cancel) + cancel_busy_cue(buf[0] ? 0 : -1); + } +} + + +/* + * Delete messages which are marked FILTERED and excluded. + * Messages which are FILTERED but not excluded are those that have had + * their state set by a filter pattern, but are to remain in the same + * folder. + */ +void +delete_filtered_msgs(MAILSTREAM *stream) +{ + int exbits; + long i; + char *seq; + MESSAGECACHE *mc; + + for(i = 1L; i <= stream->nmsgs; i++) + if(msgno_exceptions(stream, i, "0", &exbits, FALSE) + && (exbits & MSG_EX_FILTERED) + && get_lflag(stream, NULL, i, MN_EXLD)){ + if((mc = mail_elt(stream, i)) != NULL) + mc->sequence = 1; + } + else if((mc = mail_elt(stream, i)) != NULL) + mc->sequence = 0; + + if((seq = build_sequence(stream, NULL, NULL)) != NULL){ + mail_flag(stream, seq, "\\DELETED", ST_SET | ST_SILENT); + fs_give((void **) &seq); + } +} |