collect(): ~[FfMmUu]: should default to the "dot" (Andrew Gee)
[s-mailx.git] / nam-a-grp.c
blobd5e9c9b74a97333c5194c6698ec4dff605d55b74
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Name lists, alternates and groups: aliases, mailing lists, shortcuts.
3 *@ TODO Dynamic hashmaps; names and (these) groups have _nothing_ in common!
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE nam_a_grp
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 enum a_nag_type{
44 /* Main types */
45 a_NAG_T_ALTERNATES = 1,
46 a_NAG_T_COMMANDALIAS,
47 a_NAG_T_ALIAS,
48 a_NAG_T_MLIST,
49 a_NAG_T_SHORTCUT,
50 a_NAG_T_CHARSETALIAS,
51 a_NAG_T_FILETYPE,
52 a_NAG_T_MASK = 0x1F,
54 /* Subtype bits and flags */
55 a_NAG_T_SUBSCRIBE = 1u<<6,
56 a_NAG_T_REGEX = 1u<<7,
58 /* Extended type mask to be able to reflect what we really have; i.e., mlist
59 * can have a_NAG_T_REGEX if they are subscribed or not, but `mlsubscribe'
60 * should print out only a_NAG_T_MLIST which have the a_NAG_T_SUBSCRIBE
61 * attribute set */
62 a_NAG_T_PRINT_MASK = a_NAG_T_MASK | a_NAG_T_SUBSCRIBE
64 n_CTA(a_NAG_T_MASK >= a_NAG_T_FILETYPE, "Mask does not cover necessary bits");
66 struct a_nag_group{
67 struct a_nag_group *ng_next;
68 ui32_t ng_subclass_off; /* of "subclass" in .ng_id (if any) */
69 ui16_t ng_id_len_sub; /* length of .ng_id: _subclass_off - this */
70 ui8_t ng_type; /* enum a_nag_type */
71 /* Identifying name, of variable size. Dependent on actual "subtype" more
72 * data follows thereafter, but note this is always used (i.e., for regular
73 * expression entries this is still set to the plain string) */
74 char ng_id[n_VFIELD_SIZE(1)];
76 #define a_NAG_GP_TO_SUBCLASS(X,G) \
77 do{\
78 union a_nag_group_subclass {void *gs_vp; char *gs_cp;} a__gs__;\
79 a__gs__.gs_cp = &((char*)n_UNCONST(G))[(G)->ng_subclass_off];\
80 (X) = a__gs__.gs_vp;\
81 }while(0)
83 struct a_nag_grp_names_head{
84 struct a_nag_grp_names *ngnh_head;
87 struct a_nag_grp_names{
88 struct a_nag_grp_names *ngn_next;
89 char ngn_id[n_VFIELD_SIZE(0)];
92 #ifdef HAVE_REGEX
93 struct a_nag_grp_regex{
94 struct a_nag_grp_regex *ngr_last;
95 struct a_nag_grp_regex *ngr_next;
96 struct a_nag_group *ngr_mygroup; /* xxx because lists use grp_regex*! ?? */
97 size_t ngr_hits; /* Number of times this group matched */
98 regex_t ngr_regex;
100 #endif
102 struct a_nag_cmd_alias{
103 struct str nca_expand;
106 struct a_nag_file_type{
107 struct str nft_load;
108 struct str nft_save;
111 struct a_nag_group_lookup{
112 struct a_nag_group **ngl_htable;
113 struct a_nag_group **ngl_slot;
114 struct a_nag_group *ngl_slot_last;
115 struct a_nag_group *ngl_group;
118 static struct n_file_type const a_nag_OBSOLETE_xz = { /* TODO v15 compat */
119 "xz", 2, "xz -cd", sizeof("xz -cd") -1, "xz -cz", sizeof("xz -cz") -1
120 }, a_nag_OBSOLETE_gz = {
121 "gz", 2, "gzip -cd", sizeof("gzip -cd") -1, "gzip -cz", sizeof("gzip -cz") -1
122 }, a_nag_OBSOLETE_bz2 = {
123 "bz2", 3, "bzip2 -cd", sizeof("bzip2 -cd") -1,
124 "bzip2 -cz", sizeof("bzip2 -cz") -1
127 /* `alternates' */
128 static struct a_nag_group *a_nag_alternates_heads[HSHSIZE];
130 /* `commandalias' */
131 static struct a_nag_group *a_nag_commandalias_heads[HSHSIZE];
133 /* `alias' */
134 static struct a_nag_group *a_nag_alias_heads[HSHSIZE];
136 /* `mlist', `mlsubscribe'. Anything is stored in the hashmap.. */
137 static struct a_nag_group *a_nag_mlist_heads[HSHSIZE];
139 /* ..but entries which have a_NAG_T_REGEX set are false lookups and will really
140 * be accessed via sequential lists instead, which are type-specific for better
141 * performance, but also to make it possible to have ".*@xy.org" as a mlist
142 * and "(one|two)@xy.org" as a mlsubscription.
143 * These lists use a bit of QOS optimization in that a matching group will
144 * become relinked as the new list head if its hit count is
145 * (>= ((xy_hits / _xy_size) >> 2))
146 * Note that the hit counts only count currently linked in nodes.. */
147 #ifdef HAVE_REGEX
148 static struct a_nag_grp_regex *a_nag_mlist_regex;
149 static struct a_nag_grp_regex *a_nag_mlsub_regex;
150 static size_t a_nag_mlist_size;
151 static size_t a_nag_mlist_hits;
152 static size_t a_nag_mlsub_size;
153 static size_t a_nag_mlsub_hits;
154 #endif
156 /* `shortcut' */
157 static struct a_nag_group *a_nag_shortcut_heads[HSHSIZE];
159 /* `charsetalias' */
160 static struct a_nag_group *a_nag_charsetalias_heads[HSHSIZE];
162 /* `filetype' */
163 static struct a_nag_group *a_nag_filetype_heads[HSHSIZE];
165 /* Same name, while taking care for *allnet*? */
166 static bool_t a_nag_is_same_name(char const *n1, char const *n2);
168 /* Mark all nodes with the given name */
169 static struct name *a_nag_namelist_mark_name(struct name *np, char const *name);
171 /* Grab a single name (liberal name) */
172 static char const *a_nag_yankname(char const *ap, char *wbuf,
173 char const *separators, int keepcomms);
175 /* Extraction multiplexer that splits an input line to names */
176 static struct name *a_nag_extract1(char const *line, enum gfield ntype,
177 char const *separators, bool_t keepcomms);
179 /* Recursively expand a alias name. Limit expansion to some fixed level.
180 * Direct recursion is not expanded for convenience */
181 static struct name *a_nag_gexpand(size_t level, struct name *nlist,
182 struct a_nag_group *ngp, bool_t metoo, int ntype);
184 /* elide() helper */
185 static int a_nag_elide_qsort(void const *s1, void const *s2);
187 /* Lookup a group, return it or NULL, fill in glp anyway */
188 static struct a_nag_group *a_nag_group_lookup(enum a_nag_type nt,
189 struct a_nag_group_lookup *nglp, char const *id);
191 /* Easier-to-use wrapper around _group_lookup() */
192 static struct a_nag_group *a_nag_group_find(enum a_nag_type nt, char const *id);
194 /* Iteration: go to the first group, which also inits the iterator. A valid
195 * iterator can be stepped via _next(). A NULL return means no (more) groups
196 * to be iterated exist, in which case only nglp->ngl_group is set (NULL) */
197 static struct a_nag_group *a_nag_group_go_first(enum a_nag_type nt,
198 struct a_nag_group_lookup *nglp);
199 static struct a_nag_group *a_nag_group_go_next(struct a_nag_group_lookup *nglp);
201 /* Fetch the group id, create it as necessary, fail with NULL if impossible */
202 static struct a_nag_group *a_nag_group_fetch(enum a_nag_type nt, char const *id,
203 size_t addsz);
205 /* "Intelligent" delete which handles a "*" id, too;
206 * returns a true boolean if a group was deleted, and always succeeds for "*" */
207 static bool_t a_nag_group_del(enum a_nag_type nt, char const *id);
209 static struct a_nag_group *a_nag__group_del(struct a_nag_group_lookup *nglp);
210 static void a_nag__names_del(struct a_nag_group *ngp);
212 /* Print all groups of the given type, alphasorted, or store in `vput' varname:
213 * only in this mode it can return failure */
214 static bool_t a_nag_group_print_all(enum a_nag_type nt,
215 char const *varname);
217 static int a_nag__group_print_qsorter(void const *a, void const *b);
219 /* Really print a group, actually. Or store in vputsp, if set.
220 * Return number of written lines */
221 static size_t a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
222 struct n_string *vputsp);
224 /* Multiplexers for list and subscribe commands */
225 static int a_nag_mlmux(enum a_nag_type nt, char const **argv);
226 static int a_nag_unmlmux(enum a_nag_type nt, char const **argv);
228 /* Relinkers for the sequential match lists */
229 #ifdef HAVE_REGEX
230 static void a_nag_mlmux_linkin(struct a_nag_group *ngp);
231 static void a_nag_mlmux_linkout(struct a_nag_group *ngp);
232 # define a_NAG_MLMUX_LINKIN(GP) \
233 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkin(GP); while(0)
234 # define a_NAG_MLMUX_LINKOUT(GP) \
235 do if((GP)->ng_type & a_NAG_T_REGEX) a_nag_mlmux_linkout(GP); while(0)
236 #else
237 # define a_NAG_MLMUX_LINKIN(GP)
238 # define a_NAG_MLMUX_LINKOUT(GP)
239 #endif
241 static bool_t
242 a_nag_is_same_name(char const *n1, char const *n2){
243 bool_t rv;
244 char c1, c2, c1r, c2r;
245 NYD2_ENTER;
247 if(ok_blook(allnet)){
248 for(;; ++n1, ++n2){
249 c1 = *n1;
250 c1 = lowerconv(c1);
251 c1r = (c1 == '\0' || c1 == '@');
252 c2 = *n2;
253 c2 = lowerconv(c2);
254 c2r = (c2 == '\0' || c2 == '@');
256 if(c1r || c2r){
257 rv = (c1r == c2r);
258 break;
259 }else if(c1 != c2){
260 rv = FAL0;
261 break;
264 }else
265 rv = !asccasecmp(n1, n2);
266 NYD2_LEAVE;
267 return rv;
270 static struct name *
271 a_nag_namelist_mark_name(struct name *np, char const *name){
272 struct name *p;
273 NYD2_ENTER;
275 for(p = np; p != NULL; p = p->n_flink)
276 if(!(p->n_type & GDEL) && !(p->n_flags & (ui32_t)SI32_MIN) &&
277 a_nag_is_same_name(p->n_name, name))
278 p->n_flags |= (ui32_t)SI32_MIN;
279 NYD2_LEAVE;
280 return np;
283 static char const *
284 a_nag_yankname(char const *ap, char *wbuf, char const *separators,
285 int keepcomms)
287 char const *cp;
288 char *wp, c, inquote, lc, lastsp;
289 NYD_ENTER;
291 *(wp = wbuf) = '\0';
293 /* Skip over intermediate list trash, as in ".org> , <xy@zz.org>" */
294 for (c = *ap; blankchar(c) || c == ','; c = *++ap)
296 if (c == '\0') {
297 cp = NULL;
298 goto jleave;
301 /* Parse a full name: TODO RFC 5322
302 * - Keep everything in quotes, liberal handle *quoted-pair*s therein
303 * - Skip entire (nested) comments
304 * - In non-quote, non-comment, join adjacent space to a single SP
305 * - Understand separators only in non-quote, non-comment context,
306 * and only if not part of a *quoted-pair* (XXX too liberal) */
307 cp = ap;
308 for (inquote = lc = lastsp = 0;; lc = c, ++cp) {
309 c = *cp;
310 if (c == '\0')
311 break;
312 if (c == '\\')
313 goto jwpwc;
314 if (c == '"') {
315 if (lc != '\\')
316 inquote = !inquote;
317 #if 0 /* TODO when doing real RFC 5322 parsers - why have i done this? */
318 else
319 --wp;
320 #endif
321 goto jwpwc;
323 if (inquote || lc == '\\') {
324 jwpwc:
325 *wp++ = c;
326 lastsp = 0;
327 continue;
329 if (c == '(') {
330 ap = cp;
331 cp = skip_comment(cp + 1);
332 if (keepcomms)
333 while (ap < cp)
334 *wp++ = *ap++;
335 --cp;
336 lastsp = 0;
337 continue;
339 if (strchr(separators, c) != NULL)
340 break;
342 lc = lastsp;
343 lastsp = blankchar(c);
344 if (!lastsp || !lc)
345 *wp++ = c;
347 if (blankchar(lc))
348 --wp;
350 *wp = '\0';
351 jleave:
352 NYD_LEAVE;
353 return cp;
356 static struct name *
357 a_nag_extract1(char const *line, enum gfield ntype, char const *separators,
358 bool_t keepcomms)
360 struct name *topp, *np, *t;
361 char const *cp;
362 char *nbuf;
363 NYD_ENTER;
365 topp = NULL;
366 if (line == NULL || *line == '\0')
367 goto jleave;
369 np = NULL;
370 cp = line;
371 nbuf = n_alloc(strlen(line) +1);
372 while ((cp = a_nag_yankname(cp, nbuf, separators, keepcomms)) != NULL) {
373 t = nalloc(nbuf, ntype);
374 if (topp == NULL)
375 topp = t;
376 else
377 np->n_flink = t;
378 t->n_blink = np;
379 np = t;
381 n_free(nbuf);
382 jleave:
383 NYD_LEAVE;
384 return topp;
387 static struct name *
388 a_nag_gexpand(size_t level, struct name *nlist, struct a_nag_group *ngp,
389 bool_t metoo, int ntype){
390 struct a_nag_grp_names *ngnp;
391 struct name *nlist_tail;
392 char const *logname;
393 struct a_nag_grp_names_head *ngnhp;
394 NYD2_ENTER;
396 if(UICMP(z, level++, >, n_ALIAS_MAXEXP)){
397 n_err(_("Expanding alias to depth larger than %d\n"), n_ALIAS_MAXEXP);
398 goto jleave;
401 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
402 logname = ok_vlook(LOGNAME);
404 for(ngnp = ngnhp->ngnh_head; ngnp != NULL; ngnp = ngnp->ngn_next){
405 struct a_nag_group *xngp;
406 char *cp;
408 cp = ngnp->ngn_id;
410 if(!strcmp(cp, ngp->ng_id))
411 goto jas_is;
413 if((xngp = a_nag_group_find(a_NAG_T_ALIAS, cp)) != NULL){
414 /* For S-nail(1), the "alias" may *be* the sender in that a name maps
415 * to a full address specification; aliases cannot be empty */
416 struct a_nag_grp_names_head *xngnhp;
418 a_NAG_GP_TO_SUBCLASS(xngnhp, xngp);
420 assert(xngnhp->ngnh_head != NULL);
421 if(metoo || xngnhp->ngnh_head->ngn_next != NULL ||
422 !a_nag_is_same_name(cp, logname))
423 nlist = a_nag_gexpand(level, nlist, xngp, metoo, ntype);
424 continue;
427 /* Here we should allow to expand to itself if only person in alias */
428 jas_is:
429 if(metoo || ngnhp->ngnh_head->ngn_next == NULL ||
430 !a_nag_is_same_name(cp, logname)){
431 struct name *np;
433 np = nalloc(cp, ntype | GFULL);
434 if((nlist_tail = nlist) != NULL){
435 while(nlist_tail->n_flink != NULL)
436 nlist_tail = nlist_tail->n_flink;
437 nlist_tail->n_flink = np;
438 np->n_blink = nlist_tail;
439 }else
440 nlist = np;
443 jleave:
444 NYD2_LEAVE;
445 return nlist;
448 static int
449 a_nag_elide_qsort(void const *s1, void const *s2){
450 struct name const * const *np1, * const *np2;
451 int rv;
452 NYD2_ENTER;
454 np1 = s1;
455 np2 = s2;
456 rv = asccasecmp((*np1)->n_name, (*np2)->n_name);
457 NYD2_LEAVE;
458 return rv;
461 static struct a_nag_group *
462 a_nag_group_lookup(enum a_nag_type nt, struct a_nag_group_lookup *nglp,
463 char const *id){
464 char c1;
465 struct a_nag_group *lngp, *ngp;
466 bool_t icase;
467 NYD2_ENTER;
469 icase = FAL0;
471 /* C99 */{
472 ui32_t h;
473 struct a_nag_group **ngpa;
475 switch((nt &= a_NAG_T_MASK)){
476 case a_NAG_T_ALTERNATES:
477 ngpa = a_nag_alternates_heads;
478 icase = TRU1;
479 break;
480 default:
481 case a_NAG_T_COMMANDALIAS:
482 ngpa = a_nag_commandalias_heads;
483 break;
484 case a_NAG_T_ALIAS:
485 ngpa = a_nag_alias_heads;
486 break;
487 case a_NAG_T_MLIST:
488 ngpa = a_nag_mlist_heads;
489 icase = TRU1;
490 break;
491 case a_NAG_T_SHORTCUT:
492 ngpa = a_nag_shortcut_heads;
493 break;
494 case a_NAG_T_CHARSETALIAS:
495 ngpa = a_nag_charsetalias_heads;
496 icase = TRU1;
497 break;
498 case a_NAG_T_FILETYPE:
499 ngpa = a_nag_filetype_heads;
500 icase = TRU1;
501 break;
504 nglp->ngl_htable = ngpa;
505 h = icase ? n_torek_ihash(id) : n_torek_hash(id);
506 ngp = *(nglp->ngl_slot = &ngpa[h % HSHSIZE]);
509 lngp = NULL;
510 c1 = *id++;
512 if(icase){
513 c1 = lowerconv(c1);
514 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
515 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
516 !asccasecmp(&ngp->ng_id[1], id))
517 break;
518 }else{
519 for(; ngp != NULL; lngp = ngp, ngp = ngp->ng_next)
520 if((ngp->ng_type & a_NAG_T_MASK) == nt && *ngp->ng_id == c1 &&
521 !strcmp(&ngp->ng_id[1], id))
522 break;
525 nglp->ngl_slot_last = lngp;
526 nglp->ngl_group = ngp;
527 NYD2_LEAVE;
528 return ngp;
531 static struct a_nag_group *
532 a_nag_group_find(enum a_nag_type nt, char const *id){
533 struct a_nag_group_lookup ngl;
534 struct a_nag_group *ngp;
535 NYD2_ENTER;
537 ngp = a_nag_group_lookup(nt, &ngl, id);
538 NYD2_LEAVE;
539 return ngp;
542 static struct a_nag_group *
543 a_nag_group_go_first(enum a_nag_type nt, struct a_nag_group_lookup *nglp){
544 size_t i;
545 struct a_nag_group **ngpa, *ngp;
546 NYD2_ENTER;
548 switch((nt &= a_NAG_T_MASK)){
549 case a_NAG_T_ALTERNATES:
550 ngpa = a_nag_alternates_heads;
551 break;
552 default:
553 case a_NAG_T_COMMANDALIAS:
554 ngpa = a_nag_commandalias_heads;
555 break;
556 case a_NAG_T_ALIAS:
557 ngpa = a_nag_alias_heads;
558 break;
559 case a_NAG_T_MLIST:
560 ngpa = a_nag_mlist_heads;
561 break;
562 case a_NAG_T_SHORTCUT:
563 ngpa = a_nag_shortcut_heads;
564 break;
565 case a_NAG_T_CHARSETALIAS:
566 ngpa = a_nag_charsetalias_heads;
567 break;
568 case a_NAG_T_FILETYPE:
569 ngpa = a_nag_filetype_heads;
570 break;
573 nglp->ngl_htable = ngpa;
575 for(i = 0; i < HSHSIZE; ++ngpa, ++i)
576 if((ngp = *ngpa) != NULL){
577 nglp->ngl_slot = ngpa;
578 nglp->ngl_group = ngp;
579 goto jleave;
582 nglp->ngl_group = ngp = NULL;
583 jleave:
584 nglp->ngl_slot_last = NULL;
585 NYD2_LEAVE;
586 return ngp;
589 static struct a_nag_group *
590 a_nag_group_go_next(struct a_nag_group_lookup *nglp){
591 struct a_nag_group *ngp, **ngpa;
592 NYD2_ENTER;
594 if((ngp = nglp->ngl_group->ng_next) != NULL)
595 nglp->ngl_slot_last = nglp->ngl_group;
596 else{
597 nglp->ngl_slot_last = NULL;
598 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
599 if((ngp = *nglp->ngl_slot) != NULL)
600 break;
602 nglp->ngl_group = ngp;
603 NYD2_LEAVE;
604 return ngp;
607 static struct a_nag_group *
608 a_nag_group_fetch(enum a_nag_type nt, char const *id, size_t addsz){
609 struct a_nag_group_lookup ngl;
610 struct a_nag_group *ngp;
611 size_t l, i;
612 NYD2_ENTER;
614 if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL)
615 goto jleave;
617 l = strlen(id) +1;
618 if(UIZ_MAX - n_ALIGN(l) <=
619 n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id)))
620 goto jleave;
622 i = n_ALIGN(n_VSTRUCT_SIZEOF(struct a_nag_group, ng_id) + l);
623 switch(nt & a_NAG_T_MASK){
624 case a_NAG_T_ALTERNATES:
625 case a_NAG_T_SHORTCUT:
626 case a_NAG_T_CHARSETALIAS:
627 default:
628 break;
629 case a_NAG_T_COMMANDALIAS:
630 addsz += sizeof(struct a_nag_cmd_alias);
631 break;
632 case a_NAG_T_ALIAS:
633 addsz += sizeof(struct a_nag_grp_names_head);
634 break;
635 case a_NAG_T_MLIST:
636 #ifdef HAVE_REGEX
637 if(n_is_maybe_regex(id)){
638 addsz = sizeof(struct a_nag_grp_regex);
639 nt |= a_NAG_T_REGEX;
641 #endif
642 break;
643 case a_NAG_T_FILETYPE:
644 addsz += sizeof(struct a_nag_file_type);
645 break;
647 if(UIZ_MAX - i < addsz || UI32_MAX <= i || UI16_MAX < i - l)
648 goto jleave;
650 ngp = n_alloc(i + addsz);
651 memcpy(ngp->ng_id, id, l);
652 ngp->ng_subclass_off = (ui32_t)i;
653 ngp->ng_id_len_sub = (ui16_t)(i - --l);
654 ngp->ng_type = nt;
655 switch(nt & a_NAG_T_MASK){
656 case a_NAG_T_ALTERNATES:
657 case a_NAG_T_MLIST:
658 case a_NAG_T_CHARSETALIAS:
659 case a_NAG_T_FILETYPE:{
660 char *cp, c;
662 for(cp = ngp->ng_id; (c = *cp) != '\0'; ++cp)
663 *cp = lowerconv(c);
664 }break;
665 default:
666 break;
669 if((nt & a_NAG_T_MASK) == a_NAG_T_ALIAS){
670 struct a_nag_grp_names_head *ngnhp;
672 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
673 ngnhp->ngnh_head = NULL;
675 #ifdef HAVE_REGEX
676 else if(nt & a_NAG_T_REGEX){
677 int s;
678 struct a_nag_grp_regex *ngrp;
680 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
682 if((s = regcomp(&ngrp->ngr_regex, id,
683 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
684 n_err(_("Invalid regular expression: %s: %s\n"),
685 n_shexp_quote_cp(id, FAL0), n_regex_err_to_doc(NULL, s));
686 n_free(ngp);
687 ngp = NULL;
688 goto jleave;
690 ngrp->ngr_mygroup = ngp;
691 a_nag_mlmux_linkin(ngp);
693 #endif /* HAVE_REGEX */
695 ngp->ng_next = *ngl.ngl_slot;
696 *ngl.ngl_slot = ngp;
697 jleave:
698 NYD2_LEAVE;
699 return ngp;
702 static bool_t
703 a_nag_group_del(enum a_nag_type nt, char const *id){
704 struct a_nag_group_lookup ngl;
705 struct a_nag_group *ngp;
706 enum a_nag_type xnt;
707 NYD2_ENTER;
709 xnt = nt & a_NAG_T_MASK;
711 /* Delete 'em all? */
712 if(id[0] == '*' && id[1] == '\0'){
713 for(ngp = a_nag_group_go_first(nt, &ngl); ngp != NULL;)
714 ngp = ((ngp->ng_type & a_NAG_T_MASK) == xnt) ? a_nag__group_del(&ngl)
715 : a_nag_group_go_next(&ngl);
716 ngp = (struct a_nag_group*)TRU1;
717 }else if((ngp = a_nag_group_lookup(nt, &ngl, id)) != NULL){
718 if(ngp->ng_type & xnt)
719 a_nag__group_del(&ngl);
720 else
721 ngp = NULL;
723 NYD2_LEAVE;
724 return (ngp != NULL);
727 static struct a_nag_group *
728 a_nag__group_del(struct a_nag_group_lookup *nglp){
729 struct a_nag_group *x, *ngp;
730 NYD2_ENTER;
732 /* Overly complicated: link off this node, step ahead to next.. */
733 x = nglp->ngl_group;
734 if((ngp = nglp->ngl_slot_last) != NULL)
735 ngp = (ngp->ng_next = x->ng_next);
736 else{
737 nglp->ngl_slot_last = NULL;
738 ngp = (*nglp->ngl_slot = x->ng_next);
740 if(ngp == NULL){
741 struct a_nag_group **ngpa;
743 for(ngpa = &nglp->ngl_htable[HSHSIZE]; ++nglp->ngl_slot < ngpa;)
744 if((ngp = *nglp->ngl_slot) != NULL)
745 break;
748 nglp->ngl_group = ngp;
750 if((x->ng_type & a_NAG_T_MASK) == a_NAG_T_ALIAS)
751 a_nag__names_del(x);
752 #ifdef HAVE_REGEX
753 else if(x->ng_type & a_NAG_T_REGEX){
754 struct a_nag_grp_regex *ngrp;
756 a_NAG_GP_TO_SUBCLASS(ngrp, x);
758 regfree(&ngrp->ngr_regex);
759 a_nag_mlmux_linkout(x);
761 #endif
763 n_free(x);
764 NYD2_LEAVE;
765 return ngp;
768 static void
769 a_nag__names_del(struct a_nag_group *ngp){
770 struct a_nag_grp_names_head *ngnhp;
771 struct a_nag_grp_names *ngnp;
772 NYD2_ENTER;
774 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
776 for(ngnp = ngnhp->ngnh_head; ngnp != NULL;){
777 struct a_nag_grp_names *x;
779 x = ngnp;
780 ngnp = ngnp->ngn_next;
781 n_free(x);
783 NYD2_LEAVE;
786 static bool_t
787 a_nag_group_print_all(enum a_nag_type nt, char const *varname){
788 struct n_string s;
789 size_t lines;
790 FILE *fp;
791 char const **ida;
792 struct a_nag_group const *ngp;
793 ui32_t h, i;
794 struct a_nag_group **ngpa;
795 char const *tname;
796 enum a_nag_type xnt;
797 NYD_ENTER;
799 if(varname != NULL)
800 n_string_creat_auto(&s);
802 xnt = nt & a_NAG_T_PRINT_MASK;
804 switch(xnt & a_NAG_T_MASK){
805 case a_NAG_T_ALTERNATES:
806 tname = "alternates";
807 ngpa = a_nag_alternates_heads;
808 break;
809 default:
810 case a_NAG_T_COMMANDALIAS:
811 tname = "commandalias";
812 ngpa = a_nag_commandalias_heads;
813 break;
814 case a_NAG_T_ALIAS:
815 tname = "alias";
816 ngpa = a_nag_alias_heads;
817 break;
818 case a_NAG_T_MLIST:
819 tname = "mlist";
820 ngpa = a_nag_mlist_heads;
821 break;
822 case a_NAG_T_SHORTCUT:
823 tname = "shortcut";
824 ngpa = a_nag_shortcut_heads;
825 break;
826 case a_NAG_T_CHARSETALIAS:
827 tname = "charsetalias";
828 ngpa = a_nag_charsetalias_heads;
829 break;
830 case a_NAG_T_FILETYPE:
831 tname = "filetype";
832 ngpa = a_nag_filetype_heads;
833 break;
836 /* Count entries */
837 for(i = h = 0; h < HSHSIZE; ++h)
838 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
839 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
840 ++i;
841 if(i == 0){
842 if(varname == NULL)
843 fprintf(n_stdout, _("# no %s registered\n"), tname);
844 goto jleave;
846 ++i;
847 ida = n_autorec_alloc(i * sizeof *ida);
849 /* Create alpha sorted array of entries */
850 for(i = h = 0; h < HSHSIZE; ++h)
851 for(ngp = ngpa[h]; ngp != NULL; ngp = ngp->ng_next)
852 if((ngp->ng_type & a_NAG_T_PRINT_MASK) == xnt)
853 ida[i++] = ngp->ng_id;
854 if(i > 1)
855 qsort(ida, i, sizeof *ida, &a_nag__group_print_qsorter);
856 ida[i] = NULL;
858 if(varname != NULL)
859 fp = NULL;
860 else if((fp = Ftmp(NULL, "nagprint", OF_RDWR | OF_UNLINK | OF_REGISTER)
861 ) == NULL)
862 fp = n_stdout;
864 /* Create visual result */
865 lines = 0;
867 switch(xnt & a_NAG_T_MASK){
868 case a_NAG_T_ALTERNATES:
869 if(fp != NULL){
870 fputs(tname, fp);
871 lines = 1;
873 break;
874 default:
875 break;
878 for(i = 0; ida[i] != NULL; ++i)
879 lines += a_nag_group_print(a_nag_group_find(nt, ida[i]), fp, &s);
881 #ifdef HAVE_REGEX
882 if(varname == NULL && (nt & a_NAG_T_MASK) == a_NAG_T_MLIST){
883 if(nt & a_NAG_T_SUBSCRIBE)
884 i = (ui32_t)a_nag_mlsub_size, h = (ui32_t)a_nag_mlsub_hits;
885 else
886 i = (ui32_t)a_nag_mlist_size, h = (ui32_t)a_nag_mlist_hits;
888 if(i > 0 && (n_poption & n_PO_D_V)){
889 assert(fp != NULL);
890 fprintf(fp, _("# %s list regex(7) total: %u entries, %u hits\n"),
891 (nt & a_NAG_T_SUBSCRIBE ? _("Subscribed") : _("Non-subscribed")),
892 i, h);
893 ++lines;
896 #endif
898 switch(xnt & a_NAG_T_MASK){
899 case a_NAG_T_ALTERNATES:
900 if(fp != NULL){
901 putc('\n', fp);
902 assert(lines == 1);
904 break;
905 default:
906 break;
909 if(varname == NULL && fp != n_stdout){
910 assert(fp != NULL);
911 page_or_print(fp, lines);
912 Fclose(fp);
915 jleave:
916 if(varname != NULL){
917 tname = n_string_cp(&s);
918 if(n_var_vset(varname, (uintptr_t)tname))
919 varname = NULL;
920 else
921 n_pstate_err_no = n_ERR_NOTSUP;
923 NYD_LEAVE;
924 return (varname == NULL);
927 static int
928 a_nag__group_print_qsorter(void const *a, void const *b){
929 int rv;
930 NYD2_ENTER;
932 rv = strcmp(*(char**)n_UNCONST(a), *(char**)n_UNCONST(b));
933 NYD2_LEAVE;
934 return rv;
937 static size_t
938 a_nag_group_print(struct a_nag_group const *ngp, FILE *fo,
939 struct n_string *vputsp){
940 char const *cp;
941 size_t rv;
942 NYD2_ENTER;
944 rv = 1;
946 switch(ngp->ng_type & a_NAG_T_MASK){
947 case a_NAG_T_ALTERNATES:{
948 if(fo != NULL)
949 fprintf(fo, " %s", ngp->ng_id);
950 else{
951 if(vputsp->s_len > 0)
952 vputsp = n_string_push_c(vputsp, ' ');
953 /*vputsp =*/ n_string_push_cp(vputsp, ngp->ng_id);
955 rv = 0;
956 }break;
957 case a_NAG_T_COMMANDALIAS:{
958 struct a_nag_cmd_alias *ncap;
960 assert(fo != NULL); /* xxx no vput yet */
961 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
962 fprintf(fo, "commandalias %s %s\n",
963 n_shexp_quote_cp(ngp->ng_id, TRU1),
964 n_shexp_quote_cp(ncap->nca_expand.s, TRU1));
965 }break;
966 case a_NAG_T_ALIAS:{
967 struct a_nag_grp_names_head *ngnhp;
968 struct a_nag_grp_names *ngnp;
970 assert(fo != NULL); /* xxx no vput yet */
971 fprintf(fo, "alias %s ", ngp->ng_id);
973 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
974 if((ngnp = ngnhp->ngnh_head) != NULL) { /* xxx always 1+ entries */
976 struct a_nag_grp_names *x;
978 x = ngnp;
979 ngnp = ngnp->ngn_next;
980 fprintf(fo, " \"%s\"", string_quote(x->ngn_id)); /* TODO shexp */
981 }while(ngnp != NULL);
983 putc('\n', fo);
984 }break;
985 case a_NAG_T_MLIST:
986 assert(fo != NULL); /* xxx no vput yet */
987 #ifdef HAVE_REGEX
988 if((ngp->ng_type & a_NAG_T_REGEX) && (n_poption & n_PO_D_V)){
989 size_t i;
990 struct a_nag_grp_regex *lp, *ngrp;
992 lp = (ngp->ng_type & a_NAG_T_SUBSCRIBE ? a_nag_mlsub_regex
993 : a_nag_mlist_regex);
994 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
995 for(i = 1; lp != ngrp; lp = lp->ngr_next)
996 ++i;
997 fprintf(fo, "# regex(7): hits %" PRIuZ ", sort %" PRIuZ ".\n ",
998 ngrp->ngr_hits, i);
999 ++rv;
1001 #endif
1002 fprintf(fo, "wysh %s %s\n",
1003 (ngp->ng_type & a_NAG_T_SUBSCRIBE ? "mlsubscribe" : "mlist"),
1004 n_shexp_quote_cp(ngp->ng_id, TRU1));
1005 break;
1006 case a_NAG_T_SHORTCUT:
1007 assert(fo != NULL); /* xxx no vput yet */
1008 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1009 fprintf(fo, "wysh shortcut %s %s\n",
1010 ngp->ng_id, n_shexp_quote_cp(cp, TRU1));
1011 break;
1012 case a_NAG_T_CHARSETALIAS:
1013 assert(fo != NULL); /* xxx no vput yet */
1014 a_NAG_GP_TO_SUBCLASS(cp, ngp);
1015 fprintf(fo, "charsetalias %s %s\n",
1016 n_shexp_quote_cp(ngp->ng_id, TRU1), n_shexp_quote_cp(cp, TRU1));
1017 break;
1018 case a_NAG_T_FILETYPE:{
1019 struct a_nag_file_type *nftp;
1021 assert(fo != NULL); /* xxx no vput yet */
1022 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
1023 fprintf(fo, "filetype %s %s %s\n",
1024 n_shexp_quote_cp(ngp->ng_id, TRU1),
1025 n_shexp_quote_cp(nftp->nft_load.s, TRU1),
1026 n_shexp_quote_cp(nftp->nft_save.s, TRU1));
1027 }break;
1029 NYD2_LEAVE;
1030 return rv;
1033 static int
1034 a_nag_mlmux(enum a_nag_type nt, char const **argv){
1035 struct a_nag_group *ngp;
1036 char const *ecp;
1037 int rv;
1038 NYD2_ENTER;
1040 rv = 0;
1041 n_UNINIT(ecp, NULL);
1043 if(*argv == NULL)
1044 a_nag_group_print_all(nt, NULL);
1045 else do{
1046 if((ngp = a_nag_group_find(nt, *argv)) != NULL){
1047 if(nt & a_NAG_T_SUBSCRIBE){
1048 if(!(ngp->ng_type & a_NAG_T_SUBSCRIBE)){
1049 a_NAG_MLMUX_LINKOUT(ngp);
1050 ngp->ng_type |= a_NAG_T_SUBSCRIBE;
1051 a_NAG_MLMUX_LINKIN(ngp);
1052 }else{
1053 ecp = N_("Mailing-list already `mlsubscribe'd: %s\n");
1054 goto jerr;
1056 }else{
1057 ecp = N_("Mailing-list already `mlist'ed: %s\n");
1058 goto jerr;
1060 }else if(a_nag_group_fetch(nt, *argv, 0) == NULL){
1061 ecp = N_("Failed to create storage for mailing-list: %s\n");
1062 jerr:
1063 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
1064 rv = 1;
1066 }while(*++argv != NULL);
1068 NYD2_LEAVE;
1069 return rv;
1072 static int
1073 a_nag_unmlmux(enum a_nag_type nt, char const **argv){
1074 struct a_nag_group *ngp;
1075 int rv;
1076 NYD2_ENTER;
1078 rv = 0;
1080 for(; *argv != NULL; ++argv){
1081 if(nt & a_NAG_T_SUBSCRIBE){
1082 struct a_nag_group_lookup ngl;
1083 bool_t isaster;
1085 if(!(isaster = (**argv == '*')))
1086 ngp = a_nag_group_find(nt, *argv);
1087 else if((ngp = a_nag_group_go_first(nt, &ngl)) == NULL)
1088 continue;
1089 else if(ngp != NULL && !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1090 goto jaster_entry;
1092 if(ngp != NULL){
1093 jaster_redo:
1094 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1095 a_NAG_MLMUX_LINKOUT(ngp);
1096 ngp->ng_type &= ~a_NAG_T_SUBSCRIBE;
1097 a_NAG_MLMUX_LINKIN(ngp);
1099 if(isaster){
1100 jaster_entry:
1101 while((ngp = a_nag_group_go_next(&ngl)) != NULL &&
1102 !(ngp->ng_type & a_NAG_T_SUBSCRIBE))
1104 if(ngp != NULL)
1105 goto jaster_redo;
1107 }else{
1108 n_err(_("Mailing-list not `mlsubscribe'd: %s\n"),
1109 n_shexp_quote_cp(*argv, FAL0));
1110 rv = 1;
1112 continue;
1114 }else if(a_nag_group_del(nt, *argv))
1115 continue;
1116 n_err(_("No such mailing-list: %s\n"), n_shexp_quote_cp(*argv, FAL0));
1117 rv = 1;
1119 NYD2_LEAVE;
1120 return rv;
1123 #ifdef HAVE_REGEX
1124 static void
1125 a_nag_mlmux_linkin(struct a_nag_group *ngp){
1126 struct a_nag_grp_regex **lpp, *ngrp, *lhp;
1127 NYD2_ENTER;
1129 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1130 lpp = &a_nag_mlsub_regex;
1131 ++a_nag_mlsub_size;
1132 }else{
1133 lpp = &a_nag_mlist_regex;
1134 ++a_nag_mlist_size;
1137 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1139 if((lhp = *lpp) != NULL){
1140 (ngrp->ngr_last = lhp->ngr_last)->ngr_next = ngrp;
1141 (ngrp->ngr_next = lhp)->ngr_last = ngrp;
1142 }else
1143 *lpp = ngrp->ngr_last = ngrp->ngr_next = ngrp;
1144 ngrp->ngr_hits = 0;
1145 NYD2_LEAVE;
1148 static void
1149 a_nag_mlmux_linkout(struct a_nag_group *ngp){
1150 struct a_nag_grp_regex *ngrp, **lpp;
1151 NYD2_ENTER;
1153 a_NAG_GP_TO_SUBCLASS(ngrp, ngp);
1155 if(ngp->ng_type & a_NAG_T_SUBSCRIBE){
1156 lpp = &a_nag_mlsub_regex;
1157 --a_nag_mlsub_size;
1158 a_nag_mlsub_hits -= ngrp->ngr_hits;
1159 }else{
1160 lpp = &a_nag_mlist_regex;
1161 --a_nag_mlist_size;
1162 a_nag_mlist_hits -= ngrp->ngr_hits;
1165 if(ngrp->ngr_next == ngrp)
1166 *lpp = NULL;
1167 else{
1168 (ngrp->ngr_last->ngr_next = ngrp->ngr_next)->ngr_last = ngrp->ngr_last;
1169 if(*lpp == ngrp)
1170 *lpp = ngrp->ngr_next;
1172 NYD2_LEAVE;
1174 #endif /* HAVE_REGEX */
1176 FL struct name *
1177 nalloc(char const *str, enum gfield ntype)
1179 struct n_addrguts ag;
1180 struct str in, out;
1181 struct name *np;
1182 NYD_ENTER;
1183 assert(!(ntype & GFULLEXTRA) || (ntype & GFULL) != 0);
1185 str = n_addrspec_with_guts(&ag, str,
1186 ((ntype & (GFULL | GSKIN | GREF)) != 0), FAL0);
1187 if(str == NULL){
1189 np = NULL; TODO We cannot return NULL,
1190 goto jleave; TODO thus handle failures in here!
1192 str = ag.ag_input;
1195 if (!(ag.ag_n_flags & NAME_NAME_SALLOC)) {
1196 ag.ag_n_flags |= NAME_NAME_SALLOC;
1197 np = n_autorec_alloc(sizeof(*np) + ag.ag_slen +1);
1198 memcpy(np + 1, ag.ag_skinned, ag.ag_slen +1);
1199 ag.ag_skinned = (char*)(np + 1);
1200 } else
1201 np = n_autorec_alloc(sizeof *np);
1203 np->n_flink = NULL;
1204 np->n_blink = NULL;
1205 np->n_type = ntype;
1206 np->n_fullname = np->n_name = ag.ag_skinned;
1207 np->n_fullextra = NULL;
1208 np->n_flags = ag.ag_n_flags;
1210 if (ntype & GFULL) {
1211 if (ag.ag_ilen == ag.ag_slen
1212 #ifdef HAVE_IDNA
1213 && !(ag.ag_n_flags & NAME_IDNA)
1214 #endif
1216 goto jleave;
1217 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1218 goto jleave;
1220 /* n_fullextra is only the complete name part without address.
1221 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1222 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1223 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1224 char const *cp;
1226 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1227 goto jskipfullextra;
1228 i = ag.ag_ilen - e;
1229 in.s = n_lofi_alloc(s + 1 + i +1);
1230 while(s > 0 && blankchar(str[s - 1]))
1231 --s;
1232 memcpy(in.s, str, s);
1233 if (i > 0) {
1234 in.s[s++] = ' ';
1235 while (blankchar(str[e])) {
1236 ++e;
1237 if (--i == 0)
1238 break;
1240 if (i > 0)
1241 memcpy(&in.s[s], &str[e], i);
1243 s += i;
1244 in.s[in.l = s] = '\0';
1245 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1247 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1249 while (i > 0 && spacechar(cp[i - 1]))
1250 --i;
1251 np->n_fullextra = savestrbuf(cp, i);
1253 n_lofi_free(in.s);
1254 n_free(out.s);
1256 jskipfullextra:
1258 /* n_fullname depends on IDNA conversion */
1259 #ifdef HAVE_IDNA
1260 if (!(ag.ag_n_flags & NAME_IDNA)) {
1261 #endif
1262 in.s = n_UNCONST(str);
1263 in.l = ag.ag_ilen;
1264 #ifdef HAVE_IDNA
1265 } else {
1266 /* The domain name was IDNA and has been converted. We also have to
1267 * ensure that the domain name in .n_fullname is replaced with the
1268 * converted version, since MIME doesn't perform encoding of addrs */
1269 /* TODO This definetely doesn't belong here! */
1270 size_t l = ag.ag_iaddr_start,
1271 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1272 in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
1273 memcpy(in.s, str, l);
1274 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1275 l += ag.ag_slen;
1276 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1277 l += lsuff;
1278 in.s[l] = '\0';
1279 in.l = l;
1281 #endif
1282 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1283 np->n_fullname = savestr(out.s);
1284 n_free(out.s);
1285 #ifdef HAVE_IDNA
1286 if (ag.ag_n_flags & NAME_IDNA)
1287 n_lofi_free(in.s);
1288 #endif
1290 jleave:
1291 NYD_LEAVE;
1292 return np;
1295 FL struct name *
1296 ndup(struct name *np, enum gfield ntype)
1298 struct name *nnp;
1299 NYD_ENTER;
1301 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1302 nnp = nalloc(np->n_name, ntype);
1303 goto jleave;
1306 nnp = n_autorec_alloc(sizeof *np);
1307 nnp->n_flink = nnp->n_blink = NULL;
1308 nnp->n_type = ntype;
1309 nnp->n_flags = np->n_flags | NAME_NAME_SALLOC;
1310 nnp->n_name = savestr(np->n_name);
1311 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1312 nnp->n_fullname = nnp->n_name;
1313 nnp->n_fullextra = NULL;
1314 } else {
1315 nnp->n_fullname = savestr(np->n_fullname);
1316 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1317 : savestr(np->n_fullextra);
1319 jleave:
1320 NYD_LEAVE;
1321 return nnp;
1324 FL struct name *
1325 cat(struct name *n1, struct name *n2){
1326 struct name *tail;
1327 NYD2_ENTER;
1329 tail = n2;
1330 if(n1 == NULL)
1331 goto jleave;
1332 tail = n1;
1333 if(n2 == NULL || (n2->n_type & GDEL))
1334 goto jleave;
1336 while(tail->n_flink != NULL)
1337 tail = tail->n_flink;
1338 tail->n_flink = n2;
1339 n2->n_blink = tail;
1340 tail = n1;
1341 jleave:
1342 NYD2_LEAVE;
1343 return tail;
1346 FL struct name *
1347 namelist_dup(struct name const *np, enum gfield ntype){
1348 struct name *nlist, *xnp;
1349 NYD2_ENTER;
1351 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1352 struct name *x;
1354 if(!(np->n_type & GDEL)){
1355 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1356 if((x->n_blink = xnp) == NULL)
1357 nlist = x;
1358 else
1359 xnp->n_flink = x;
1360 xnp = x;
1363 NYD2_LEAVE;
1364 return nlist;
1367 FL ui32_t
1368 count(struct name const *np)
1370 ui32_t c;
1371 NYD_ENTER;
1373 for (c = 0; np != NULL; np = np->n_flink)
1374 if (!(np->n_type & GDEL))
1375 ++c;
1376 NYD_LEAVE;
1377 return c;
1380 FL ui32_t
1381 count_nonlocal(struct name const *np)
1383 ui32_t c;
1384 NYD_ENTER;
1386 for (c = 0; np != NULL; np = np->n_flink)
1387 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1388 ++c;
1389 NYD_LEAVE;
1390 return c;
1393 FL struct name *
1394 extract(char const *line, enum gfield ntype)
1396 struct name *rv;
1397 NYD_ENTER;
1399 rv = a_nag_extract1(line, ntype, " \t,", 0);
1400 NYD_LEAVE;
1401 return rv;
1404 FL struct name *
1405 lextract(char const *line, enum gfield ntype)
1407 struct name *rv;
1408 NYD_ENTER;
1410 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1411 ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
1412 NYD_LEAVE;
1413 return rv;
1416 FL char *
1417 detract(struct name *np, enum gfield ntype)
1419 char *topp, *cp;
1420 struct name *p;
1421 int flags, s;
1422 NYD_ENTER;
1424 topp = NULL;
1425 if (np == NULL)
1426 goto jleave;
1428 flags = ntype & (GCOMMA | GNAMEONLY);
1429 ntype &= ~(GCOMMA | GNAMEONLY);
1430 s = 0;
1432 for (p = np; p != NULL; p = p->n_flink) {
1433 if (ntype && (p->n_type & GMASK) != ntype)
1434 continue;
1435 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1436 if (flags & GCOMMA)
1437 ++s;
1439 if (s == 0)
1440 goto jleave;
1442 s += 2;
1443 topp = n_autorec_alloc(s);
1444 cp = topp;
1445 for (p = np; p != NULL; p = p->n_flink) {
1446 if (ntype && (p->n_type & GMASK) != ntype)
1447 continue;
1448 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1449 if ((flags & GCOMMA) && p->n_flink != NULL)
1450 *cp++ = ',';
1451 *cp++ = ' ';
1453 *--cp = 0;
1454 if ((flags & GCOMMA) && *--cp == ',')
1455 *cp = 0;
1456 jleave:
1457 NYD_LEAVE;
1458 return topp;
1461 FL struct name *
1462 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1463 int comma, enum gfield gflags)
1465 struct name *nq;
1466 NYD_ENTER;
1468 jloop:
1469 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1470 for (nq = np; nq != NULL; nq = nq->n_flink)
1471 if (is_addr_invalid(nq, EACM_NONE))
1472 goto jloop;
1473 NYD_LEAVE;
1474 return np;
1477 FL bool_t
1478 name_is_same_domain(struct name const *n1, struct name const *n2)
1480 char const *d1, *d2;
1481 bool_t rv;
1482 NYD_ENTER;
1484 d1 = strrchr(n1->n_name, '@');
1485 d2 = strrchr(n2->n_name, '@');
1487 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1489 NYD_LEAVE;
1490 return rv;
1493 FL struct name *
1494 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1495 si8_t *set_on_error)
1497 struct name *n;
1498 NYD_ENTER;
1500 for (n = np; n != NULL; n = n->n_flink) {
1501 si8_t rv;
1503 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1504 if (set_on_error != NULL)
1505 *set_on_error |= rv; /* don't loose -1! */
1506 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1507 continue;
1508 if (n->n_blink)
1509 n->n_blink->n_flink = n->n_flink;
1510 if (n->n_flink)
1511 n->n_flink->n_blink = n->n_blink;
1512 if (n == np)
1513 np = n->n_flink;
1516 NYD_LEAVE;
1517 return np;
1520 FL struct name *
1521 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1522 bool_t metoo, si8_t *set_on_error)
1524 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1525 struct name *tolist, *np, **npp;
1526 NYD_ENTER;
1528 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1529 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1531 tolist = usermap(tolist, metoo);
1532 tolist = n_alternates_remove(tolist, TRU1);
1533 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1535 for (np = tolist; np != NULL; np = np->n_flink) {
1536 switch (np->n_type & (GDEL | GMASK)) {
1537 case GTO: npp = &hp->h_to; break;
1538 case GCC: npp = &hp->h_cc; break;
1539 case GBCC: npp = &hp->h_bcc; break;
1540 default: continue;
1542 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1544 NYD_LEAVE;
1545 return tolist;
1548 FL struct name *
1549 usermap(struct name *names, bool_t force_metoo){
1550 struct a_nag_group *ngp;
1551 struct name *nlist, *nlist_tail, *np, *cp;
1552 int metoo;
1553 NYD_ENTER;
1555 metoo = (force_metoo || ok_blook(metoo));
1556 nlist = nlist_tail = NULL;
1557 np = names;
1559 for(; np != NULL; np = cp){
1560 assert(!(np->n_type & GDEL)); /* TODO legacy */
1561 cp = np->n_flink;
1563 if(is_fileorpipe_addr(np) ||
1564 (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
1565 if((np->n_blink = nlist_tail) != NULL)
1566 nlist_tail->n_flink = np;
1567 else
1568 nlist = np;
1569 nlist_tail = np;
1570 np->n_flink = NULL;
1571 }else{
1572 nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
1573 if((nlist_tail = nlist) != NULL)
1574 while(nlist_tail->n_flink != NULL)
1575 nlist_tail = nlist_tail->n_flink;
1578 NYD_LEAVE;
1579 return nlist;
1582 FL struct name *
1583 elide(struct name *names)
1585 size_t i, j, k;
1586 struct name *nlist, *np, **nparr;
1587 NYD_ENTER;
1589 nlist = NULL;
1591 if(names == NULL)
1592 goto jleave;
1594 /* Throw away all deleted nodes */
1595 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1596 if(!(names->n_type & GDEL)){
1597 names->n_blink = np;
1598 if(np != NULL)
1599 np->n_flink = names;
1600 else
1601 nlist = names;
1602 np = names;
1603 ++i;
1605 if(nlist == NULL || i == 1)
1606 goto jleave;
1607 np->n_flink = NULL;
1609 /* Create a temporay array and sort that */
1610 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1612 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1613 nparr[i++] = np;
1615 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1617 /* Remove duplicates XXX speedup, or list_uniq()! */
1618 for(j = 0, --i; j < i;){
1619 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1620 ++j;
1621 else{
1622 for(; k < i; ++k)
1623 nparr[k] = nparr[k + 1];
1624 --i;
1628 /* Throw away all list members which are not part of the array.
1629 * Note this keeps the original, possibly carefully crafted, order of the
1630 * addressees, thus */
1631 for(np = nlist; np != NULL; np = np->n_flink){
1632 for(j = 0; j <= i; ++j)
1633 if(np == nparr[j]){
1634 nparr[j] = NULL;
1635 goto jiter;
1637 /* Drop it */
1638 if(np == nlist){
1639 nlist = np->n_flink;
1640 np->n_blink = NULL;
1641 }else
1642 np->n_blink->n_flink = np->n_flink;
1643 if(np->n_flink != NULL)
1644 np->n_flink->n_blink = np->n_blink;
1645 jiter:;
1648 n_lofi_free(nparr);
1649 jleave:
1650 NYD_LEAVE;
1651 return nlist;
1654 FL int
1655 c_alternates(void *vp){
1656 struct a_nag_group *ngp;
1657 char const *varname, *ccp;
1658 char **argv;
1659 NYD_ENTER;
1661 n_pstate_err_no = n_ERR_NONE;
1663 argv = vp;
1664 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1666 if(*argv == NULL){
1667 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
1668 vp = NULL;
1669 }else{
1670 if(varname != NULL)
1671 n_err(_("`alternates': `vput' only supported for show mode\n"));
1673 /* Delete the old set to "declare a list", if *posix* */
1674 if(ok_blook(posix))
1675 a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
1677 while((ccp = *argv++) != NULL){
1678 size_t l;
1679 struct name *np;
1681 if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
1682 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1683 n_err(_("Invalid `alternates' argument: %s\n"),
1684 n_shexp_quote_cp(ccp, FAL0));
1685 n_pstate_err_no = n_ERR_INVAL;
1686 vp = NULL;
1687 continue;
1689 ccp = np->n_name;
1691 l = strlen(ccp) +1;
1692 if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
1693 n_err(_("Failed to create storage for alternates: %s\n"),
1694 n_shexp_quote_cp(ccp, FAL0));
1695 n_pstate_err_no = n_ERR_NOMEM;
1696 vp = NULL;
1700 NYD_LEAVE;
1701 return (vp != NULL ? 0 : 1);
1704 FL int
1705 c_unalternates(void *vp){
1706 char **argv;
1707 int rv;
1708 NYD_ENTER;
1710 rv = 0;
1711 argv = vp;
1713 do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
1714 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1715 rv = 1;
1716 }while(*++argv != NULL);
1717 NYD_LEAVE;
1718 return rv;
1721 FL struct name *
1722 n_alternates_remove(struct name *np, bool_t keep_single){
1723 /* XXX keep a single pointer, initial null, and immediate remove nodes
1724 * XXX on successful match unless keep single and that pointer null! */
1725 struct a_nag_group_lookup ngl;
1726 struct a_nag_group *ngp;
1727 struct name *xp, *newnp;
1728 NYD_ENTER;
1730 /* Delete the temporary bit from all */
1731 for(xp = np; xp != NULL; xp = xp->n_flink)
1732 xp->n_flags &= ~(ui32_t)SI32_MIN;
1734 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1735 * and hash-lookup alternate instead (unless *allnet*) */
1736 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1737 ngp = a_nag_group_go_next(&ngl))
1738 np = a_nag_namelist_mark_name(np, ngp->ng_id);
1740 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1742 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1743 xp = xp->n_flink)
1744 np = a_nag_namelist_mark_name(np, xp->n_name);
1746 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1747 xp = xp->n_flink)
1748 np = a_nag_namelist_mark_name(np, xp->n_name);
1750 /* C99 */{
1751 char const *v15compat;
1753 if((v15compat = ok_vlook(replyto)) != NULL){
1754 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1755 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1756 xp = xp->n_flink)
1757 np = a_nag_namelist_mark_name(np, xp->n_name);
1761 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1762 xp = xp->n_flink)
1763 np = a_nag_namelist_mark_name(np, xp->n_name);
1765 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1766 for(xp = newnp = NULL; np != NULL; np = np->n_flink){
1767 if(np->n_type & GDEL)
1768 continue;
1769 if(np->n_flags & (ui32_t)SI32_MIN){
1770 if(!keep_single)
1771 continue;
1772 keep_single = FAL0;
1775 np->n_blink = xp;
1776 if(xp != NULL)
1777 xp->n_flink = np;
1778 else
1779 newnp = np;
1780 xp = np;
1781 xp->n_flags &= ~(ui32_t)SI32_MIN;
1783 if(xp != NULL)
1784 xp->n_flink = NULL;
1785 np = newnp;
1787 NYD_LEAVE;
1788 return np;
1791 FL bool_t
1792 n_is_myname(char const *name){
1793 struct a_nag_group_lookup ngl;
1794 struct a_nag_group *ngp;
1795 struct name *xp;
1796 NYD_ENTER;
1798 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1799 goto jleave;
1801 if(!ok_blook(allnet)){
1802 if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
1803 goto jleave;
1804 }else{
1805 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1806 ngp = a_nag_group_go_next(&ngl))
1807 if(a_nag_is_same_name(ngp->ng_id, name))
1808 goto jleave;
1811 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1812 xp = xp->n_flink)
1813 if(a_nag_is_same_name(xp->n_name, name))
1814 goto jleave;
1816 /* C99 */{
1817 char const *v15compat;
1819 if((v15compat = ok_vlook(replyto)) != NULL){
1820 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1821 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1822 xp = xp->n_flink)
1823 if(a_nag_is_same_name(xp->n_name, name))
1824 goto jleave;
1828 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1829 xp = xp->n_flink)
1830 if(a_nag_is_same_name(xp->n_name, name))
1831 goto jleave;
1833 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1834 xp = xp->n_flink)
1835 if(a_nag_is_same_name(xp->n_name, name))
1836 goto jleave;
1838 name = NULL;
1839 jleave:
1840 NYD_LEAVE;
1841 return (name != NULL);
1844 FL int
1845 c_addrcodec(void *vp){
1846 struct n_addrguts ag;
1847 struct str trims;
1848 struct n_string s_b, *sp;
1849 size_t alen;
1850 int mode;
1851 char const **argv, *varname, *act, *cp;
1852 NYD_ENTER;
1854 sp = n_string_creat_auto(&s_b);
1855 argv = vp;
1856 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1858 act = *argv;
1859 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1861 mode = 0;
1862 if(*act == '+')
1863 mode = 1, ++act;
1864 if(*act == '+')
1865 mode = 2, ++act;
1866 if(*act == '+')
1867 mode = 3, ++act;
1868 if(act >= cp)
1869 goto jesynopsis;
1870 alen = PTR2SIZE(cp - act);
1871 if(*cp != '\0')
1872 ++cp;
1874 trims.l = strlen(trims.s = n_UNCONST(cp));
1875 cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
1876 if(trims.l <= UIZ_MAX / 4)
1877 trims.l <<= 1;
1878 sp = n_string_reserve(sp, trims.l);
1880 n_pstate_err_no = n_ERR_NONE;
1882 if(is_ascncaseprefix(act, "encode", alen)){
1883 /* This function cannot be a simple nalloc() wrapper even later on, since
1884 * we may need to turn any ", () or \ into quoted-pairs */
1885 char c;
1887 while((c = *cp++) != '\0'){
1888 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1889 (c == '\\' && mode < 3))
1890 sp = n_string_push_c(sp, '\\');
1891 sp = n_string_push_c(sp, c);
1894 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1895 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1896 ) != NAME_ADDRSPEC_ISADDR){
1897 cp = sp->s_dat;
1898 n_pstate_err_no = n_ERR_INVAL;
1899 vp = NULL;
1900 }else{
1901 struct name *np;
1903 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1904 cp = np->n_fullname;
1906 }else if(mode == 0){
1907 if(is_ascncaseprefix(act, "decode", alen)){
1908 char c;
1910 while((c = *cp++) != '\0'){
1911 switch(c){
1912 case '(':
1913 sp = n_string_push_c(sp, '(');
1914 act = skip_comment(cp);
1915 if(--act > cp)
1916 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1917 sp = n_string_push_c(sp, ')');
1918 cp = ++act;
1919 break;
1920 case '"':
1921 while(*cp != '\0'){
1922 if((c = *cp++) == '"')
1923 break;
1924 if(c == '\\' && (c = *cp) != '\0')
1925 ++cp;
1926 sp = n_string_push_c(sp, c);
1928 break;
1929 default:
1930 if(c == '\\' && (c = *cp++) == '\0')
1931 break;
1932 sp = n_string_push_c(sp, c);
1933 break;
1936 cp = n_string_cp(sp);
1937 }else if(is_ascncaseprefix(act, "skin", alen) ||
1938 (mode = 1, is_ascncaseprefix(act, "skinlist", alen))){
1939 /* Let's just use the is-single-address hack for this one, too.. */
1940 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1941 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1942 ) != NAME_ADDRSPEC_ISADDR){
1943 n_pstate_err_no = n_ERR_INVAL;
1944 vp = NULL;
1945 }else{
1946 struct name *np;
1948 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1949 cp = np->n_name;
1951 if(mode == 1 && is_mlist(cp, FAL0) != MLIST_OTHER)
1952 n_pstate_err_no = n_ERR_EXIST;
1954 }else
1955 goto jesynopsis;
1956 }else
1957 goto jesynopsis;
1959 if(varname == NULL){
1960 if(fprintf(n_stdout, "%s\n", cp) < 0){
1961 n_pstate_err_no = n_err_no;
1962 vp = NULL;
1964 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1965 n_pstate_err_no = n_ERR_NOTSUP;
1966 vp = NULL;
1969 jleave:
1970 NYD_LEAVE;
1971 return (vp != NULL ? 0 : 1);
1972 jesynopsis:
1973 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1974 "<rest-of-line>\n"));
1975 n_pstate_err_no = n_ERR_INVAL;
1976 vp = NULL;
1977 goto jleave;
1980 FL int
1981 c_commandalias(void *vp){
1982 struct a_nag_group *ngp;
1983 char const **argv, *ccp;
1984 int rv;
1985 NYD_ENTER;
1987 rv = 0;
1988 argv = vp;
1990 if((ccp = *argv) == NULL){
1991 a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
1992 goto jleave;
1995 /* Verify the name is a valid one, and not a command modifier.
1996 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
1997 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
1998 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "local") ||
1999 !asccasecmp(ccp, "wysh") || !asccasecmp(ccp, "vput") ||
2000 !asccasecmp(ccp, "scope") || !asccasecmp(ccp, "u")){
2001 n_err(_("`commandalias': not a valid command name: %s\n"),
2002 n_shexp_quote_cp(ccp, FAL0));
2003 rv = 1;
2004 goto jleave;
2007 if(argv[1] == NULL){
2008 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
2009 a_nag_group_print(ngp, n_stdout, NULL);
2010 else{
2011 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
2012 rv = 1;
2014 }else{
2015 /* Because one hardly ever redefines, anything is stored in one chunk */
2016 char *cp;
2017 size_t i, len;
2019 /* Delete the old one, if any; don't get fooled to remove them all */
2020 if(ccp[0] != '*' || ccp[1] != '\0')
2021 a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
2023 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
2024 len += strlen(argv[i]) + 1;
2025 if(len == 0)
2026 len = 1;
2028 if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
2029 n_err(_("Failed to create storage for commandalias: %s\n"),
2030 n_shexp_quote_cp(ccp, FAL0));
2031 rv = 1;
2032 }else{
2033 struct a_nag_cmd_alias *ncap;
2035 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2036 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2037 cp += sizeof *ncap;
2038 ncap->nca_expand.s = cp;
2039 ncap->nca_expand.l = len - 1;
2041 for(len = 0; (ccp = *argv++) != NULL;)
2042 if((i = strlen(ccp)) > 0){
2043 if(len++ != 0)
2044 *cp++ = ' ';
2045 memcpy(cp, ccp, i);
2046 cp += i;
2048 *cp = '\0';
2051 jleave:
2052 NYD_LEAVE;
2053 return rv;
2056 FL int
2057 c_uncommandalias(void *vp){
2058 char **argv;
2059 int rv;
2060 NYD_ENTER;
2062 rv = 0;
2063 argv = vp;
2065 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
2066 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2067 rv = 1;
2068 }while(*++argv != NULL);
2069 NYD_LEAVE;
2070 return rv;
2073 FL char const *
2074 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
2075 struct a_nag_group *ngp;
2076 NYD_ENTER;
2078 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
2079 name = ngp->ng_id;
2081 if(expansion_or_null != NULL){
2082 struct a_nag_cmd_alias *ncap;
2084 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2085 *expansion_or_null = &ncap->nca_expand;
2087 }else
2088 name = NULL;
2089 NYD_LEAVE;
2090 return name;
2093 FL bool_t
2094 n_alias_is_valid_name(char const *name){
2095 char c;
2096 char const *cp;
2097 bool_t rv;
2098 NYD2_ENTER;
2100 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
2101 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2102 * i.e., [[:alnum:]_#:@.-]+$?.
2103 * As extensions allow high-bit bytes, semicolon and period. */
2104 if(!alnumchar(c) && c != '_' && c != '-' &&
2105 c != '#' && c != ':' && c != '@' &&
2106 !((ui8_t)c & 0x80) && c != '!' && c != '.'){
2107 if(c == '$' && cp != name && *cp == '\0')
2108 break;
2109 rv = FAL0;
2110 break;
2112 NYD2_LEAVE;
2113 return rv;
2116 FL int
2117 c_alias(void *v)
2119 char const *ecp;
2120 char **argv;
2121 struct a_nag_group *ngp;
2122 int rv;
2123 NYD_ENTER;
2125 rv = 0;
2126 argv = v;
2127 n_UNINIT(ecp, NULL);
2129 if(*argv == NULL)
2130 a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
2131 else if(!n_alias_is_valid_name(*argv)){
2132 ecp = N_("Not a valid alias name: %s\n");
2133 goto jerr;
2134 }else if(argv[1] == NULL){
2135 if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
2136 a_nag_group_print(ngp, n_stdout, NULL);
2137 else{
2138 ecp = N_("No such alias: %s\n");
2139 goto jerr;
2141 }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
2142 ecp = N_("Failed to create alias storage for: %s\n");
2143 jerr:
2144 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
2145 rv = 1;
2146 }else{
2147 struct a_nag_grp_names *ngnp_tail, *ngnp;
2148 struct a_nag_grp_names_head *ngnhp;
2150 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
2152 if((ngnp_tail = ngnhp->ngnh_head) != NULL)
2153 while((ngnp = ngnp_tail->ngn_next) != NULL)
2154 ngnp_tail = ngnp;
2156 for(++argv; *argv != NULL; ++argv){
2157 size_t i;
2159 i = strlen(*argv) +1;
2160 ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
2161 if(ngnp_tail != NULL)
2162 ngnp_tail->ngn_next = ngnp;
2163 else
2164 ngnhp->ngnh_head = ngnp;
2165 ngnp_tail = ngnp;
2166 ngnp->ngn_next = NULL;
2167 memcpy(ngnp->ngn_id, *argv, i);
2170 NYD_LEAVE;
2171 return rv;
2174 FL int
2175 c_unalias(void *v){
2176 char **argv;
2177 int rv;
2178 NYD_ENTER;
2180 rv = 0;
2181 argv = v;
2183 do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
2184 n_err(_("No such alias: %s\n"), *argv);
2185 rv = 1;
2186 }while(*++argv != NULL);
2187 NYD_LEAVE;
2188 return rv;
2191 FL int
2192 c_mlist(void *v){
2193 int rv;
2194 NYD_ENTER;
2196 rv = a_nag_mlmux(a_NAG_T_MLIST, v);
2197 NYD_LEAVE;
2198 return rv;
2201 FL int
2202 c_unmlist(void *v){
2203 int rv;
2204 NYD_ENTER;
2206 rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
2207 NYD_LEAVE;
2208 return rv;
2211 FL int
2212 c_mlsubscribe(void *v){
2213 int rv;
2214 NYD_ENTER;
2216 rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2217 NYD_LEAVE;
2218 return rv;
2221 FL int
2222 c_unmlsubscribe(void *v){
2223 int rv;
2224 NYD_ENTER;
2226 rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2227 NYD_LEAVE;
2228 return rv;
2231 FL enum mlist_state
2232 is_mlist(char const *name, bool_t subscribed_only){
2233 struct a_nag_group *ngp;
2234 #ifdef HAVE_REGEX
2235 struct a_nag_grp_regex **lpp, *ngrp;
2236 bool_t re2;
2237 #endif
2238 enum mlist_state rv;
2239 NYD_ENTER;
2241 ngp = a_nag_group_find(a_NAG_T_MLIST, name);
2242 rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2244 if(rv == MLIST_KNOWN){
2245 if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
2246 rv = MLIST_SUBSCRIBED;
2247 else if(subscribed_only)
2248 rv = MLIST_OTHER;
2249 /* Of course, if that is a regular expression it doesn't mean a thing */
2250 #ifdef HAVE_REGEX
2251 if(ngp->ng_type & a_NAG_T_REGEX)
2252 rv = MLIST_OTHER;
2253 else
2254 #endif
2255 goto jleave;
2258 /* Not in the hashmap (as something matchable), walk the lists */
2259 #ifdef HAVE_REGEX
2260 re2 = FAL0;
2261 lpp = &a_nag_mlsub_regex;
2263 jregex_redo:
2264 if((ngrp = *lpp) != NULL){
2265 do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
2266 /* Relink as the head of this list if the hit count of this group is
2267 * >= 25% of the average hit count */
2268 size_t i;
2270 if(!re2)
2271 i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
2272 else
2273 i = ++a_nag_mlist_hits / a_nag_mlist_size;
2274 i >>= 2;
2276 if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
2277 ngrp->ngr_last->ngr_next = ngrp->ngr_next;
2278 ngrp->ngr_next->ngr_last = ngrp->ngr_last;
2279 (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
2280 (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
2281 *lpp = ngrp;
2283 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2284 goto jleave;
2285 }while((ngrp = ngrp->ngr_next) != *lpp);
2288 if(!re2 && !subscribed_only){
2289 re2 = TRU1;
2290 lpp = &a_nag_mlist_regex;
2291 goto jregex_redo;
2293 assert(rv == MLIST_OTHER);
2294 #endif /* HAVE_REGEX */
2296 jleave:
2297 NYD_LEAVE;
2298 return rv;
2301 FL enum mlist_state
2302 is_mlist_mp(struct message *mp, enum mlist_state what){
2303 struct name *np;
2304 bool_t cc;
2305 enum mlist_state rv;
2306 NYD_ENTER;
2308 rv = MLIST_OTHER;
2310 cc = FAL0;
2311 np = lextract(hfield1("to", mp), GTO | GSKIN);
2312 jredo:
2313 for(; np != NULL; np = np->n_flink){
2314 switch(is_mlist(np->n_name, FAL0)){
2315 case MLIST_OTHER:
2316 break;
2317 case MLIST_KNOWN:
2318 if(what == MLIST_KNOWN || what == MLIST_OTHER){
2319 if(rv == MLIST_OTHER)
2320 rv = MLIST_KNOWN;
2321 if(what == MLIST_KNOWN)
2322 goto jleave;
2324 break;
2325 case MLIST_SUBSCRIBED:
2326 if(what == MLIST_SUBSCRIBED || what == MLIST_OTHER){
2327 if(rv != MLIST_SUBSCRIBED)
2328 rv = MLIST_SUBSCRIBED;
2329 goto jleave;
2331 break;
2335 if(!cc){
2336 cc = TRU1;
2337 np = lextract(hfield1("cc", mp), GCC | GSKIN);
2338 goto jredo;
2340 jleave:
2341 NYD_LEAVE;
2342 return rv;
2345 FL int
2346 c_shortcut(void *vp){
2347 struct a_nag_group *ngp;
2348 char **argv;
2349 int rv;
2350 NYD_ENTER;
2352 rv = 0;
2353 argv = vp;
2355 if(*argv == NULL)
2356 a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
2357 else if(argv[1] == NULL){
2358 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
2359 a_nag_group_print(ngp, n_stdout, NULL);
2360 else{
2361 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2362 rv = 1;
2364 }else for(; *argv != NULL; argv += 2){
2365 /* Because one hardly ever redefines, anything is stored in one chunk */
2366 size_t l;
2367 char *cp;
2369 if(argv[1] == NULL){
2370 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2371 rv = 1;
2372 break;
2374 if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
2375 a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
2377 l = strlen(argv[1]) +1;
2378 if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
2379 n_err(_("Failed to create storage for shortcut: %s\n"),
2380 n_shexp_quote_cp(*argv, FAL0));
2381 rv = 1;
2382 }else{
2383 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2384 memcpy(cp, argv[1], l);
2387 NYD_LEAVE;
2388 return rv;
2391 FL int
2392 c_unshortcut(void *vp){
2393 char **argv;
2394 int rv;
2395 NYD_ENTER;
2397 rv = 0;
2398 argv = vp;
2400 do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
2401 n_err(_("No such shortcut: %s\n"), *argv);
2402 rv = 1;
2403 }while(*++argv != NULL);
2404 NYD_LEAVE;
2405 return rv;
2408 FL char const *
2409 shortcut_expand(char const *str){
2410 struct a_nag_group *ngp;
2411 NYD_ENTER;
2413 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
2414 a_NAG_GP_TO_SUBCLASS(str, ngp);
2415 else
2416 str = NULL;
2417 NYD_LEAVE;
2418 return str;
2421 FL int
2422 c_charsetalias(void *vp){
2423 struct a_nag_group *ngp;
2424 char **argv;
2425 int rv;
2426 NYD_ENTER;
2428 rv = 0;
2429 argv = vp;
2431 if(*argv == NULL)
2432 a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
2433 else if(argv[1] == NULL){
2434 if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
2435 a_nag_group_print(ngp, n_stdout, NULL);
2436 else{
2437 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2438 rv = 1;
2440 }else for(; *argv != NULL; argv += 2){
2441 /* Because one hardly ever redefines, anything is stored in one chunk */
2442 char *cp;
2443 size_t dstl;
2444 char const *dst, *src;
2446 if((dst = argv[1]) == NULL){
2447 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2448 rv = 1;
2449 break;
2450 }else if((dst = n_iconv_normalize_name(dst)) == NULL){
2451 n_err(_("charsetalias: invalid target charset %s\n"),
2452 n_shexp_quote_cp(argv[1], FAL0));
2453 rv = 1;
2454 continue;
2455 }else if((src = n_iconv_normalize_name(argv[0])) == NULL){
2456 n_err(_("charsetalias: invalid source charset %s\n"),
2457 n_shexp_quote_cp(argv[0], FAL0));
2458 rv = 1;
2459 continue;
2462 /* Delete the old one, if any; don't get fooled to remove them all */
2463 if(src[0] != '*' || src[1] != '\0')
2464 a_nag_group_del(a_NAG_T_CHARSETALIAS, src);
2466 dstl = strlen(dst) +1;
2467 if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, src, dstl)) == NULL){
2468 n_err(_("Failed to create storage for charsetalias: %s\n"),
2469 n_shexp_quote_cp(src, FAL0));
2470 rv = 1;
2471 }else{
2472 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2473 memcpy(cp, dst, dstl);
2476 NYD_LEAVE;
2477 return rv;
2480 FL int
2481 c_uncharsetalias(void *vp){
2482 char **argv, *cp;
2483 int rv;
2484 NYD_ENTER;
2486 rv = 0;
2487 argv = vp;
2490 if((cp = n_iconv_normalize_name(*argv)) == NULL ||
2491 !a_nag_group_del(a_NAG_T_CHARSETALIAS, cp)){
2492 n_err(_("No such `charsetalias': %s\n"),
2493 n_shexp_quote_cp(*argv, FAL0));
2494 rv = 1;
2496 }while(*++argv != NULL);
2497 NYD_LEAVE;
2498 return rv;
2501 FL char const *
2502 n_charsetalias_expand(char const *cp){
2503 struct a_nag_group *ngp;
2504 size_t i;
2505 char const *cp_orig;
2506 NYD_ENTER;
2508 cp_orig = cp;
2510 for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
2511 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2512 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2513 break;
2516 if(cp != cp_orig)
2517 cp = savestr(cp);
2518 NYD_LEAVE;
2519 return cp;
2522 FL int
2523 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2524 struct a_nag_group *ngp;
2525 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2526 int rv;
2527 NYD_ENTER;
2529 rv = 0;
2530 argv = vp;
2532 if(*argv == NULL)
2533 a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
2534 else if(argv[1] == NULL){
2535 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
2536 a_nag_group_print(ngp, n_stdout, NULL);
2537 else{
2538 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2539 rv = 1;
2541 }else for(; *argv != NULL; argv += 3){
2542 /* Because one hardly ever redefines, anything is stored in one chunk */
2543 char const *ccp;
2544 char *cp, c;
2545 size_t llc, lsc;
2547 if(argv[1] == NULL || argv[2] == NULL){
2548 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2549 rv = 1;
2550 break;
2553 /* Delete the old one, if any; don't get fooled to remove them all */
2554 ccp = argv[0];
2555 if(ccp[0] != '*' || ccp[1] != '\0')
2556 a_nag_group_del(a_NAG_T_FILETYPE, ccp);
2558 /* Lowercase it all (for display purposes) */
2559 cp = savestr(ccp);
2560 ccp = cp;
2561 while((c = *cp) != '\0')
2562 *cp++ = lowerconv(c);
2564 llc = strlen(argv[1]) +1;
2565 lsc = strlen(argv[2]) +1;
2566 if(UIZ_MAX - llc <= lsc)
2567 goto jenomem;
2569 if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
2570 jenomem:
2571 n_err(_("Failed to create storage for filetype: %s\n"),
2572 n_shexp_quote_cp(argv[0], FAL0));
2573 rv = 1;
2574 }else{
2575 struct a_nag_file_type *nftp;
2577 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2578 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2579 cp += sizeof *nftp;
2580 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2581 cp += llc;
2582 nftp->nft_load.l = --llc;
2583 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2584 /*cp += lsc;*/
2585 nftp->nft_save.l = --lsc;
2588 NYD_LEAVE;
2589 return rv;
2592 FL int
2593 c_unfiletype(void *vp){
2594 char **argv;
2595 int rv;
2596 NYD_ENTER;
2598 rv = 0;
2599 argv = vp;
2601 do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
2602 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2603 rv = 1;
2604 }while(*++argv != NULL);
2605 NYD_LEAVE;
2606 return rv;
2609 FL bool_t
2610 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2611 struct stat stb;
2612 struct a_nag_group_lookup ngl;
2613 struct n_string s, *sp;
2614 struct a_nag_group const *ngp;
2615 ui32_t l;
2616 NYD2_ENTER;
2618 sp = n_string_creat_auto(&s);
2619 sp = n_string_assign_cp(sp, file);
2620 sp = n_string_push_c(sp, '.');
2621 l = sp->s_len;
2623 for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
2624 ngp = a_nag_group_go_next(&ngl)){
2625 sp = n_string_trunc(sp, l);
2626 sp = n_string_push_buf(sp, ngp->ng_id,
2627 ngp->ng_subclass_off - ngp->ng_id_len_sub);
2629 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2630 if(res_or_null != NULL){
2631 struct a_nag_file_type *nftp;
2633 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2634 res_or_null->ft_ext_dat = ngp->ng_id;
2635 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2636 res_or_null->ft_load_dat = nftp->nft_load.s;
2637 res_or_null->ft_load_len = nftp->nft_load.l;
2638 res_or_null->ft_save_dat = nftp->nft_save.s;
2639 res_or_null->ft_save_len = nftp->nft_save.l;
2641 goto jleave; /* TODO after v15 legacy drop: break; */
2645 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2646 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2647 ngp = (struct a_nag_group*)0x1;
2649 sp = n_string_trunc(sp, l);
2650 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2651 a_nag_OBSOLETE_xz.ft_ext_len);
2652 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2653 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2654 if(res_or_null != NULL)
2655 *res_or_null = a_nag_OBSOLETE_xz;
2656 goto jleave;
2659 sp = n_string_trunc(sp, l);
2660 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2661 a_nag_OBSOLETE_gz.ft_ext_len);
2662 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2663 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2664 if(res_or_null != NULL)
2665 *res_or_null = a_nag_OBSOLETE_gz;
2666 goto jleave;
2669 sp = n_string_trunc(sp, l);
2670 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2671 a_nag_OBSOLETE_bz2.ft_ext_len);
2672 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2673 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2674 if(res_or_null != NULL)
2675 *res_or_null = a_nag_OBSOLETE_bz2;
2676 goto jleave;
2679 ngp = NULL;
2681 jleave:
2682 NYD2_LEAVE;
2683 return (ngp != NULL);
2686 FL bool_t
2687 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2688 char const *ext, *lext;
2689 NYD2_ENTER;
2691 if((ext = strrchr(file, '/')) != NULL)
2692 file = ++ext;
2694 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2695 struct a_nag_group const *ngp;
2697 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
2698 lext = ext;
2699 if(res_or_null != NULL){
2700 struct a_nag_file_type *nftp;
2702 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2703 res_or_null->ft_ext_dat = ngp->ng_id;
2704 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2705 res_or_null->ft_load_dat = nftp->nft_load.s;
2706 res_or_null->ft_load_len = nftp->nft_load.l;
2707 res_or_null->ft_save_dat = nftp->nft_save.s;
2708 res_or_null->ft_save_len = nftp->nft_save.l;
2710 goto jleave; /* TODO after v15 legacy drop: break; */
2714 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2715 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2716 if(lext == NULL)
2717 goto jleave;
2719 if(!asccasecmp(lext, "xz")){
2720 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2721 if(res_or_null != NULL)
2722 *res_or_null = a_nag_OBSOLETE_xz;
2723 goto jleave;
2724 }else if(!asccasecmp(lext, "gz")){
2725 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2726 if(res_or_null != NULL)
2727 *res_or_null = a_nag_OBSOLETE_gz;
2728 goto jleave;
2729 }else if(!asccasecmp(lext, "bz2")){
2730 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2731 if(res_or_null != NULL)
2732 *res_or_null = a_nag_OBSOLETE_bz2;
2733 goto jleave;
2734 }else{
2735 char const *cload, *csave;
2736 char *vbuf;
2737 size_t l;
2739 #undef a_X1
2740 #define a_X1 "file-hook-load-"
2741 #undef a_X2
2742 #define a_X2 "file-hook-save-"
2743 l = strlen(lext);
2744 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2746 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2747 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2748 vbuf[sizeof(a_X1) -1 + l] = '\0';
2749 cload = n_var_vlook(vbuf, FAL0);
2751 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2752 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2753 vbuf[sizeof(a_X2) -1 + l] = '\0';
2754 csave = n_var_vlook(vbuf, FAL0);
2756 #undef a_X2
2757 #undef a_X1
2758 n_lofi_free(vbuf);
2760 if((csave != NULL) | (cload != NULL)){
2761 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2762 "please use the `filetype' command");
2764 if(((csave != NULL) ^ (cload != NULL)) == 0){
2765 if(res_or_null != NULL){
2766 res_or_null->ft_ext_dat = lext;
2767 res_or_null->ft_ext_len = l;
2768 res_or_null->ft_load_dat = cload;
2769 res_or_null->ft_load_len = strlen(cload);
2770 res_or_null->ft_save_dat = csave;
2771 res_or_null->ft_save_len = strlen(csave);
2773 goto jleave;
2774 }else
2775 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2776 lext);
2780 lext = NULL;
2782 jleave:
2783 NYD2_LEAVE;
2784 return (lext != NULL);
2787 /* s-it-mode */