1 /**
2  * @file
3  * Handling for email address groups
4  *
5  * @authors
6  * Copyright (C) 2006 Thomas Roessler <roessler@does-not-exist.org>
7  * Copyright (C) 2009 Rocco Rutte <pdmef@gmx.net>
8  * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
9  *
10  * @copyright
11  * This program is free software: you can redistribute it and/or modify it under
12  * the terms of the GNU General Public License as published by the Free Software
13  * Foundation, either version 2 of the License, or (at your option) any later
14  * version.
15  *
16  * This program is distributed in the hope that it will be useful, but WITHOUT
17  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19  * details.
20  *
21  * You should have received a copy of the GNU General Public License along with
22  * this program.  If not, see <http://www.gnu.org/licenses/>.
23  */
24 
25 /**
26  * @page addr_group Address groups
27  *
28  * Handling for email address groups
29  */
30 
31 #include "config.h"
32 #include <assert.h>
33 #include <stdbool.h>
34 #include <stdlib.h>
35 #include "group.h"
36 #include "address.h"
37 
38 static struct HashTable *Groups = NULL;
39 
40 /**
41  * group_free - Free an Address Group
42  * @param ptr Group to free
43  */
group_free(struct Group ** ptr)44 void group_free(struct Group **ptr)
45 {
46   if (!ptr || !*ptr)
47     return;
48 
49   struct Group *g = *ptr;
50 
51   mutt_addrlist_clear(&g->al);
52   mutt_regexlist_free(&g->rs);
53   FREE(&g->name);
54 
55   FREE(ptr);
56 }
57 
58 /**
59  * group_new - Create a new Address Group
60  * @param  pat Pattern
61  * @retval ptr New Address Group
62  *
63  * @note The pattern will be copied
64  */
group_new(const char * pat)65 struct Group *group_new(const char *pat)
66 {
67   struct Group *g = mutt_mem_calloc(1, sizeof(struct Group));
68 
69   g->name = mutt_str_dup(pat);
70   STAILQ_INIT(&g->rs);
71   TAILQ_INIT(&g->al);
72 
73   return g;
74 }
75 
76 /**
77  * group_hash_free - Free our hash table data - Implements ::hash_hdata_free_t - @ingroup hash_hdata_free_api
78  */
group_hash_free(int type,void * obj,intptr_t data)79 void group_hash_free(int type, void *obj, intptr_t data)
80 {
81   struct Group *g = obj;
82   group_free(&g);
83 }
84 
85 /**
86  * mutt_grouplist_init - Initialize the GroupList singleton
87  *
88  * This is called once from init.c when initializing the global structures.
89  */
mutt_grouplist_init(void)90 void mutt_grouplist_init(void)
91 {
92   Groups = mutt_hash_new(1031, MUTT_HASH_NO_FLAGS);
93 
94   mutt_hash_set_destructor(Groups, group_hash_free, 0);
95 }
96 
97 /**
98  * mutt_grouplist_free - Free GroupList singleton resource
99  *
100  * This is called once from init.c when deinitializing the global resources.
101  */
mutt_grouplist_free(void)102 void mutt_grouplist_free(void)
103 {
104   mutt_hash_free(&Groups);
105 }
106 
107 /**
108  * mutt_pattern_group - Match a pattern to a Group
109  * @param pat Pattern to match
110  * @retval ptr Matching Group, or new Group (if no match)
111  */
mutt_pattern_group(const char * pat)112 struct Group *mutt_pattern_group(const char *pat)
113 {
114   if (!pat)
115     return NULL;
116 
117   struct Group *g = mutt_hash_find(Groups, pat);
118   if (!g)
119   {
120     mutt_debug(LL_DEBUG2, "Creating group %s\n", pat);
121     g = group_new(pat);
122     mutt_hash_insert(Groups, g->name, g);
123   }
124 
125   return g;
126 }
127 
128 /**
129  * group_remove - Remove a Group from the Hash Table
130  * @param g Group to remove
131  */
group_remove(struct Group * g)132 static void group_remove(struct Group *g)
133 {
134   if (!g)
135     return;
136   mutt_hash_delete(Groups, g->name, g);
137   mutt_addrlist_clear(&g->al);
138   mutt_regexlist_free(&g->rs);
139   FREE(&g->name);
140   FREE(&g);
141 }
142 
143 /**
144  * mutt_grouplist_clear - Clear a GroupList
145  * @param gl GroupList to clear
146  */
mutt_grouplist_clear(struct GroupList * gl)147 void mutt_grouplist_clear(struct GroupList *gl)
148 {
149   if (!gl)
150     return;
151 
152   struct GroupNode *np = STAILQ_FIRST(gl);
153   struct GroupNode *next = NULL;
154   while (np)
155   {
156     group_remove(np->group);
157     next = STAILQ_NEXT(np, entries);
158     FREE(&np);
159     np = next;
160   }
161   STAILQ_INIT(gl);
162 }
163 
164 /**
165  * empty_group - Is a Group empty?
166  * @param g Group to test
167  * @retval true The Group is empty
168  */
empty_group(struct Group * g)169 static bool empty_group(struct Group *g)
170 {
171   if (!g)
172     return true;
173   return TAILQ_EMPTY(&g->al) && STAILQ_EMPTY(&g->rs);
174 }
175 
176 /**
177  * mutt_grouplist_add - Add a Group to a GroupList
178  * @param gl    GroupList to add to
179  * @param group Group to add
180  */
mutt_grouplist_add(struct GroupList * gl,struct Group * group)181 void mutt_grouplist_add(struct GroupList *gl, struct Group *group)
182 {
183   if (!gl || !group)
184     return;
185 
186   struct GroupNode *np = NULL;
187   STAILQ_FOREACH(np, gl, entries)
188   {
189     if (np->group == group)
190       return;
191   }
192   np = mutt_mem_calloc(1, sizeof(struct GroupNode));
193   np->group = group;
194   STAILQ_INSERT_TAIL(gl, np, entries);
195 }
196 
197 /**
198  * mutt_grouplist_destroy - Free a GroupList
199  * @param gl GroupList to free
200  */
mutt_grouplist_destroy(struct GroupList * gl)201 void mutt_grouplist_destroy(struct GroupList *gl)
202 {
203   if (!gl)
204     return;
205 
206   struct GroupNode *np = STAILQ_FIRST(gl);
207   struct GroupNode *next = NULL;
208   while (np)
209   {
210     next = STAILQ_NEXT(np, entries);
211     FREE(&np);
212     np = next;
213   }
214   STAILQ_INIT(gl);
215 }
216 
217 /**
218  * group_add_addrlist - Add an Address List to a Group
219  * @param g  Group to add to
220  * @param al Address List
221  */
group_add_addrlist(struct Group * g,const struct AddressList * al)222 static void group_add_addrlist(struct Group *g, const struct AddressList *al)
223 {
224   if (!g || !al)
225     return;
226 
227   struct AddressList al_new = TAILQ_HEAD_INITIALIZER(al_new);
228   mutt_addrlist_copy(&al_new, al, false);
229   mutt_addrlist_remove_xrefs(&g->al, &al_new);
230   struct Address *a = NULL, *tmp = NULL;
231   TAILQ_FOREACH_SAFE(a, &al_new, entries, tmp)
232   {
233     TAILQ_REMOVE(&al_new, a, entries);
234     mutt_addrlist_append(&g->al, a);
235   }
236   assert(TAILQ_EMPTY(&al_new));
237 }
238 
239 /**
240  * group_add_regex - Add a Regex to a Group
241  * @param g     Group to add to
242  * @param s     Regex string to add
243  * @param flags Flags, e.g. REG_ICASE
244  * @param err   Buffer for error message
245  * @retval  0 Success
246  * @retval -1 Error
247  */
group_add_regex(struct Group * g,const char * s,uint16_t flags,struct Buffer * err)248 static int group_add_regex(struct Group *g, const char *s, uint16_t flags, struct Buffer *err)
249 {
250   return mutt_regexlist_add(&g->rs, s, flags, err);
251 }
252 
253 /**
254  * group_remove_regex - Remove a Regex from a Group
255  * @param g Group to modify
256  * @param s Regex string to match
257  * @retval  0 Success
258  * @retval -1 Error
259  */
group_remove_regex(struct Group * g,const char * s)260 static int group_remove_regex(struct Group *g, const char *s)
261 {
262   return mutt_regexlist_remove(&g->rs, s);
263 }
264 
265 /**
266  * mutt_grouplist_add_addrlist - Add Address list to a GroupList
267  * @param gl GroupList to add to
268  * @param al Address list to add
269  */
mutt_grouplist_add_addrlist(struct GroupList * gl,struct AddressList * al)270 void mutt_grouplist_add_addrlist(struct GroupList *gl, struct AddressList *al)
271 {
272   if (!gl || !al)
273     return;
274 
275   struct GroupNode *np = NULL;
276   STAILQ_FOREACH(np, gl, entries)
277   {
278     group_add_addrlist(np->group, al);
279   }
280 }
281 
282 /**
283  * mutt_grouplist_remove_addrlist - Remove an AddressList from a GroupList
284  * @param gl GroupList to remove from
285  * @param al AddressList to remove
286  * @retval  0 Success
287  * @retval -1 Error
288  */
mutt_grouplist_remove_addrlist(struct GroupList * gl,struct AddressList * al)289 int mutt_grouplist_remove_addrlist(struct GroupList *gl, struct AddressList *al)
290 {
291   if (!gl || !al)
292     return -1;
293 
294   struct GroupNode *gnp = NULL;
295   STAILQ_FOREACH(gnp, gl, entries)
296   {
297     struct Address *a = NULL;
298     TAILQ_FOREACH(a, al, entries)
299     {
300       mutt_addrlist_remove(&gnp->group->al, a->mailbox);
301     }
302     if (empty_group(gnp->group))
303     {
304       group_remove(gnp->group);
305     }
306   }
307 
308   return 0;
309 }
310 
311 /**
312  * mutt_grouplist_add_regex - Add matching Addresses to a GroupList
313  * @param gl    GroupList to add to
314  * @param s     Address to match
315  * @param flags Flags, e.g. REG_ICASE
316  * @param err   Buffer for error message
317  * @retval  0 Success
318  * @retval -1 Error
319  */
mutt_grouplist_add_regex(struct GroupList * gl,const char * s,uint16_t flags,struct Buffer * err)320 int mutt_grouplist_add_regex(struct GroupList *gl, const char *s,
321                              uint16_t flags, struct Buffer *err)
322 {
323   if (!gl || !s)
324     return -1;
325 
326   int rc = 0;
327 
328   struct GroupNode *np = NULL;
329   STAILQ_FOREACH(np, gl, entries)
330   {
331     rc = group_add_regex(np->group, s, flags, err);
332     if (rc)
333       return rc;
334   }
335   return rc;
336 }
337 
338 /**
339  * mutt_grouplist_remove_regex - Remove matching addresses from a GroupList
340  * @param gl GroupList to remove from
341  * @param s  Address to match
342  * @retval  0 Success
343  * @retval -1 Error
344  */
mutt_grouplist_remove_regex(struct GroupList * gl,const char * s)345 int mutt_grouplist_remove_regex(struct GroupList *gl, const char *s)
346 {
347   if (!gl || !s)
348     return -1;
349 
350   int rc = 0;
351   struct GroupNode *np = NULL;
352   STAILQ_FOREACH(np, gl, entries)
353   {
354     rc = group_remove_regex(np->group, s);
355     if (empty_group(np->group))
356       group_remove(np->group);
357     if (rc)
358       return rc;
359   }
360   return rc;
361 }
362 
363 /**
364  * mutt_group_match - Does a string match an entry in a Group?
365  * @param g Group to match against
366  * @param s String to match
367  * @retval true There's a match
368  */
mutt_group_match(struct Group * g,const char * s)369 bool mutt_group_match(struct Group *g, const char *s)
370 {
371   if (!g || !s)
372     return false;
373 
374   if (mutt_regexlist_match(&g->rs, s))
375     return true;
376   struct Address *a = NULL;
377   TAILQ_FOREACH(a, &g->al, entries)
378   {
379     if (a->mailbox && mutt_istr_equal(s, a->mailbox))
380       return true;
381   }
382 
383   return false;
384 }
385