1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `headerpick', `retain' and `ignore', and `un..' variants.
4 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
5 * SPDX-License-Identifier: ISC
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #ifndef HAVE_AMALGAMATION
27 ui32_t it_count
; /* Entries in .it_ht (and .it_re) */
28 bool_t it_all
; /* _All_ fields ought to be _type_ (ignore/retain) */
30 struct a_ignore_field
{
31 struct a_ignore_field
*if_next
;
32 char if_field
[n_VFIELD_SIZE(0)]; /* Header field */
33 } *it_ht
[3]; /* TODO make hashmap dynamic */
36 struct a_ignore_re
*ir_next
;
38 char ir_input
[n_VFIELD_SIZE(0)]; /* Regex input text (for showing it) */
39 } *it_re
, *it_re_tail
;
44 struct a_ignore_type i_retain
;
45 struct a_ignore_type i_ignore
;
46 bool_t i_auto
; /* In auto-reclaimed, not heap memory */
47 bool_t i_bltin
; /* Is a built-in n_IGNORE* type */
48 ui8_t i_ibm_idx
; /* If .i_bltin: a_ignore_bltin_map[] idx */
52 struct a_ignore_bltin_map
{
53 struct n_ignore
*ibm_ip
;
54 char const ibm_name
[8];
57 static struct a_ignore_bltin_map
const a_ignore_bltin_map
[] = {
58 {n_IGNORE_TYPE
, "type\0"},
59 {n_IGNORE_SAVE
, "save\0"},
60 {n_IGNORE_FWD
, "forward\0"},
61 {n_IGNORE_TOP
, "top\0"},
63 {n_IGNORE_TYPE
, "print\0"},
64 {n_IGNORE_FWD
, "fwd\0"}
66 #ifdef HAVE_DEVEL /* Avoid gcc warn cascade since n_ignore is defined locally */
67 n_CTAV(-n__IGNORE_TYPE
- n__IGNORE_ADJUST
== 0);
68 n_CTAV(-n__IGNORE_SAVE
- n__IGNORE_ADJUST
== 1);
69 n_CTAV(-n__IGNORE_FWD
- n__IGNORE_ADJUST
== 2);
70 n_CTAV(-n__IGNORE_TOP
- n__IGNORE_ADJUST
== 3);
71 n_CTAV(n__IGNORE_MAX
== 3);
74 static struct n_ignore
*a_ignore_bltin
[n__IGNORE_MAX
+ 1];
75 /* Almost everyone uses `ignore'/`retain', put _TYPE in BSS */
76 static struct n_ignore a_ignore_type
;
78 /* Return real self, which is xself unless that is one of the built-in specials,
79 * in which case NULL is returned if nonexistent and docreate is false.
80 * The other statics assume self has been resolved (unless noted) */
81 static struct n_ignore
*a_ignore_resolve_self(struct n_ignore
*xself
,
84 /* Lookup whether a mapping is contained: TRU1=retained, TRUM1=ignored.
85 * If retain is _not_ TRUM1 then only the retained/ignored slot is inspected,
86 * and regular expressions are not executed but instead their .ir_input is
87 * text-compared against len bytes of dat.
88 * Note it doesn't handle the .it_all "all fields" condition */
89 static bool_t
a_ignore_lookup(struct n_ignore
const *self
, bool_t retain
,
90 char const *dat
, size_t len
);
92 /* Delete all retain( else ignor)ed members */
93 static void a_ignore_del_allof(struct n_ignore
*ip
, bool_t retain
);
95 /* Try to map a string to one of the built-in types */
96 static struct a_ignore_bltin_map
const *a_ignore_resolve_bltin(char const *cp
);
98 /* Logic behind `headerpick T T' (a.k.a. `retain'+) */
99 static bool_t
a_ignore_addcmd_mux(struct n_ignore
*ip
, char const **list
,
102 static void a_ignore__show(struct n_ignore
const *ip
, bool_t retain
);
103 static int a_ignore__cmp(void const *l
, void const *r
);
105 /* Logic behind `unheaderpick T T' (a.k.a. `unretain'+) */
106 static bool_t
a_ignore_delcmd_mux(struct n_ignore
*ip
, char const **list
,
109 static bool_t
a_ignore__delone(struct n_ignore
*ip
, bool_t retain
,
112 static struct n_ignore
*
113 a_ignore_resolve_self(struct n_ignore
*xself
, bool_t docreate
){
115 struct n_ignore
*self
;
119 suip
= -(uintptr_t)self
- n__IGNORE_ADJUST
;
121 if(suip
<= n__IGNORE_MAX
){
122 if((self
= a_ignore_bltin
[suip
]) == NULL
&& docreate
){
123 if(xself
== n_IGNORE_TYPE
){
124 self
= &a_ignore_type
;
125 /* LIB: memset(self, 0, sizeof *self);*/
127 self
= n_ignore_new(FAL0
);
128 self
->i_bltin
= TRU1
;
129 self
->i_ibm_idx
= (ui8_t
)suip
;
130 a_ignore_bltin
[suip
] = self
;
138 a_ignore_lookup(struct n_ignore
const *self
, bool_t retain
,
139 char const *dat
, size_t len
){
142 struct a_ignore_re
*irp
;
144 struct a_ignore_field
*ifp
;
150 hi
= n_torek_ihashn(dat
, len
) % n_NELEM(self
->i_retain
.it_ht
);
152 /* Again: doesn't handle .it_all conditions! */
153 /* (Inner functions would be nice, again) */
154 if(retain
&& self
->i_retain
.it_count
> 0){
156 for(ifp
= self
->i_retain
.it_ht
[hi
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
157 if(!ascncasecmp(ifp
->if_field
, dat
, len
))
160 if(dat
[len
- 1] != '\0')
161 dat
= savestrbuf(dat
, len
);
162 for(irp
= self
->i_retain
.it_re
; irp
!= NULL
; irp
= irp
->ir_next
)
164 ? (regexec(&irp
->ir_regex
, dat
, 0,NULL
, 0) != REG_NOMATCH
)
165 : !strncmp(irp
->ir_input
, dat
, len
)))
168 rv
= (retain
== TRUM1
) ? TRUM1
: FAL0
;
169 }else if((retain
== TRUM1
|| !retain
) && self
->i_ignore
.it_count
> 0){
171 for(ifp
= self
->i_ignore
.it_ht
[hi
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
172 if(!ascncasecmp(ifp
->if_field
, dat
, len
))
175 if(dat
[len
- 1] != '\0')
176 dat
= savestrbuf(dat
, len
);
177 for(irp
= self
->i_ignore
.it_re
; irp
!= NULL
; irp
= irp
->ir_next
)
179 ? (regexec(&irp
->ir_regex
, dat
, 0,NULL
, 0) != REG_NOMATCH
)
180 : !strncmp(irp
->ir_input
, dat
, len
)))
183 rv
= (retain
== TRUM1
) ? TRU1
: FAL0
;
192 a_ignore_del_allof(struct n_ignore
*ip
, bool_t retain
){
194 struct a_ignore_re
*irp
;
196 struct a_ignore_field
*ifp
;
197 struct a_ignore_type
*itp
;
200 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
205 for(i
= 0; i
< n_NELEM(itp
->it_ht
); ++i
)
206 for(ifp
= itp
->it_ht
[i
]; ifp
!= NULL
;){
207 struct a_ignore_field
*x
;
216 for(irp
= itp
->it_re
; irp
!= NULL
;){
217 struct a_ignore_re
*x
;
221 regfree(&x
->ir_regex
);
227 memset(itp
, 0, sizeof *itp
);
231 static struct a_ignore_bltin_map
const *
232 a_ignore_resolve_bltin(char const *cp
){
233 struct a_ignore_bltin_map
const *ibmp
;
236 for(ibmp
= &a_ignore_bltin_map
[0];;)
237 if(!asccasecmp(cp
, ibmp
->ibm_name
))
239 else if(++ibmp
== &a_ignore_bltin_map
[n_NELEM(a_ignore_bltin_map
)]){
248 a_ignore_addcmd_mux(struct n_ignore
*ip
, char const **list
, bool_t retain
){
253 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
256 if(ip
!= NULL
&& ip
->i_bltin
)
257 a_ignore__show(ip
, retain
);
260 for(ap
= list
; *ap
!= 0; ++ap
)
261 switch(n_ignore_insert_cp(ip
, retain
, *ap
)){
263 n_err(_("Invalid field name cannot be %s: %s\n"),
264 (retain
? _("retained") : _("ignored")), *ap
);
268 if(n_poption
& n_PO_D_V
)
269 n_err(_("Field already %s: %s\n"),
270 (retain
? _("retained") : _("ignored")), *ap
);
281 a_ignore__show(struct n_ignore
const *ip
, bool_t retain
){
283 struct a_ignore_re
*irp
;
285 struct a_ignore_field
*ifp
;
287 char const **ap
, **ring
;
288 struct a_ignore_type
const *itp
;
291 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
294 char const *pre
, *attr
;
297 pre
= n_empty
, attr
= n_star
;
298 else if(itp
->it_count
== 0)
299 pre
= n_ns
, attr
= _("currently covers no fields");
302 fprintf(n_stdout
, _("%sheaderpick %s %s %s\n"),
303 pre
, a_ignore_bltin_map
[ip
->i_ibm_idx
].ibm_name
,
304 (retain
? "retain" : "ignore"), attr
);
308 ring
= n_autorec_alloc((itp
->it_count
+1) * sizeof *ring
);
309 for(ap
= ring
, i
= 0; i
< n_NELEM(itp
->it_ht
); ++i
)
310 for(ifp
= itp
->it_ht
[i
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
311 *ap
++ = ifp
->if_field
;
314 qsort(ring
, PTR2SIZE(ap
- ring
), sizeof *ring
, &a_ignore__cmp
);
316 i
= fprintf(n_stdout
, "headerpick %s %s",
317 a_ignore_bltin_map
[ip
->i_ibm_idx
].ibm_name
,
318 (retain
? "retain" : "ignore"));
321 for(ap
= ring
; *ap
!= NULL
; ++ap
){
322 /* These fields are all ASCII, no visual width needed */
325 len
= strlen(*ap
) + 1;
326 if(UICMP(z
, len
, >=, sw
- i
)){
327 fputs(" \\\n ", n_stdout
);
332 fputs(*ap
, n_stdout
);
335 /* Regular expression in FIFO order */
337 for(irp
= itp
->it_re
; irp
!= NULL
; irp
= irp
->ir_next
){
341 cp
= n_shexp_quote_cp(irp
->ir_input
, FAL0
);
342 len
= strlen(cp
) + 1;
343 if(UICMP(z
, len
, >=, sw
- i
)){
344 fputs(" \\\n ", n_stdout
);
353 putc('\n', n_stdout
);
360 a_ignore__cmp(void const *l
, void const *r
){
363 rv
= asccasecmp(*(char const * const *)l
, *(char const * const *)r
);
368 a_ignore_delcmd_mux(struct n_ignore
*ip
, char const **list
, bool_t retain
){
370 struct a_ignore_type
*itp
;
374 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
375 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
377 if(itp
->it_count
== 0 && !itp
->it_all
)
378 n_err(_("No fields currently being %s\n"),
379 (retain
? _("retained") : _("ignored")));
381 while((cp
= *list
++) != NULL
)
382 if(cp
[0] == '*' && cp
[1] == '\0')
383 a_ignore_del_allof(ip
, retain
);
384 else if(!a_ignore__delone(ip
, retain
, cp
)){
385 n_err(_("Field not %s: %s\n"),
386 (retain
? _("retained") : _("ignored")), cp
);
394 a_ignore__delone(struct n_ignore
*ip
, bool_t retain
, char const *field
){
395 struct a_ignore_type
*itp
;
398 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
401 if(n_is_maybe_regex(field
)){
402 struct a_ignore_re
**lirp
, *irp
;
404 for(irp
= *(lirp
= &itp
->it_re
); irp
!= NULL
;
405 lirp
= &irp
->ir_next
, irp
= irp
->ir_next
)
406 if(!strcmp(field
, irp
->ir_input
)){
407 *lirp
= irp
->ir_next
;
408 if(irp
== itp
->it_re_tail
)
409 itp
->it_re_tail
= irp
->ir_next
;
411 regfree(&irp
->ir_regex
);
418 #endif /* HAVE_REGEX */
420 struct a_ignore_field
**ifpp
, *ifp
;
423 hi
= n_torek_ihashn(field
, UIZ_MAX
) % n_NELEM(itp
->it_ht
);
425 for(ifp
= *(ifpp
= &itp
->it_ht
[hi
]); ifp
!= NULL
;
426 ifpp
= &ifp
->if_next
, ifp
= ifp
->if_next
)
427 if(!asccasecmp(ifp
->if_field
, field
)){
428 *ifpp
= ifp
->if_next
;
443 c_headerpick(void *vp
){
445 struct a_ignore_bltin_map
const *ibmp
;
453 /* Without arguments, show all settings of all contexts */
456 for(ibmp
= &a_ignore_bltin_map
[0];
457 ibmp
<= &a_ignore_bltin_map
[n__IGNORE_MAX
]; ++ibmp
){
458 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
459 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
464 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
465 n_err(_("`headerpick': invalid context: %s\n"), *argv
);
470 /* With only <context>, show all settings of it */
473 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
474 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
478 if(is_asccaseprefix(*argv
, "retain"))
480 else if(is_asccaseprefix(*argv
, "ignore"))
483 n_err(_("`headerpick': invalid type (retain, ignore): %s\n"), *argv
);
488 /* With only <context> and <type>, show its settings */
490 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
494 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
501 c_unheaderpick(void *vp
){
503 struct a_ignore_bltin_map
const *ibmp
;
511 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
512 n_err(_("`unheaderpick': invalid context: %s\n"), *argv
);
517 if(is_asccaseprefix(*argv
, "retain"))
519 else if(is_asccaseprefix(*argv
, "ignore"))
522 n_err(_("`unheaderpick': invalid type (retain, ignore): %s\n"), *argv
);
527 rv
= !a_ignore_delcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
538 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, vp
, TRU1
);
548 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, vp
, FAL0
);
554 c_unretain(void *vp
){
558 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, vp
, TRU1
);
564 c_unignore(void *vp
){
568 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, vp
, FAL0
);
574 c_saveretain(void *v
){ /* TODO v15 drop */
578 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
584 c_saveignore(void *v
){ /* TODO v15 drop */
588 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
594 c_unsaveretain(void *v
){ /* TODO v15 drop */
598 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
604 c_unsaveignore(void *v
){ /* TODO v15 drop */
608 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
614 c_fwdretain(void *v
){ /* TODO v15 drop */
618 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
624 c_fwdignore(void *v
){ /* TODO v15 drop */
628 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
634 c_unfwdretain(void *v
){ /* TODO v15 drop */
638 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
644 c_unfwdignore(void *v
){ /* TODO v15 drop */
648 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
654 n_ignore_new(bool_t isauto
){
655 struct n_ignore
*self
;
658 self
= isauto
? n_autorec_calloc(1, sizeof *self
) : n_calloc(1,sizeof *self
);
659 self
->i_auto
= isauto
;
665 n_ignore_del(struct n_ignore
*self
){
667 a_ignore_del_allof(self
, TRU1
);
668 a_ignore_del_allof(self
, FAL0
);
675 n_ignore_is_any(struct n_ignore
const *self
){
679 self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
);
680 rv
= (self
!= NULL
&&
681 (self
->i_retain
.it_count
!= 0 || self
->i_retain
.it_all
||
682 self
->i_ignore
.it_count
!= 0 || self
->i_ignore
.it_all
));
688 n_ignore_insert(struct n_ignore
*self
, bool_t retain
,
689 char const *dat
, size_t len
){
691 struct a_ignore_re
*irp
;
694 struct a_ignore_field
*ifp
;
695 struct a_ignore_type
*itp
;
699 retain
= !!retain
; /* Make it true bool, TRUM1 has special _lookup meaning */
701 self
= a_ignore_resolve_self(self
, TRU1
);
706 /* Request to ignore or retain _anything_? That is special-treated */
707 if(len
== 1 && dat
[0] == '*'){
708 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
713 a_ignore_del_allof(self
, retain
);
719 /* Check for regular expression or valid fieldname */
721 if(!(isre
= n_is_maybe_regex_buf(dat
, len
)))
727 for(i
= 0; i
< len
; ++i
){
729 if(!fieldnamechar(c
))
735 if(a_ignore_lookup(self
, retain
, dat
, len
) == (retain
? TRU1
: TRUM1
))
738 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
740 if(itp
->it_count
== UI32_MAX
){
741 n_err(_("Header selection size limit reached, cannot insert: %.*s\n"),
742 (int)n_MIN(len
, SI32_MAX
), dat
);
750 struct a_ignore_re
*x
;
754 i
= n_VSTRUCT_SIZEOF(struct a_ignore_re
, ir_input
) + ++len
;
755 irp
= self
->i_auto
? n_autorec_alloc(i
) : n_alloc(i
);
756 memcpy(irp
->ir_input
, dat
, --len
);
757 irp
->ir_input
[len
] = '\0';
759 if((s
= regcomp(&irp
->ir_regex
, irp
->ir_input
,
760 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
761 n_err(_("Invalid regular expression: %s: %s\n"),
762 n_shexp_quote_cp(irp
->ir_input
, FAL0
),
763 n_regex_err_to_doc(NULL
, s
));
771 if((x
= itp
->it_re_tail
) != NULL
)
775 itp
->it_re_tail
= irp
;
777 #endif /* HAVE_REGEX */
782 i
= n_VSTRUCT_SIZEOF(struct a_ignore_field
, if_field
) + len
+ 1;
783 ifp
= self
->i_auto
? n_autorec_alloc(i
) : n_alloc(i
);
784 memcpy(ifp
->if_field
, dat
, len
);
785 ifp
->if_field
[len
] = '\0';
786 hi
= n_torek_ihashn(dat
, len
) % n_NELEM(itp
->it_ht
);
787 ifp
->if_next
= itp
->it_ht
[hi
];
788 itp
->it_ht
[hi
] = ifp
;
797 n_ignore_lookup(struct n_ignore
const *self
, char const *dat
, size_t len
){
801 if(self
== n_IGNORE_ALL
)
804 (self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
)) == NULL
)
806 else if(self
->i_retain
.it_all
)
808 else if(self
->i_retain
.it_count
== 0 && self
->i_ignore
.it_all
)
811 rv
= a_ignore_lookup(self
, TRUM1
, dat
, len
);