1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `headerpick', `retain' and `ignore', and `un..' variants.
4 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 * Copyright (c) 1980, 1993
8 * The Regents of the University of California. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 #ifndef HAVE_AMALGAMATION
42 ui32_t it_count
; /* Entries in .it_ht (and .it_re) */
43 bool_t it_all
; /* _All_ fields ought to be _type_ (ignore/retain) */
45 struct a_ignore_field
{
46 struct a_ignore_field
*if_next
;
47 char if_field
[n_VFIELD_SIZE(0)]; /* Header field */
48 } *it_ht
[3]; /* TODO make hashmap dynamic */
51 struct a_ignore_re
*ir_next
;
53 char ir_input
[n_VFIELD_SIZE(0)]; /* Regex input text (for showing it) */
54 } *it_re
, *it_re_tail
;
59 struct a_ignore_type i_retain
;
60 struct a_ignore_type i_ignore
;
61 bool_t i_auto
; /* In auto-reclaimed, not heap memory */
62 bool_t i_bltin
; /* Is a built-in n_IGNORE* type */
63 ui8_t i_ibm_idx
; /* If .i_bltin: a_ignore_bltin_map[] idx */
67 struct a_ignore_bltin_map
{
68 struct n_ignore
*ibm_ip
;
69 char const ibm_name
[8];
72 static struct a_ignore_bltin_map
const a_ignore_bltin_map
[] = {
73 {n_IGNORE_TYPE
, "type\0"},
74 {n_IGNORE_SAVE
, "save\0"},
75 {n_IGNORE_FWD
, "forward\0"},
76 {n_IGNORE_TOP
, "top\0"},
78 {n_IGNORE_TYPE
, "print\0"},
79 {n_IGNORE_FWD
, "fwd\0"}
81 #ifdef HAVE_DEVEL /* Avoid gcc warn cascade since n_ignore is defined locally */
82 n_CTAV(-n__IGNORE_TYPE
- n__IGNORE_ADJUST
== 0);
83 n_CTAV(-n__IGNORE_SAVE
- n__IGNORE_ADJUST
== 1);
84 n_CTAV(-n__IGNORE_FWD
- n__IGNORE_ADJUST
== 2);
85 n_CTAV(-n__IGNORE_TOP
- n__IGNORE_ADJUST
== 3);
86 n_CTAV(n__IGNORE_MAX
== 3);
89 static struct n_ignore
*a_ignore_bltin
[n__IGNORE_MAX
+ 1];
90 /* Almost everyone uses `ignore'/`retain', put _TYPE in BSS */
91 static struct n_ignore a_ignore_type
;
93 /* Return real self, which is xself unless that is one of the built-in specials,
94 * in which case NULL is returned if nonexistent and docreate is false.
95 * The other statics assume self has been resolved (unless noted) */
96 static struct n_ignore
*a_ignore_resolve_self(struct n_ignore
*xself
,
99 /* Lookup whether a mapping is contained: TRU1=retained, TRUM1=ignored.
100 * If retain is _not_ TRUM1 then only the retained/ignored slot is inspected,
101 * and regular expressions are not executed but instead their .ir_input is
102 * text-compared against len bytes of dat.
103 * Note it doesn't handle the .it_all "all fields" condition */
104 static bool_t
a_ignore_lookup(struct n_ignore
const *self
, bool_t retain
,
105 char const *dat
, size_t len
);
107 /* Delete all retain( else ignor)ed members */
108 static void a_ignore_del_allof(struct n_ignore
*ip
, bool_t retain
);
110 /* Try to map a string to one of the built-in types */
111 static struct a_ignore_bltin_map
const *a_ignore_resolve_bltin(char const *cp
);
113 /* Logic behind `headerpick T T' (a.k.a. `retain'+) */
114 static bool_t
a_ignore_addcmd_mux(struct n_ignore
*ip
, char const **list
,
117 static void a_ignore__show(struct n_ignore
const *ip
, bool_t retain
);
118 static int a_ignore__cmp(void const *l
, void const *r
);
120 /* Logic behind `unheaderpick T T' (a.k.a. `unretain'+) */
121 static bool_t
a_ignore_delcmd_mux(struct n_ignore
*ip
, char const **list
,
124 static bool_t
a_ignore__delone(struct n_ignore
*ip
, bool_t retain
,
127 static struct n_ignore
*
128 a_ignore_resolve_self(struct n_ignore
*xself
, bool_t docreate
){
130 struct n_ignore
*self
;
134 suip
= -(uintptr_t)self
- n__IGNORE_ADJUST
;
136 if(suip
<= n__IGNORE_MAX
){
137 if((self
= a_ignore_bltin
[suip
]) == NULL
&& docreate
){
138 if(xself
== n_IGNORE_TYPE
){
139 self
= &a_ignore_type
;
140 /* LIB: memset(self, 0, sizeof *self);*/
142 self
= n_ignore_new(FAL0
);
143 self
->i_bltin
= TRU1
;
144 self
->i_ibm_idx
= (ui8_t
)suip
;
145 a_ignore_bltin
[suip
] = self
;
153 a_ignore_lookup(struct n_ignore
const *self
, bool_t retain
,
154 char const *dat
, size_t len
){
157 struct a_ignore_re
*irp
;
159 struct a_ignore_field
*ifp
;
165 hi
= n_torek_ihashn(dat
, len
) % n_NELEM(self
->i_retain
.it_ht
);
167 /* Again: doesn't handle .it_all conditions! */
168 /* (Inner functions would be nice, again) */
169 if(retain
&& self
->i_retain
.it_count
> 0){
171 for(ifp
= self
->i_retain
.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_retain
.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
) ? TRUM1
: FAL0
;
184 }else if((retain
== TRUM1
|| !retain
) && self
->i_ignore
.it_count
> 0){
186 for(ifp
= self
->i_ignore
.it_ht
[hi
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
187 if(!ascncasecmp(ifp
->if_field
, dat
, len
))
190 if(dat
[len
- 1] != '\0')
191 dat
= savestrbuf(dat
, len
);
192 for(irp
= self
->i_ignore
.it_re
; irp
!= NULL
; irp
= irp
->ir_next
)
194 ? (regexec(&irp
->ir_regex
, dat
, 0,NULL
, 0) != REG_NOMATCH
)
195 : !strncmp(irp
->ir_input
, dat
, len
)))
198 rv
= (retain
== TRUM1
) ? TRU1
: FAL0
;
207 a_ignore_del_allof(struct n_ignore
*ip
, bool_t retain
){
209 struct a_ignore_re
*irp
;
211 struct a_ignore_field
*ifp
;
212 struct a_ignore_type
*itp
;
215 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
220 for(i
= 0; i
< n_NELEM(itp
->it_ht
); ++i
)
221 for(ifp
= itp
->it_ht
[i
]; ifp
!= NULL
;){
222 struct a_ignore_field
*x
;
231 for(irp
= itp
->it_re
; irp
!= NULL
;){
232 struct a_ignore_re
*x
;
236 regfree(&x
->ir_regex
);
242 memset(itp
, 0, sizeof *itp
);
246 static struct a_ignore_bltin_map
const *
247 a_ignore_resolve_bltin(char const *cp
){
248 struct a_ignore_bltin_map
const *ibmp
;
251 for(ibmp
= &a_ignore_bltin_map
[0];;)
252 if(!asccasecmp(cp
, ibmp
->ibm_name
))
254 else if(++ibmp
== &a_ignore_bltin_map
[n_NELEM(a_ignore_bltin_map
)]){
263 a_ignore_addcmd_mux(struct n_ignore
*ip
, char const **list
, bool_t retain
){
268 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
271 if(ip
!= NULL
&& ip
->i_bltin
)
272 a_ignore__show(ip
, retain
);
275 for(ap
= list
; *ap
!= 0; ++ap
)
276 switch(n_ignore_insert_cp(ip
, retain
, *ap
)){
278 n_err(_("Invalid field name cannot be %s: %s\n"),
279 (retain
? _("retained") : _("ignored")), *ap
);
283 if(n_poption
& n_PO_D_V
)
284 n_err(_("Field already %s: %s\n"),
285 (retain
? _("retained") : _("ignored")), *ap
);
296 a_ignore__show(struct n_ignore
const *ip
, bool_t retain
){
298 struct a_ignore_re
*irp
;
300 struct a_ignore_field
*ifp
;
302 char const **ap
, **ring
;
303 struct a_ignore_type
const *itp
;
306 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
309 char const *pre
, *attr
;
312 pre
= n_empty
, attr
= n_star
;
313 else if(itp
->it_count
== 0)
314 pre
= n_ns
, attr
= _("currently covers no fields");
317 fprintf(n_stdout
, _("%sheaderpick %s %s %s\n"),
318 pre
, a_ignore_bltin_map
[ip
->i_ibm_idx
].ibm_name
,
319 (retain
? "retain" : "ignore"), attr
);
323 ring
= salloc((itp
->it_count
+1) * sizeof *ring
);
324 for(ap
= ring
, i
= 0; i
< n_NELEM(itp
->it_ht
); ++i
)
325 for(ifp
= itp
->it_ht
[i
]; ifp
!= NULL
; ifp
= ifp
->if_next
)
326 *ap
++ = ifp
->if_field
;
329 qsort(ring
, PTR2SIZE(ap
- ring
), sizeof *ring
, &a_ignore__cmp
);
331 i
= fprintf(n_stdout
, "headerpick %s %s",
332 a_ignore_bltin_map
[ip
->i_ibm_idx
].ibm_name
,
333 (retain
? "retain" : "ignore"));
336 for(ap
= ring
; *ap
!= NULL
; ++ap
){
337 /* These fields are all ASCII, no visual width needed */
340 len
= strlen(*ap
) + 1;
341 if(UICMP(z
, len
, >=, sw
- i
)){
342 fputs(" \\\n ", n_stdout
);
347 fputs(*ap
, n_stdout
);
350 /* Regular expression in FIFO order */
352 for(irp
= itp
->it_re
; irp
!= NULL
; irp
= irp
->ir_next
){
356 cp
= n_shexp_quote_cp(irp
->ir_input
, FAL0
);
357 len
= strlen(cp
) + 1;
358 if(UICMP(z
, len
, >=, sw
- i
)){
359 fputs(" \\\n ", n_stdout
);
368 putc('\n', n_stdout
);
375 a_ignore__cmp(void const *l
, void const *r
){
378 rv
= asccasecmp(*(char const * const *)l
, *(char const * const *)r
);
383 a_ignore_delcmd_mux(struct n_ignore
*ip
, char const **list
, bool_t retain
){
385 struct a_ignore_type
*itp
;
391 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
392 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
394 if(itp
->it_count
== 0 && !itp
->it_all
)
395 n_err(_("No fields currently being %s\n"),
396 (retain
? _("retained") : _("ignored")));
398 while((cp
= *list
++) != NULL
)
399 if(cp
[0] == '*' && cp
[1] == '\0')
400 a_ignore_del_allof(ip
, retain
);
401 else if(!a_ignore__delone(ip
, retain
, cp
)){
402 n_err(_("Field not %s: %s\n"),
403 (retain
? _("retained") : _("ignored")), cp
);
411 a_ignore__delone(struct n_ignore
*ip
, bool_t retain
, char const *field
){
412 struct a_ignore_type
*itp
;
415 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
418 if(n_is_maybe_regex(field
)){
419 struct a_ignore_re
**lirp
, *irp
;
421 for(irp
= *(lirp
= &itp
->it_re
); irp
!= NULL
;
422 lirp
= &irp
->ir_next
, irp
= irp
->ir_next
)
423 if(!strcmp(field
, irp
->ir_input
)){
424 *lirp
= irp
->ir_next
;
425 if(irp
== itp
->it_re_tail
)
426 itp
->it_re_tail
= irp
->ir_next
;
428 regfree(&irp
->ir_regex
);
435 #endif /* HAVE_REGEX */
437 struct a_ignore_field
**ifpp
, *ifp
;
440 hi
= n_torek_ihashn(field
, UIZ_MAX
) % n_NELEM(itp
->it_ht
);
442 for(ifp
= *(ifpp
= &itp
->it_ht
[hi
]); ifp
!= NULL
;
443 ifpp
= &ifp
->if_next
, ifp
= ifp
->if_next
)
444 if(!asccasecmp(ifp
->if_field
, field
)){
445 *ifpp
= ifp
->if_next
;
460 c_headerpick(void *vp
){
462 struct a_ignore_bltin_map
const *ibmp
;
470 /* Without arguments, show all settings of all contexts */
473 for(ibmp
= &a_ignore_bltin_map
[0];
474 ibmp
<= &a_ignore_bltin_map
[n__IGNORE_MAX
]; ++ibmp
){
475 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
476 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
481 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
482 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
);
505 /* With only <context> and <type>, show its settings */
507 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
511 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
518 c_unheaderpick(void *vp
){
520 struct a_ignore_bltin_map
const *ibmp
;
528 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
529 n_err(_("`unheaderpick': invalid context: %s\n"), *argv
);
534 if(is_asccaseprefix(*argv
, "retain"))
536 else if(is_asccaseprefix(*argv
, "ignore"))
539 n_err(_("`unheaderpick': invalid type (retain, ignore): %s\n"), *argv
);
544 rv
= !a_ignore_delcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
555 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, vp
, TRU1
);
565 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, vp
, FAL0
);
571 c_unretain(void *vp
){
575 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, vp
, TRU1
);
581 c_unignore(void *vp
){
585 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, vp
, FAL0
);
591 c_saveretain(void *v
){ /* TODO v15 drop */
595 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
601 c_saveignore(void *v
){ /* TODO v15 drop */
605 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
611 c_unsaveretain(void *v
){ /* TODO v15 drop */
615 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
621 c_unsaveignore(void *v
){ /* TODO v15 drop */
625 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
631 c_fwdretain(void *v
){ /* TODO v15 drop */
635 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
641 c_fwdignore(void *v
){ /* TODO v15 drop */
645 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
651 c_unfwdretain(void *v
){ /* TODO v15 drop */
655 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
661 c_unfwdignore(void *v
){ /* TODO v15 drop */
665 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
671 n_ignore_new(bool_t isauto
){
672 struct n_ignore
*self
;
675 self
= isauto
? n_autorec_calloc(1, sizeof *self
) : n_calloc(1,sizeof *self
);
676 self
->i_auto
= isauto
;
682 n_ignore_del(struct n_ignore
*self
){
684 a_ignore_del_allof(self
, TRU1
);
685 a_ignore_del_allof(self
, FAL0
);
692 n_ignore_is_any(struct n_ignore
const *self
){
696 self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
);
697 rv
= (self
!= NULL
&&
698 (self
->i_retain
.it_count
!= 0 || self
->i_retain
.it_all
||
699 self
->i_ignore
.it_count
!= 0 || self
->i_ignore
.it_all
));
705 n_ignore_insert(struct n_ignore
*self
, bool_t retain
,
706 char const *dat
, size_t len
){
708 struct a_ignore_re
*irp
;
711 struct a_ignore_field
*ifp
;
712 struct a_ignore_type
*itp
;
716 retain
= !!retain
; /* Make it true bool, TRUM1 has special _lookup meaning */
718 self
= a_ignore_resolve_self(self
, TRU1
);
723 /* Request to ignore or retain _anything_? That is special-treated */
724 if(len
== 1 && dat
[0] == '*'){
725 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
730 a_ignore_del_allof(self
, retain
);
736 /* Check for regular expression or valid fieldname */
738 if(!(isre
= n_is_maybe_regex_buf(dat
, len
)))
744 for(i
= 0; i
< len
; ++i
){
746 if(!fieldnamechar(c
))
752 if(a_ignore_lookup(self
, retain
, dat
, len
) == (retain
? TRU1
: TRUM1
))
755 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
757 if(itp
->it_count
== UI32_MAX
){
758 n_err(_("Header selection size limit reached, cannot insert: %.*s\n"),
759 (int)n_MIN(len
, SI32_MAX
), dat
);
767 struct a_ignore_re
*x
;
771 i
= n_VSTRUCT_SIZEOF(struct a_ignore_re
, ir_input
) + ++len
;
772 irp
= self
->i_auto
? n_autorec_alloc(i
) : n_alloc(i
);
773 memcpy(irp
->ir_input
, dat
, --len
);
774 irp
->ir_input
[len
] = '\0';
776 if((s
= regcomp(&irp
->ir_regex
, irp
->ir_input
,
777 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
778 n_err(_("Invalid regular expression: %s: %s\n"),
779 n_shexp_quote_cp(irp
->ir_input
, FAL0
),
780 n_regex_err_to_doc(&irp
->ir_regex
, s
));
788 if((x
= itp
->it_re_tail
) != NULL
)
792 itp
->it_re_tail
= irp
;
794 #endif /* HAVE_REGEX */
799 i
= n_VSTRUCT_SIZEOF(struct a_ignore_field
, if_field
) + len
+ 1;
800 ifp
= self
->i_auto
? n_autorec_alloc(i
) : n_alloc(i
);
801 memcpy(ifp
->if_field
, dat
, len
);
802 ifp
->if_field
[len
] = '\0';
803 hi
= n_torek_ihashn(dat
, len
) % n_NELEM(itp
->it_ht
);
804 ifp
->if_next
= itp
->it_ht
[hi
];
805 itp
->it_ht
[hi
] = ifp
;
814 n_ignore_lookup(struct n_ignore
const *self
, char const *dat
, size_t len
){
818 if(self
== n_IGNORE_ALL
)
821 (self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
)) == NULL
)
823 else if(self
->i_retain
.it_all
)
825 else if(self
->i_retain
.it_count
== 0 && self
->i_ignore
.it_all
)
828 rv
= a_ignore_lookup(self
, TRUM1
, dat
, len
);