/*@ S-nail - a mail user agent derived from Berkeley Mail. *@ `headerpick', `retain' and `ignore', and `un..' variants. * * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso . * SPDX-License-Identifier: ISC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #undef su_FILE #define su_FILE ignore #define mx_SOURCE #ifndef mx_HAVE_AMALGAMATION # include "mx/nail.h" #endif #include #include #include #include "mx/termios.h" /* TODO fake */ #include "su/code-in.h" struct a_ignore_type{ u32 it_count; /* Entries in .it_ht (and .it_re) */ boole it_all; /* _All_ fields ought to be _type_ (ignore/retain) */ u8 it__dummy[3]; struct a_ignore_field{ struct a_ignore_field *if_next; char if_field[VFIELD_SIZE(0)]; /* Header field */ } *it_ht[3]; /* TODO make hashmap dynamic */ #ifdef mx_HAVE_REGEX struct a_ignore_re{ struct a_ignore_re *ir_next; regex_t ir_regex; char ir_input[VFIELD_SIZE(0)]; /* Regex input text (for showing it) */ } *it_re, *it_re_tail; #endif }; struct n_ignore{ struct a_ignore_type i_retain; struct a_ignore_type i_ignore; boole i_auto; /* In auto-reclaimed, not heap memory */ boole i_bltin; /* Is a built-in n_IGNORE* type */ u8 i_ibm_idx; /* If .i_bltin: a_ignore_bltin_map[] idx */ u8 i__dummy[5]; }; struct a_ignore_bltin_map{ struct n_ignore *ibm_ip; char const ibm_name[8]; }; static struct a_ignore_bltin_map const a_ignore_bltin_map[] = { {n_IGNORE_TYPE, "type"}, {n_IGNORE_SAVE, "save"}, {n_IGNORE_FWD, "forward\0"}, {n_IGNORE_TOP, "top"}, {n_IGNORE_TYPE, "print\0"}, {n_IGNORE_FWD, "fwd"} }; #ifdef mx_HAVE_DEVEL /* Avoid gcc warn cascade "n_ignore is defined locally" */ CTAV(-n__IGNORE_TYPE - n__IGNORE_ADJUST == 0); CTAV(-n__IGNORE_SAVE - n__IGNORE_ADJUST == 1); CTAV(-n__IGNORE_FWD - n__IGNORE_ADJUST == 2); CTAV(-n__IGNORE_TOP - n__IGNORE_ADJUST == 3); CTAV(n__IGNORE_MAX == 3); #endif static struct n_ignore *a_ignore_bltin[n__IGNORE_MAX + 1]; /* Almost everyone uses `ignore'/`retain', put _TYPE in BSS */ static struct n_ignore a_ignore_type; /* Return real self, which is xself unless that is a built-in special, * in which case NIL is returned if nonexistent and docreate is false. * The other statics assume self has been resolved (unless noted) */ static struct n_ignore *a_ignore_resolve_self(struct n_ignore *xself, boole docreate); /* Lookup whether a mapping is contained: TRU1=retained, TRUM1=ignored. * If retain is _not_ TRUM1 then only the retained/ignored slot is inspected, * and regular expressions are not executed but instead their .ir_input is * text-compared against len bytes of dat. * Note it doesn't handle the .it_all "all fields" condition */ static boole a_ignore_lookup(struct n_ignore const *self, boole retain, char const *dat, uz len); /* Delete all retain( else ignor)ed members */ static void a_ignore_del_allof(struct n_ignore *ip, boole retain); /* Try to map a string to one of the built-in types */ static struct a_ignore_bltin_map const *a_ignore_resolve_bltin(char const *cp); /* Logic behind `headerpick T T' (a.k.a. `retain'+) */ static boole a_ignore_addcmd_mux(struct n_ignore *ip, char const **list, boole retain); static void a_ignore__show(struct n_ignore const *ip, boole retain); /* Logic behind `unheaderpick T T' (a.k.a. `unretain'+) */ static boole a_ignore_delcmd_mux(struct n_ignore *ip, char const **list, boole retain); static boole a_ignore__delone(struct n_ignore *ip, boole retain, char const *field); static struct n_ignore * a_ignore_resolve_self(struct n_ignore *xself, boole docreate){ up suip; struct n_ignore *self; NYD2_IN; self = xself; suip = -R(up,self) - n__IGNORE_ADJUST; if(suip <= n__IGNORE_MAX){ if((self = a_ignore_bltin[suip]) == NIL && docreate){ if(xself == n_IGNORE_TYPE){ self = &a_ignore_type; /* LIB: su_mem_set(self, 0, sizeof *self);*/ }else self = n_ignore_new(FAL0); self->i_bltin = TRU1; self->i_ibm_idx = S(u8,suip); a_ignore_bltin[suip] = self; } } NYD2_OU; return self; } static boole a_ignore_lookup(struct n_ignore const *self, boole retain, char const *dat, uz len){ boole rv; #ifdef mx_HAVE_REGEX struct a_ignore_re *irp; #endif struct a_ignore_field *ifp; u32 hi; NYD2_IN; if(len == UZ_MAX) len = su_cs_len(dat); hi = su_cs_hash_case_cbuf(dat, len) % NELEM(self->i_retain.it_ht); /* Again: does not handle .it_all conditions! */ /* (Inner functions would be nice, again) */ if(retain && self->i_retain.it_count > 0){ rv = TRU1; for(ifp = self->i_retain.it_ht[hi]; ifp != NIL; ifp = ifp->if_next) if(!su_cs_cmp_case_n(ifp->if_field, dat, len) && ifp->if_field[len] == '\0') goto jleave; #ifdef mx_HAVE_REGEX if(dat[len - 1] != '\0') dat = savestrbuf(dat, len); for(irp = self->i_retain.it_re; irp != NIL; irp = irp->ir_next) if((retain == TRUM1 ? (regexec(&irp->ir_regex, dat, 0,NIL, 0) != REG_NOMATCH) : (!su_cs_cmp_n(irp->ir_input, dat, len) && irp->ir_input[len] == '\0'))) goto jleave; #endif rv = (retain == TRUM1) ? TRUM1 : FAL0; }else if((retain == TRUM1 || !retain) && self->i_ignore.it_count > 0){ rv = TRUM1; for(ifp = self->i_ignore.it_ht[hi]; ifp != NIL; ifp = ifp->if_next) if(!su_cs_cmp_case_n(ifp->if_field, dat, len) && ifp->if_field[len] == '\0') goto jleave; #ifdef mx_HAVE_REGEX if(dat[len - 1] != '\0') dat = savestrbuf(dat, len); for(irp = self->i_ignore.it_re; irp != NIL; irp = irp->ir_next) if((retain == TRUM1 ? (regexec(&irp->ir_regex, dat, 0,NIL, 0) != REG_NOMATCH) : (!su_cs_cmp_n(irp->ir_input, dat, len) && irp->ir_input[len] == '\0'))) goto jleave; #endif rv = (retain == TRUM1) ? TRU1 : FAL0; }else rv = FAL0; jleave: NYD2_OU; return rv; } static void a_ignore_del_allof(struct n_ignore *ip, boole retain){ #ifdef mx_HAVE_REGEX struct a_ignore_re *irp; #endif struct a_ignore_field *ifp; struct a_ignore_type *itp; NYD2_IN; itp = retain ? &ip->i_retain : &ip->i_ignore; if(!ip->i_auto){ uz i; for(i = 0; i < NELEM(itp->it_ht); ++i) for(ifp = itp->it_ht[i]; ifp != NIL;){ struct a_ignore_field *x; x = ifp; ifp = ifp->if_next; su_FREE(x); } } #ifdef mx_HAVE_REGEX for(irp = itp->it_re; irp != NIL;){ struct a_ignore_re *x; x = irp; irp = irp->ir_next; regfree(&x->ir_regex); if(!ip->i_auto) su_FREE(x); } #endif su_mem_set(itp, 0, sizeof *itp); NYD2_OU; } static struct a_ignore_bltin_map const * a_ignore_resolve_bltin(char const *cp){ struct a_ignore_bltin_map const *ibmp; NYD2_IN; for(ibmp = &a_ignore_bltin_map[0];;) if(!su_cs_cmp_case(cp, ibmp->ibm_name)) break; else if(++ibmp == &a_ignore_bltin_map[NELEM(a_ignore_bltin_map)]){ ibmp = NIL; break; } NYD2_OU; return ibmp; } static boole a_ignore_addcmd_mux(struct n_ignore *ip, char const **list, boole retain){ char const **ap; boole rv; NYD2_IN; ip = a_ignore_resolve_self(ip, rv = (*list != NIL)); if(!rv){ if(ip != NIL && ip->i_bltin) a_ignore__show(ip, retain); rv = TRU1; }else{ for(ap = list; *ap != 0; ++ap) switch(n_ignore_insert_cp(ip, retain, *ap)){ case FAL0: n_err(_("Invalid field name cannot be %s: %s\n"), (retain ? _("retained") : _("ignored")), *ap); rv = FAL0; break; case TRUM1: if(n_poption & n_PO_D_V) n_err(_("Field already %s: %s\n"), (retain ? _("retained") : _("ignored")), *ap); /* FALLTHRU */ case TRU1: break; } } NYD2_OU; return rv; } static void a_ignore__show(struct n_ignore const *ip, boole retain){ #ifdef mx_HAVE_REGEX struct a_ignore_re *irp; #endif struct a_ignore_field *ifp; uz i, sw; char const **ap, **ring; struct a_ignore_type const *itp; NYD2_IN; itp = retain ? &ip->i_retain : &ip->i_ignore; do{ char const *pre, *attr; if(itp->it_all) pre = n_empty, attr = n_star; else if(itp->it_count == 0) pre = n_ns, attr = _("currently covers no fields"); else break; fprintf(n_stdout, _("%sheaderpick %s %s %s\n"), pre, a_ignore_bltin_map[ip->i_ibm_idx].ibm_name, (retain ? "retain" : "ignore"), attr); goto jleave; }while(0); ring = n_autorec_alloc((itp->it_count +1) * sizeof *ring); for(ap = ring, i = 0; i < NELEM(itp->it_ht); ++i) for(ifp = itp->it_ht[i]; ifp != NIL; ifp = ifp->if_next) *ap++ = ifp->if_field; *ap = NIL; su_sort_shell_vpp(su_S(void const**,ring), P2UZ(ap - ring), su_cs_toolbox_case.tb_compare); i = fprintf(n_stdout, "headerpick %s %s", a_ignore_bltin_map[ip->i_ibm_idx].ibm_name, (retain ? "retain" : "ignore")); sw = mx_termios_dimen.tiosd_width; for(ap = ring; *ap != NIL; ++ap){ /* These fields are all ASCII, no visual width needed */ uz len; char const *cp; cp = n_shexp_quote_cp(*ap, FAL0); len = su_cs_len(cp) + 1; if(UCMP(z, len, >=, sw - i)){ fputs(" \\\n ", n_stdout); i = 1; } i += len; putc(' ', n_stdout); fputs(cp, n_stdout); } /* Regular expression in FIFO order */ #ifdef mx_HAVE_REGEX for(irp = itp->it_re; irp != NIL; irp = irp->ir_next){ uz len; char const *cp; cp = n_shexp_quote_cp(irp->ir_input, FAL0); len = su_cs_len(cp) + 1; if(UCMP(z, len, >=, sw - i)){ fputs(" \\\n ", n_stdout); i = 1; } i += len; putc(' ', n_stdout); fputs(cp, n_stdout); } #endif putc('\n', n_stdout); jleave: fflush(n_stdout); NYD2_OU; } static boole a_ignore_delcmd_mux(struct n_ignore *ip, char const **list, boole retain){ char const *cp; struct a_ignore_type *itp; boole rv; NYD2_IN; ip = a_ignore_resolve_self(ip, rv = (*list != NIL)); itp = retain ? &ip->i_retain : &ip->i_ignore; if(itp->it_count == 0 && !itp->it_all) n_err(_("No fields currently being %s\n"), (retain ? _("retained") : _("ignored"))); else{ while((cp = *list++) != NIL) if(cp[0] == '*' && cp[1] == '\0') a_ignore_del_allof(ip, retain); else if(!a_ignore__delone(ip, retain, cp)){ n_err(_("Field not %s: %s\n"), (retain ? _("retained") : _("ignored")), cp); rv = FAL0; } } NYD2_OU; return rv; } static boole a_ignore__delone(struct n_ignore *ip, boole retain, char const *field){ struct a_ignore_type *itp; NYD_IN; itp = retain ? &ip->i_retain : &ip->i_ignore; #ifdef mx_HAVE_REGEX if(n_is_maybe_regex(field)){ struct a_ignore_re **lirp, *irp; for(irp = *(lirp = &itp->it_re); irp != NIL; lirp = &irp->ir_next, irp = irp->ir_next) if(!su_cs_cmp(field, irp->ir_input)){ *lirp = irp->ir_next; if(irp == itp->it_re_tail) itp->it_re_tail = irp->ir_next; regfree(&irp->ir_regex); if(!ip->i_auto) su_FREE(irp); --itp->it_count; goto jleave; } }else #endif /* mx_HAVE_REGEX */ { struct a_ignore_field **ifpp, *ifp; u32 hi; hi = su_cs_hash_case_cbuf(field, UZ_MAX) % NELEM(itp->it_ht); for(ifp = *(ifpp = &itp->it_ht[hi]); ifp != NIL; ifpp = &ifp->if_next, ifp = ifp->if_next) if(!su_cs_cmp_case(ifp->if_field, field)){ *ifpp = ifp->if_next; if(!ip->i_auto) su_FREE(ifp); --itp->it_count; goto jleave; } } ip = NIL; jleave: NYD_OU; return (ip != NIL); } FL int c_headerpick(void *vp){ boole retain; struct a_ignore_bltin_map const *ibmp; char const **argv; int rv; NYD_IN; rv = 1; argv = vp; /* Without arguments, show all settings of all contexts */ if(*argv == NIL){ rv = 0; for(ibmp = &a_ignore_bltin_map[0]; ibmp <= &a_ignore_bltin_map[n__IGNORE_MAX]; ++ibmp){ rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, TRU1); rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, FAL0); } goto jleave; } if((ibmp = a_ignore_resolve_bltin(*argv)) == NIL){ n_err(_("headerpick: invalid context: %s\n"), *argv); goto jleave; } ++argv; /* With only , show all settings of it */ if(*argv == NIL){ rv = 0; rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, TRU1); rv |= !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, FAL0); goto jleave; } if(su_cs_starts_with_case("retain", *argv)) retain = TRU1; else if(su_cs_starts_with_case("ignore", *argv)) retain = FAL0; else{ n_err(_("headerpick: invalid type (retain, ignore): %s\n"), *argv); goto jleave; } ++argv; /* With only and , show its settings */ if(*argv == NIL){ rv = !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, retain); goto jleave; } rv = !a_ignore_addcmd_mux(ibmp->ibm_ip, argv, retain); jleave: NYD_OU; return rv; } FL int c_unheaderpick(void *vp){ boole retain; struct a_ignore_bltin_map const *ibmp; char const **argv; int rv; NYD_IN; rv = 1; argv = vp; if((ibmp = a_ignore_resolve_bltin(*argv)) == NIL){ n_err(_("unheaderpick: invalid context: %s\n"), *argv); goto jleave; } ++argv; if(su_cs_starts_with_case("retain", *argv)) retain = TRU1; else if(su_cs_starts_with_case("ignore", *argv)) retain = FAL0; else{ n_err(_("unheaderpick: invalid type (retain, ignore): %s\n"), *argv); goto jleave; } ++argv; rv = !a_ignore_delcmd_mux(ibmp->ibm_ip, argv, retain); jleave: NYD_OU; return rv; } FL int c_retain(void *vp){ int rv; NYD_IN; rv = !a_ignore_addcmd_mux(n_IGNORE_TYPE, vp, TRU1); NYD_OU; return rv; } FL int c_ignore(void *vp){ int rv; NYD_IN; rv = !a_ignore_addcmd_mux(n_IGNORE_TYPE, vp, FAL0); NYD_OU; return rv; } FL int c_unretain(void *vp){ int rv; NYD_IN; rv = !a_ignore_delcmd_mux(n_IGNORE_TYPE, vp, TRU1); NYD_OU; return rv; } FL int c_unignore(void *vp){ int rv; NYD_IN; rv = !a_ignore_delcmd_mux(n_IGNORE_TYPE, vp, FAL0); NYD_OU; return rv; } FL int c_saveretain(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_addcmd_mux(n_IGNORE_SAVE, v, TRU1); NYD_OU; return rv; } FL int c_saveignore(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_addcmd_mux(n_IGNORE_SAVE, v, FAL0); NYD_OU; return rv; } FL int c_unsaveretain(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_delcmd_mux(n_IGNORE_SAVE, v, TRU1); NYD_OU; return rv; } FL int c_unsaveignore(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_delcmd_mux(n_IGNORE_SAVE, v, FAL0); NYD_OU; return rv; } FL int c_fwdretain(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_addcmd_mux(n_IGNORE_FWD, v, TRU1); NYD_OU; return rv; } FL int c_fwdignore(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_addcmd_mux(n_IGNORE_FWD, v, FAL0); NYD_OU; return rv; } FL int c_unfwdretain(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_delcmd_mux(n_IGNORE_FWD, v, TRU1); NYD_OU; return rv; } FL int c_unfwdignore(void *v){ /* TODO v15 drop */ int rv; NYD_IN; rv = !a_ignore_delcmd_mux(n_IGNORE_FWD, v, FAL0); NYD_OU; return rv; } FL struct n_ignore * n_ignore_new(boole isauto){ struct n_ignore *self; NYD_IN; self = isauto ? n_autorec_calloc(1, sizeof *self) : su_CALLOC_N(1, sizeof *self); self->i_auto = isauto; NYD_OU; return self; } FL void n_ignore_del(struct n_ignore *self){ NYD_IN; a_ignore_del_allof(self, TRU1); a_ignore_del_allof(self, FAL0); if(!self->i_auto) su_FREE(self); NYD_OU; } FL boole n_ignore_is_any(struct n_ignore const *self){ boole rv; NYD_IN; self = a_ignore_resolve_self(n_UNCONST(self), FAL0); rv = (self != NIL && (self->i_retain.it_count != 0 || self->i_retain.it_all || self->i_ignore.it_count != 0 || self->i_ignore.it_all)); NYD_OU; return rv; } FL boole n_ignore_insert(struct n_ignore *self, boole retain, char const *dat, uz len){ #ifdef mx_HAVE_REGEX struct a_ignore_re *irp; boole isre; #endif struct a_ignore_field *ifp; struct a_ignore_type *itp; boole rv; NYD_IN; retain = !!retain; /* Make true bool, TRUM1 has special _lookup meaning */ rv = FAL0; self = a_ignore_resolve_self(self, TRU1); if(len == UZ_MAX) len = su_cs_len(dat); /* Request to ignore or retain _anything_? That is special-treated */ if(len == 1 && dat[0] == '*'){ itp = retain ? &self->i_retain : &self->i_ignore; if(itp->it_all) rv = TRUM1; else{ itp->it_all = TRU1; a_ignore_del_allof(self, retain); rv = TRU1; } goto jleave; } /* Check for regular expression or valid fieldname */ #ifdef mx_HAVE_REGEX if(!(isre = n_is_maybe_regex_buf(dat, len))) #endif { char c; uz i; for(i = 0; i < len; ++i){ c = dat[i]; if(!fieldnamechar(c)) goto jleave; } } rv = TRUM1; if(a_ignore_lookup(self, retain, dat, len) == (retain ? TRU1 : TRUM1)) goto jleave; itp = retain ? &self->i_retain : &self->i_ignore; if(itp->it_count == U32_MAX){ n_err(_("Header selection size limit reached, cannot insert: %.*s\n"), S(int,MIN(len, S32_MAX)), dat); rv = FAL0; goto jleave; } rv = TRU1; #ifdef mx_HAVE_REGEX if(isre){ struct a_ignore_re *x; int s; uz i; i = VSTRUCT_SIZEOF(struct a_ignore_re, ir_input) + ++len; irp = self->i_auto ? n_autorec_alloc(i) : su_ALLOC(i); su_mem_copy(irp->ir_input, dat, --len); irp->ir_input[len] = '\0'; if((s = regcomp(&irp->ir_regex, irp->ir_input, REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){ n_err(_("Invalid regular expression: %s: %s\n"), n_shexp_quote_cp(irp->ir_input, FAL0), n_regex_err_to_doc(NIL, s)); if(!self->i_auto) su_FREE(irp); rv = FAL0; goto jleave; } irp->ir_next = NIL; if((x = itp->it_re_tail) != NIL) x->ir_next = irp; else itp->it_re = irp; itp->it_re_tail = irp; }else #endif /* mx_HAVE_REGEX */ { u32 hi; uz i; i = VSTRUCT_SIZEOF(struct a_ignore_field, if_field) + len + 1; ifp = self->i_auto ? n_autorec_alloc(i) : su_ALLOC(i); su_mem_copy(ifp->if_field, dat, len); ifp->if_field[len] = '\0'; hi = su_cs_hash_case_cbuf(dat, len) % NELEM(itp->it_ht); ifp->if_next = itp->it_ht[hi]; itp->it_ht[hi] = ifp; } ++itp->it_count; jleave: NYD_OU; return rv; } FL boole n_ignore_lookup(struct n_ignore const *self, char const *dat, uz len){ boole rv; NYD_IN; if(self == n_IGNORE_ALL) rv = TRUM1; else if(len == 0 || (self = a_ignore_resolve_self(n_UNCONST(self), FAL0)) == NIL) rv = FAL0; else if(self->i_retain.it_all) rv = TRU1; else if(self->i_retain.it_count == 0 && self->i_ignore.it_all) rv = TRUM1; else rv = a_ignore_lookup(self, TRUM1, dat, len); NYD_OU; return rv; } #include "su/code-ou.h" /* s-it-mode */