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>.
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
;
389 ip
= a_ignore_resolve_self(ip
, rv
= (*list
!= NULL
));
390 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
392 if(itp
->it_count
== 0 && !itp
->it_all
)
393 n_err(_("No fields currently being %s\n"),
394 (retain
? _("retained") : _("ignored")));
396 while((cp
= *list
++) != NULL
)
397 if(cp
[0] == '*' && cp
[1] == '\0')
398 a_ignore_del_allof(ip
, retain
);
399 else if(!a_ignore__delone(ip
, retain
, cp
)){
400 n_err(_("Field not %s: %s\n"),
401 (retain
? _("retained") : _("ignored")), cp
);
409 a_ignore__delone(struct n_ignore
*ip
, bool_t retain
, char const *field
){
410 struct a_ignore_type
*itp
;
413 itp
= retain
? &ip
->i_retain
: &ip
->i_ignore
;
416 if(n_is_maybe_regex(field
)){
417 struct a_ignore_re
**lirp
, *irp
;
419 for(irp
= *(lirp
= &itp
->it_re
); irp
!= NULL
;
420 lirp
= &irp
->ir_next
, irp
= irp
->ir_next
)
421 if(!strcmp(field
, irp
->ir_input
)){
422 *lirp
= irp
->ir_next
;
423 if(irp
== itp
->it_re_tail
)
424 itp
->it_re_tail
= irp
->ir_next
;
426 regfree(&irp
->ir_regex
);
433 #endif /* HAVE_REGEX */
435 struct a_ignore_field
**ifpp
, *ifp
;
438 hi
= n_torek_ihashn(field
, UIZ_MAX
) % n_NELEM(itp
->it_ht
);
440 for(ifp
= *(ifpp
= &itp
->it_ht
[hi
]); ifp
!= NULL
;
441 ifpp
= &ifp
->if_next
, ifp
= ifp
->if_next
)
442 if(!asccasecmp(ifp
->if_field
, field
)){
443 *ifpp
= ifp
->if_next
;
458 c_headerpick(void *vp
){
460 struct a_ignore_bltin_map
const *ibmp
;
468 /* Without arguments, show all settings of all contexts */
471 for(ibmp
= &a_ignore_bltin_map
[0];
472 ibmp
<= &a_ignore_bltin_map
[n__IGNORE_MAX
]; ++ibmp
){
473 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
474 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
479 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
480 n_err(_("`headerpick': invalid context: %s\n"), *argv
);
485 /* With only <context>, show all settings of it */
488 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, TRU1
);
489 rv
|= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, FAL0
);
493 if(is_asccaseprefix(*argv
, "retain"))
495 else if(is_asccaseprefix(*argv
, "ignore"))
498 n_err(_("`headerpick': invalid type (retain, ignore): %s\n"), *argv
);
503 /* With only <context> and <type>, show its settings */
505 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
509 rv
= !a_ignore_addcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
516 c_unheaderpick(void *vp
){
518 struct a_ignore_bltin_map
const *ibmp
;
526 if((ibmp
= a_ignore_resolve_bltin(*argv
)) == NULL
){
527 n_err(_("`unheaderpick': invalid context: %s\n"), *argv
);
532 if(is_asccaseprefix(*argv
, "retain"))
534 else if(is_asccaseprefix(*argv
, "ignore"))
537 n_err(_("`unheaderpick': invalid type (retain, ignore): %s\n"), *argv
);
542 rv
= !a_ignore_delcmd_mux(ibmp
->ibm_ip
, argv
, retain
);
553 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, vp
, TRU1
);
563 rv
= !a_ignore_addcmd_mux(n_IGNORE_TYPE
, vp
, FAL0
);
569 c_unretain(void *vp
){
573 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, vp
, TRU1
);
579 c_unignore(void *vp
){
583 rv
= !a_ignore_delcmd_mux(n_IGNORE_TYPE
, vp
, FAL0
);
589 c_saveretain(void *v
){ /* TODO v15 drop */
593 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
599 c_saveignore(void *v
){ /* TODO v15 drop */
603 rv
= !a_ignore_addcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
609 c_unsaveretain(void *v
){ /* TODO v15 drop */
613 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, TRU1
);
619 c_unsaveignore(void *v
){ /* TODO v15 drop */
623 rv
= !a_ignore_delcmd_mux(n_IGNORE_SAVE
, v
, FAL0
);
629 c_fwdretain(void *v
){ /* TODO v15 drop */
633 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
639 c_fwdignore(void *v
){ /* TODO v15 drop */
643 rv
= !a_ignore_addcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
649 c_unfwdretain(void *v
){ /* TODO v15 drop */
653 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, TRU1
);
659 c_unfwdignore(void *v
){ /* TODO v15 drop */
663 rv
= !a_ignore_delcmd_mux(n_IGNORE_FWD
, v
, FAL0
);
669 n_ignore_new(bool_t isauto
){
670 struct n_ignore
*self
;
673 self
= isauto
? n_autorec_calloc(1, sizeof *self
) : n_calloc(1,sizeof *self
);
674 self
->i_auto
= isauto
;
680 n_ignore_del(struct n_ignore
*self
){
682 a_ignore_del_allof(self
, TRU1
);
683 a_ignore_del_allof(self
, FAL0
);
690 n_ignore_is_any(struct n_ignore
const *self
){
694 self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
);
695 rv
= (self
!= NULL
&&
696 (self
->i_retain
.it_count
!= 0 || self
->i_retain
.it_all
||
697 self
->i_ignore
.it_count
!= 0 || self
->i_ignore
.it_all
));
703 n_ignore_insert(struct n_ignore
*self
, bool_t retain
,
704 char const *dat
, size_t len
){
706 struct a_ignore_re
*irp
;
709 struct a_ignore_field
*ifp
;
710 struct a_ignore_type
*itp
;
714 retain
= !!retain
; /* Make it true bool, TRUM1 has special _lookup meaning */
716 self
= a_ignore_resolve_self(self
, TRU1
);
721 /* Request to ignore or retain _anything_? That is special-treated */
722 if(len
== 1 && dat
[0] == '*'){
723 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
728 a_ignore_del_allof(self
, retain
);
734 /* Check for regular expression or valid fieldname */
736 if(!(isre
= n_is_maybe_regex_buf(dat
, len
)))
742 for(i
= 0; i
< len
; ++i
){
744 if(!fieldnamechar(c
))
750 if(a_ignore_lookup(self
, retain
, dat
, len
) == (retain
? TRU1
: TRUM1
))
753 itp
= retain
? &self
->i_retain
: &self
->i_ignore
;
755 if(itp
->it_count
== UI32_MAX
){
756 n_err(_("Header selection size limit reached, cannot insert: %.*s\n"),
757 (int)n_MIN(len
, SI32_MAX
), dat
);
765 struct a_ignore_re
*x
;
769 i
= n_VSTRUCT_SIZEOF(struct a_ignore_re
, ir_input
) + ++len
;
770 irp
= self
->i_auto
? n_autorec_alloc(i
) : n_alloc(i
);
771 memcpy(irp
->ir_input
, dat
, --len
);
772 irp
->ir_input
[len
] = '\0';
774 if((s
= regcomp(&irp
->ir_regex
, irp
->ir_input
,
775 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
776 n_err(_("Invalid regular expression: %s: %s\n"),
777 n_shexp_quote_cp(irp
->ir_input
, FAL0
),
778 n_regex_err_to_doc(NULL
, s
));
786 if((x
= itp
->it_re_tail
) != NULL
)
790 itp
->it_re_tail
= irp
;
792 #endif /* HAVE_REGEX */
797 i
= n_VSTRUCT_SIZEOF(struct a_ignore_field
, if_field
) + len
+ 1;
798 ifp
= self
->i_auto
? n_autorec_alloc(i
) : n_alloc(i
);
799 memcpy(ifp
->if_field
, dat
, len
);
800 ifp
->if_field
[len
] = '\0';
801 hi
= n_torek_ihashn(dat
, len
) % n_NELEM(itp
->it_ht
);
802 ifp
->if_next
= itp
->it_ht
[hi
];
803 itp
->it_ht
[hi
] = ifp
;
812 n_ignore_lookup(struct n_ignore
const *self
, char const *dat
, size_t len
){
816 if(self
== n_IGNORE_ALL
)
819 (self
= a_ignore_resolve_self(n_UNCONST(self
), FAL0
)) == NULL
)
821 else if(self
->i_retain
.it_all
)
823 else if(self
->i_retain
.it_count
== 0 && self
->i_ignore
.it_all
)
826 rv
= a_ignore_lookup(self
, TRUM1
, dat
, len
);