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