1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `headerpick', `retain' and `ignore'.
3 *@ XXX Should these be in nam-a-grp.c?!
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #ifndef HAVE_AMALGAMATION
43 ui32_t it_count
; /* Entries in .it_ht (and .it_re) */
44 bool_t it_all
; /* _All_ fields ought to be _type_ (ignore/retain) */
46 struct a_ignore_field
{
47 struct a_ignore_field
*if_next
;
48 char if_field
[n_VFIELD_SIZE(0)]; /* Header field */
49 } *it_ht
[3]; /* TODO make hashmap dynamic */
52 struct a_ignore_re
*ir_next
;
54 char ir_input
[n_VFIELD_SIZE(0)]; /* Regex input text (for showing it) */
55 } *it_re
, *it_re_tail
;
60 struct a_ignore_type i_retain
;
61 struct a_ignore_type i_ignore
;
62 bool_t i_auto
; /* In auto-reclaimed, not heap memory */
63 bool_t i_bltin
; /* Is a builtin n_IGNORE* type */
64 ui8_t i_ibm_idx
; /* If .i_bltin: a_ignore_bltin_map[] idx */
68 struct a_ignore_bltin_map
{
69 struct n_ignore
*ibm_ip
;
70 char const ibm_name
[8];
73 static struct a_ignore_bltin_map
const a_ignore_bltin_map
[] = {
74 {n_IGNORE_TYPE
, "type\0"},
75 {n_IGNORE_SAVE
, "save\0"},
76 {n_IGNORE_FWD
, "forward\0"},
77 {n_IGNORE_TOP
, "top\0"},
79 {n_IGNORE_TYPE
, "print\0"},
80 {n_IGNORE_FWD
, "fwd\0"}
82 #ifdef HAVE_DEVEL /* Avoid gcc warn cascade since n_ignore is defined locally */
83 n_CTAV(-n__IGNORE_TYPE
- n__IGNORE_ADJUST
== 0);
84 n_CTAV(-n__IGNORE_SAVE
- n__IGNORE_ADJUST
== 1);
85 n_CTAV(-n__IGNORE_FWD
- n__IGNORE_ADJUST
== 2);
86 n_CTAV(-n__IGNORE_TOP
- n__IGNORE_ADJUST
== 3);
87 n_CTAV(n__IGNORE_MAX
== 3);
90 static struct n_ignore
*a_ignore_bltin
[n__IGNORE_MAX
+ 1];
91 /* Almost everyone uses `ignore'/`retain', put _TYPE in BSS */
92 static struct n_ignore a_ignore_type
;
94 /* Return real self, which is xself unless that is one of the builtin specials,
95 * in which case NULL is returned if nonexistent and docreate is false.
96 * The other statics assume self has been resolved (unless noted) */
97 static struct n_ignore
*a_ignore_resolve_self(struct n_ignore
*xself
,
100 /* Lookup whether a mapping is contained: TRU1=retained, TRUM1=ignored.
101 * If retain is _not_ TRUM1 then only the retained/ignored slot is inspected,
102 * and regular expressions are not executed but instead their .ir_input is
103 * text-compared against len bytes of dat.
104 * Note it doesn't handle the .it_all "all fields" condition */
105 static bool_t
a_ignore_lookup(struct n_ignore
const *self
, bool_t retain
,
106 char const *dat
, size_t len
);
108 /* Delete all retain( else ignor)ed members */
109 static void a_ignore_del_allof(struct n_ignore
*ip
, bool_t retain
);
111 /* Try to map a string to one of the builtin types */
112 static struct a_ignore_bltin_map
const *a_ignore_resolve_bltin(char const *cp
);
114 /* Logic behind `headerpick T T add' (a.k.a. `retain'+) */
115 static bool_t
a_ignore_addcmd_mux(struct n_ignore
*ip
, char const **list
,
118 static void a_ignore__show(struct n_ignore
const *ip
, bool_t retain
);
119 static int a_ignore__cmp(void const *l
, void const *r
);
121 /* Logic behind `headerpick T T remove' (a.k.a. `unretain'+) */
122 static bool_t
a_ignore_delcmd_mux(struct n_ignore
*ip
, char const **list
,
125 static bool_t
a_ignore__delone(struct n_ignore
*ip
, bool_t retain
,
128 static struct n_ignore
*
129 a_ignore_resolve_self(struct n_ignore
*xself
, bool_t docreate
){
131 struct n_ignore
*self
;
135 suip
= -(uintptr_t)self
- n__IGNORE_ADJUST
;
137 if(suip
<= n__IGNORE_MAX
){
138 if((self
= a_ignore_bltin
[suip
]) == NULL
&& docreate
){
139 if(xself
== n_IGNORE_TYPE
){
140 self
= &a_ignore_type
;
141 /* LIB: memset(self, 0, sizeof *self);*/
143 self
= n_ignore_new(FAL0
);
144 self
->i_bltin
= TRU1
;
145 self
->i_ibm_idx
= (ui8_t
)suip
;
146 a_ignore_bltin
[suip
] = self
;
154 a_ignore_lookup(struct n_ignore
const *self
, bool_t retain
,
155 char const *dat
, size_t len
){
158 struct a_ignore_re
*irp
;
160 struct a_ignore_field
*ifp
;
166 hi
= torek_ihashn(dat
, len
) % n_NELEM(self
->i_retain
.it_ht
);
168 /* Again: doesn't handle .it_all conditions! */
169 /* (Inner functions would be nice, again) */
170 if(retain
&& self
->i_retain
.it_count
> 0){
172 for(ifp
= self
->i_retain
.it_ht
[hi
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
173 if(!ascncasecmp(ifp
->if_field
, dat
, len
))
176 if(dat
[len
- 1] != '\0')
177 dat
= savestrbuf(dat
, len
);
178 for(irp
= self
->i_retain
.it_re
; irp
!= NULL
; irp
= irp
->ir_next
)
180 ? (regexec(&irp
->ir_regex
, dat
, 0,NULL
, 0) != REG_NOMATCH
)
181 : !strncmp(irp
->ir_input
, dat
, len
)))
184 rv
= (retain
== TRUM1
) ? TRUM1
: FAL0
;
185 }else if((retain
== TRUM1
|| !retain
) && self
->i_ignore
.it_count
> 0){
187 for(ifp
= self
->i_ignore
.it_ht
[hi
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
188 if(!ascncasecmp(ifp
->if_field
, dat
, len
))
191 if(dat
[len
- 1] != '\0')
192 dat
= savestrbuf(dat
, len
);
193 for(irp
= self
->i_ignore
.it_re
; irp
!= NULL
; irp
= irp
->ir_next
)
195 ? (regexec(&irp
->ir_regex
, dat
, 0,NULL
, 0) != REG_NOMATCH
)
196 : !strncmp(irp
->ir_input
, dat
, len
)))
199 rv
= (retain
== TRUM1
) ? TRU1
: FAL0
;
208 a_ignore_del_allof(struct n_ignore
*ip
, bool_t retain
){
210 struct a_ignore_re
*irp
;
212 struct a_ignore_field
*ifp
;
213 struct a_ignore_type
*itp
;
216 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
221 for(i
= 0; i
< n_NELEM(itp
->it_ht
); ++i
)
222 for(ifp
= itp
->it_ht
[i
]; ifp
!= NULL
;){
223 struct a_ignore_field
*x
;
232 for(irp
= itp
->it_re
; irp
!= NULL
;){
233 struct a_ignore_re
*x
;
237 regfree(&x
->ir_regex
);
243 memset(itp
, 0, sizeof *itp
);
247 static struct a_ignore_bltin_map
const *
248 a_ignore_resolve_bltin(char const *cp
){
249 struct a_ignore_bltin_map
const *ibmp
;
252 for(ibmp
= &a_ignore_bltin_map
[0];;)
253 if(!asccasecmp(cp
, ibmp
->ibm_name
))
255 else if(++ibmp
== &a_ignore_bltin_map
[n_NELEM(a_ignore_bltin_map
)]){
264 a_ignore_addcmd_mux(struct n_ignore
*ip
, char const **list
, bool_t retain
){
269 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
272 if(ip
!= NULL
&& ip
->i_bltin
)
273 a_ignore__show(ip
, retain
);
276 for(ap
= list
; *ap
!= 0; ++ap
)
277 switch(n_ignore_insert_cp(ip
, retain
, *ap
)){
279 n_err(_("Invalid field name cannot be %s: %s\n"),
280 (retain
? _("retained") : _("ignored")), *ap
);
284 if(n_poption
& n_PO_D_V
)
285 n_err(_("Field already %s: %s\n"),
286 (retain
? _("retained") : _("ignored")), *ap
);
297 a_ignore__show(struct n_ignore
const *ip
, bool_t retain
){
299 struct a_ignore_re
*irp
;
301 struct a_ignore_field
*ifp
;
303 char const **ap
, **ring
;
304 struct a_ignore_type
const *itp
;
307 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
310 char const *pre
, *attr
;
313 pre
= n_empty
, attr
= "*";
314 else if(itp
->it_count
== 0)
315 pre
= "#", attr
= _("currently covers no fields");
318 fprintf(n_stdout
, _("%sheaderpick %s %s %s\n"),
319 pre
, a_ignore_bltin_map
[ip
->i_ibm_idx
].ibm_name
,
320 (retain
? "retain" : "ignore"), attr
);
324 ring
= salloc((itp
->it_count
+1) * sizeof *ring
);
325 for(ap
= ring
, i
= 0; i
< n_NELEM(itp
->it_ht
); ++i
)
326 for(ifp
= itp
->it_ht
[i
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
327 *ap
++ = ifp
->if_field
;
330 qsort(ring
, PTR2SIZE(ap
- ring
), sizeof *ring
, &a_ignore__cmp
);
332 i
= fprintf(n_stdout
, "headerpick %s %s add",
333 a_ignore_bltin_map
[ip
->i_ibm_idx
].ibm_name
,
334 (retain
? "retain" : "ignore"));
337 for(ap
= ring
; *ap
!= NULL
; ++ap
){
338 /* These fields are all ASCII, no visual width needed */
341 len
= strlen(*ap
) + 1;
342 if(UICMP(z
, len
, >=, sw
- i
)){
343 fputs(" \\\n ", n_stdout
);
348 fputs(*ap
, n_stdout
);
351 /* Regular expression in FIFO order */
353 for(irp
= itp
->it_re
; irp
!= NULL
; irp
= irp
->ir_next
){
357 cp
= n_shexp_quote_cp(irp
->ir_input
, FAL0
);
358 len
= strlen(cp
) + 1;
359 if(UICMP(z
, len
, >=, sw
- i
)){
360 fputs(" \\\n ", n_stdout
);
369 putc('\n', n_stdout
);
376 a_ignore__cmp(void const *l
, void const *r
){
379 rv
= asccasecmp(*(char const * const *)l
, *(char const * const *)r
);
384 a_ignore_delcmd_mux(struct n_ignore
*ip
, char const **list
, bool_t retain
){
386 struct a_ignore_type
*itp
;
392 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
393 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
395 if(itp
->it_count
== 0 && !itp
->it_all
)
396 n_err(_("No fields currently being %s\n"),
397 (retain
? _("retained") : _("ignored")));
399 while((cp
= *list
++) != NULL
)
400 if(cp
[0] == '*' && cp
[1] == '\0')
401 a_ignore_del_allof(ip
, retain
);
402 else if(!a_ignore__delone(ip
, retain
, cp
)){
403 n_err(_("Field not %s: %s\n"),
404 (retain
? _("retained") : _("ignored")), cp
);
412 a_ignore__delone(struct n_ignore
*ip
, bool_t retain
, char const *field
){
413 struct a_ignore_type
*itp
;
416 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
419 if(n_is_maybe_regex(field
)){
420 struct a_ignore_re
**lirp
, *irp
;
422 for(irp
= *(lirp
= &itp
->it_re
); irp
!= NULL
;
423 lirp
= &irp
->ir_next
, irp
= irp
->ir_next
)
424 if(!strcmp(field
, irp
->ir_input
)){
425 *lirp
= irp
->ir_next
;
426 if(irp
== itp
->it_re_tail
)
427 itp
->it_re_tail
= irp
->ir_next
;
429 regfree(&irp
->ir_regex
);
436 #endif /* HAVE_REGEX */
438 struct a_ignore_field
**ifpp
, *ifp
;
441 hi
= torek_ihashn(field
, UIZ_MAX
) % n_NELEM(itp
->it_ht
);
443 for(ifp
= *(ifpp
= &itp
->it_ht
[hi
]); ifp
!= NULL
;
444 ifpp
= &ifp
->if_next
, ifp
= ifp
->if_next
)
445 if(!asccasecmp(ifp
->if_field
, field
)){
446 *ifpp
= ifp
->if_next
;
461 c_headerpick(void *v
){
463 struct a_ignore_bltin_map
const *ibmp
;
471 /* Without arguments, show all settings of all contexts */
474 for(ibmp
= &a_ignore_bltin_map
[0];
475 ibmp
<= &a_ignore_bltin_map
[n__IGNORE_MAX
]; ++ibmp
){
476 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
477 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
482 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
483 n_err(_("`headerpick': invalid context: %s\n"), *argv
);
487 /* With only <context>, show all settings of it */
490 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
491 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
495 if(is_asccaseprefix(*argv
, "retain"))
497 else if(is_asccaseprefix(*argv
, "ignore"))
500 n_err(_("`headerpick': invalid type (retain, ignore): %s\n"), *argv
);
504 /* With only <context> and <type>, show its settings */
506 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
511 n_err(_("Synopsis: headerpick: [<context> [<type> "
512 "[<action> <header-list>]]]\n"));
514 }else if(is_asccaseprefix(*argv
, "add") ||
515 (argv
[0][0] == '+' && argv
[0][1] == '\0'))
516 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, ++argv
, retain
);
517 else if(is_asccaseprefix(*argv
, "remove") ||
518 (argv
[0][0] == '-' && argv
[0][1] == '\0'))
519 rv
= !a_ignore_delcmd_mux(ibmp
->ibm_ip
, ++argv
, retain
);
521 n_err(_("`headerpick': invalid action (add, +, remove, -): %s\n"), *argv
);
532 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, v
, TRU1
);
542 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, v
, FAL0
);
552 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, v
, TRU1
);
562 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, v
, FAL0
);
568 c_saveretain(void *v
){ /* TODO v15 drop */
572 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
578 c_saveignore(void *v
){ /* TODO v15 drop */
582 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
588 c_unsaveretain(void *v
){ /* TODO v15 drop */
592 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
598 c_unsaveignore(void *v
){ /* TODO v15 drop */
602 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
608 c_fwdretain(void *v
){ /* TODO v15 drop */
612 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
618 c_fwdignore(void *v
){ /* TODO v15 drop */
622 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
628 c_unfwdretain(void *v
){ /* TODO v15 drop */
632 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
638 c_unfwdignore(void *v
){ /* TODO v15 drop */
642 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
648 n_ignore_new(bool_t isauto
){
649 struct n_ignore
*self
;
652 self
= isauto
? n_autorec_calloc(NULL
, 1, sizeof *self
)
653 : n_calloc(1, sizeof *self
);
654 self
->i_auto
= isauto
;
660 n_ignore_del(struct n_ignore
*self
){
662 a_ignore_del_allof(self
, TRU1
);
663 a_ignore_del_allof(self
, FAL0
);
670 n_ignore_is_any(struct n_ignore
const *self
){
674 self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
);
675 rv
= (self
!= NULL
&&
676 (self
->i_retain
.it_count
!= 0 || self
->i_retain
.it_all
||
677 self
->i_ignore
.it_count
!= 0 || self
->i_ignore
.it_all
));
683 n_ignore_insert(struct n_ignore
*self
, bool_t retain
,
684 char const *dat
, size_t len
){
686 struct a_ignore_re
*irp
;
689 struct a_ignore_field
*ifp
;
690 struct a_ignore_type
*itp
;
694 retain
= !!retain
; /* Make it true bool, TRUM1 has special _lookup meaning */
696 self
= a_ignore_resolve_self(self
, TRU1
);
701 /* Request to ignore or retain _anything_? That is special-treated */
702 if(len
== 1 && dat
[0] == '*'){
703 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
708 a_ignore_del_allof(self
, retain
);
714 /* Check for regular expression or valid fieldname */
716 if(!(isre
= n_is_maybe_regex_buf(dat
, len
)))
722 for(i
= 0; i
< len
; ++i
){
724 if(!fieldnamechar(c
))
730 if(a_ignore_lookup(self
, retain
, dat
, len
) == (retain
? TRU1
: TRUM1
))
733 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
735 if(itp
->it_count
== UI32_MAX
){
736 n_err(_("Header selection size limit reached, cannot insert: %.*s\n"),
737 (int)n_MIN(len
, SI32_MAX
), dat
);
745 struct a_ignore_re
*x
;
749 i
= n_VSTRUCT_SIZEOF(struct a_ignore_re
, ir_input
) + ++len
;
750 irp
= self
->i_auto
? n_autorec_alloc(NULL
, i
) : n_alloc(i
);
751 memcpy(irp
->ir_input
, dat
, --len
);
752 irp
->ir_input
[len
] = '\0';
754 if((s
= regcomp(&irp
->ir_regex
, irp
->ir_input
,
755 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
756 n_err(_("Invalid regular expression: %s: %s\n"),
757 n_shexp_quote_cp(irp
->ir_input
, FAL0
),
758 n_regex_err_to_str(&irp
->ir_regex
, s
));
766 if((x
= itp
->it_re_tail
) != NULL
)
770 itp
->it_re_tail
= irp
;
772 #endif /* HAVE_REGEX */
777 i
= n_VSTRUCT_SIZEOF(struct a_ignore_field
, if_field
) + len
+ 1;
778 ifp
= self
->i_auto
? n_autorec_alloc(NULL
, i
) : n_alloc(i
);
779 memcpy(ifp
->if_field
, dat
, len
);
780 ifp
->if_field
[len
] = '\0';
781 hi
= torek_ihashn(dat
, len
) % n_NELEM(itp
->it_ht
);
782 ifp
->if_next
= itp
->it_ht
[hi
];
783 itp
->it_ht
[hi
] = ifp
;
792 n_ignore_lookup(struct n_ignore
const *self
, char const *dat
, size_t len
){
796 if(self
== n_IGNORE_ALL
)
799 (self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
)) == NULL
)
801 else if(self
->i_retain
.it_all
)
803 else if(self
->i_retain
.it_count
== 0 && self
->i_ignore
.it_all
)
806 rv
= a_ignore_lookup(self
, TRUM1
, dat
, len
);