`vexpr': obsolete @ as suffix, it must henceforth be prefix..
[s-mailx.git] / nam-a-grp.c
blob55d4e8fefd9046b5dda210613dd53dc411827da1
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_flags = 0;
1208 np->n_fullname = np->n_name = ag.ag_skinned;
1209 np->n_fullextra = NULL;
1210 np->n_flags = ag.ag_n_flags;
1212 if (ntype & GFULL) {
1213 if (ag.ag_ilen == ag.ag_slen
1214 #ifdef HAVE_IDNA
1215 && !(ag.ag_n_flags & NAME_IDNA)
1216 #endif
1218 goto jleave;
1219 if (ag.ag_n_flags & NAME_ADDRSPEC_ISFILEORPIPE)
1220 goto jleave;
1222 /* n_fullextra is only the complete name part without address.
1223 * Beware of "-r '<abc@def>'", don't treat that as FULLEXTRA */
1224 if ((ntype & GFULLEXTRA) && ag.ag_ilen > ag.ag_slen + 2) {
1225 size_t s = ag.ag_iaddr_start, e = ag.ag_iaddr_aend, i;
1226 char const *cp;
1228 if (s == 0 || str[--s] != '<' || str[e++] != '>')
1229 goto jskipfullextra;
1230 i = ag.ag_ilen - e;
1231 in.s = n_lofi_alloc(s + 1 + i +1);
1232 while(s > 0 && blankchar(str[s - 1]))
1233 --s;
1234 memcpy(in.s, str, s);
1235 if (i > 0) {
1236 in.s[s++] = ' ';
1237 while (blankchar(str[e])) {
1238 ++e;
1239 if (--i == 0)
1240 break;
1242 if (i > 0)
1243 memcpy(&in.s[s], &str[e], i);
1245 s += i;
1246 in.s[in.l = s] = '\0';
1247 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1249 for (cp = out.s, i = out.l; i > 0 && spacechar(*cp); --i, ++cp)
1251 while (i > 0 && spacechar(cp[i - 1]))
1252 --i;
1253 np->n_fullextra = savestrbuf(cp, i);
1255 n_lofi_free(in.s);
1256 n_free(out.s);
1258 jskipfullextra:
1260 /* n_fullname depends on IDNA conversion */
1261 #ifdef HAVE_IDNA
1262 if (!(ag.ag_n_flags & NAME_IDNA)) {
1263 #endif
1264 in.s = n_UNCONST(str);
1265 in.l = ag.ag_ilen;
1266 #ifdef HAVE_IDNA
1267 } else {
1268 /* The domain name was IDNA and has been converted. We also have to
1269 * ensure that the domain name in .n_fullname is replaced with the
1270 * converted version, since MIME doesn't perform encoding of addrs */
1271 /* TODO This definetely doesn't belong here! */
1272 size_t l = ag.ag_iaddr_start,
1273 lsuff = ag.ag_ilen - ag.ag_iaddr_aend;
1274 in.s = n_lofi_alloc(l + ag.ag_slen + lsuff +1);
1275 memcpy(in.s, str, l);
1276 memcpy(in.s + l, ag.ag_skinned, ag.ag_slen);
1277 l += ag.ag_slen;
1278 memcpy(in.s + l, str + ag.ag_iaddr_aend, lsuff);
1279 l += lsuff;
1280 in.s[l] = '\0';
1281 in.l = l;
1283 #endif
1284 mime_fromhdr(&in, &out, /* TODO TD_ISPR |*/ TD_ICONV);
1285 np->n_fullname = savestr(out.s);
1286 n_free(out.s);
1287 #ifdef HAVE_IDNA
1288 if (ag.ag_n_flags & NAME_IDNA)
1289 n_lofi_free(in.s);
1290 #endif
1291 np->n_flags |= NAME_FULLNAME_SALLOC;
1293 jleave:
1294 NYD_LEAVE;
1295 return np;
1298 FL struct name *
1299 ndup(struct name *np, enum gfield ntype)
1301 struct name *nnp;
1302 NYD_ENTER;
1304 if ((ntype & (GFULL | GSKIN)) && !(np->n_flags & NAME_SKINNED)) {
1305 nnp = nalloc(np->n_name, ntype);
1306 goto jleave;
1309 nnp = n_autorec_alloc(sizeof *np);
1310 nnp->n_flink = nnp->n_blink = NULL;
1311 nnp->n_type = ntype;
1312 nnp->n_flags = (np->n_flags & ~(NAME_NAME_SALLOC | NAME_FULLNAME_SALLOC)) |
1313 NAME_NAME_SALLOC;
1314 nnp->n_name = savestr(np->n_name);
1315 if (np->n_name == np->n_fullname || !(ntype & (GFULL | GSKIN))) {
1316 nnp->n_fullname = nnp->n_name;
1317 nnp->n_fullextra = NULL;
1318 } else {
1319 nnp->n_flags |= NAME_FULLNAME_SALLOC;
1320 nnp->n_fullname = savestr(np->n_fullname);
1321 nnp->n_fullextra = (np->n_fullextra == NULL) ? NULL
1322 : savestr(np->n_fullextra);
1324 jleave:
1325 NYD_LEAVE;
1326 return nnp;
1329 FL struct name *
1330 cat(struct name *n1, struct name *n2){
1331 struct name *tail;
1332 NYD2_ENTER;
1334 tail = n2;
1335 if(n1 == NULL)
1336 goto jleave;
1337 tail = n1;
1338 if(n2 == NULL || (n2->n_type & GDEL))
1339 goto jleave;
1341 while(tail->n_flink != NULL)
1342 tail = tail->n_flink;
1343 tail->n_flink = n2;
1344 n2->n_blink = tail;
1345 tail = n1;
1346 jleave:
1347 NYD2_LEAVE;
1348 return tail;
1351 FL struct name *
1352 namelist_dup(struct name const *np, enum gfield ntype){
1353 struct name *nlist, *xnp;
1354 NYD2_ENTER;
1356 for(nlist = xnp = NULL; np != NULL; np = np->n_flink){
1357 struct name *x;
1359 if(!(np->n_type & GDEL)){
1360 x = ndup(n_UNCONST(np), (np->n_type & ~GMASK) | ntype);
1361 if((x->n_blink = xnp) == NULL)
1362 nlist = x;
1363 else
1364 xnp->n_flink = x;
1365 xnp = x;
1368 NYD2_LEAVE;
1369 return nlist;
1372 FL ui32_t
1373 count(struct name const *np)
1375 ui32_t c;
1376 NYD_ENTER;
1378 for (c = 0; np != NULL; np = np->n_flink)
1379 if (!(np->n_type & GDEL))
1380 ++c;
1381 NYD_LEAVE;
1382 return c;
1385 FL ui32_t
1386 count_nonlocal(struct name const *np)
1388 ui32_t c;
1389 NYD_ENTER;
1391 for (c = 0; np != NULL; np = np->n_flink)
1392 if (!(np->n_type & GDEL) && !(np->n_flags & NAME_ADDRSPEC_ISFILEORPIPE))
1393 ++c;
1394 NYD_LEAVE;
1395 return c;
1398 FL struct name *
1399 extract(char const *line, enum gfield ntype)
1401 struct name *rv;
1402 NYD_ENTER;
1404 rv = a_nag_extract1(line, ntype, " \t,", 0);
1405 NYD_LEAVE;
1406 return rv;
1409 FL struct name *
1410 lextract(char const *line, enum gfield ntype)
1412 struct name *rv;
1413 NYD_ENTER;
1415 rv = ((line != NULL && strpbrk(line, ",\"\\(<|"))
1416 ? a_nag_extract1(line, ntype, ",", 1) : extract(line, ntype));
1417 NYD_LEAVE;
1418 return rv;
1421 FL char *
1422 detract(struct name *np, enum gfield ntype)
1424 char *topp, *cp;
1425 struct name *p;
1426 int flags, s;
1427 NYD_ENTER;
1429 topp = NULL;
1430 if (np == NULL)
1431 goto jleave;
1433 flags = ntype & (GCOMMA | GNAMEONLY);
1434 ntype &= ~(GCOMMA | GNAMEONLY);
1435 s = 0;
1437 for (p = np; p != NULL; p = p->n_flink) {
1438 if (ntype && (p->n_type & GMASK) != ntype)
1439 continue;
1440 s += strlen(flags & GNAMEONLY ? p->n_name : p->n_fullname) +1;
1441 if (flags & GCOMMA)
1442 ++s;
1444 if (s == 0)
1445 goto jleave;
1447 s += 2;
1448 topp = n_autorec_alloc(s);
1449 cp = topp;
1450 for (p = np; p != NULL; p = p->n_flink) {
1451 if (ntype && (p->n_type & GMASK) != ntype)
1452 continue;
1453 cp = sstpcpy(cp, (flags & GNAMEONLY ? p->n_name : p->n_fullname));
1454 if ((flags & GCOMMA) && p->n_flink != NULL)
1455 *cp++ = ',';
1456 *cp++ = ' ';
1458 *--cp = 0;
1459 if ((flags & GCOMMA) && *--cp == ',')
1460 *cp = 0;
1461 jleave:
1462 NYD_LEAVE;
1463 return topp;
1466 FL struct name *
1467 grab_names(enum n_go_input_flags gif, char const *field, struct name *np,
1468 int comma, enum gfield gflags)
1470 struct name *nq;
1471 NYD_ENTER;
1473 jloop:
1474 np = lextract(n_go_input_cp(gif, field, detract(np, comma)), gflags);
1475 for (nq = np; nq != NULL; nq = nq->n_flink)
1476 if (is_addr_invalid(nq, EACM_NONE))
1477 goto jloop;
1478 NYD_LEAVE;
1479 return np;
1482 FL bool_t
1483 name_is_same_domain(struct name const *n1, struct name const *n2)
1485 char const *d1, *d2;
1486 bool_t rv;
1487 NYD_ENTER;
1489 d1 = strrchr(n1->n_name, '@');
1490 d2 = strrchr(n2->n_name, '@');
1492 rv = (d1 != NULL && d2 != NULL) ? !asccasecmp(++d1, ++d2) : FAL0;
1494 NYD_LEAVE;
1495 return rv;
1498 FL struct name *
1499 checkaddrs(struct name *np, enum expand_addr_check_mode eacm,
1500 si8_t *set_on_error)
1502 struct name *n;
1503 NYD_ENTER;
1505 for (n = np; n != NULL; n = n->n_flink) {
1506 si8_t rv;
1508 if ((rv = is_addr_invalid(n, eacm)) != 0) {
1509 if (set_on_error != NULL)
1510 *set_on_error |= rv; /* don't loose -1! */
1511 else if (eacm & EAF_MAYKEEP) /* TODO HACK! See definition! */
1512 continue;
1513 if (n->n_blink)
1514 n->n_blink->n_flink = n->n_flink;
1515 if (n->n_flink)
1516 n->n_flink->n_blink = n->n_blink;
1517 if (n == np)
1518 np = n->n_flink;
1521 NYD_LEAVE;
1522 return np;
1525 FL struct name *
1526 namelist_vaporise_head(struct header *hp, enum expand_addr_check_mode eacm,
1527 bool_t metoo, si8_t *set_on_error)
1529 /* TODO namelist_vaporise_head() is incredibly expensive and redundant */
1530 struct name *tolist, *np, **npp;
1531 NYD_ENTER;
1533 tolist = cat(hp->h_to, cat(hp->h_cc, hp->h_bcc));
1534 hp->h_to = hp->h_cc = hp->h_bcc = NULL;
1536 tolist = usermap(tolist, metoo);
1537 tolist = n_alternates_remove(tolist, TRU1);
1538 tolist = elide(checkaddrs(tolist, eacm, set_on_error));
1540 for (np = tolist; np != NULL; np = np->n_flink) {
1541 switch (np->n_type & (GDEL | GMASK)) {
1542 case GTO: npp = &hp->h_to; break;
1543 case GCC: npp = &hp->h_cc; break;
1544 case GBCC: npp = &hp->h_bcc; break;
1545 default: continue;
1547 *npp = cat(*npp, ndup(np, np->n_type | GFULL));
1549 NYD_LEAVE;
1550 return tolist;
1553 FL struct name *
1554 usermap(struct name *names, bool_t force_metoo){
1555 struct a_nag_group *ngp;
1556 struct name *nlist, *nlist_tail, *np, *cp;
1557 int metoo;
1558 NYD_ENTER;
1560 metoo = (force_metoo || ok_blook(metoo));
1561 nlist = nlist_tail = NULL;
1562 np = names;
1564 for(; np != NULL; np = cp){
1565 assert(!(np->n_type & GDEL)); /* TODO legacy */
1566 cp = np->n_flink;
1568 if(is_fileorpipe_addr(np) ||
1569 (ngp = a_nag_group_find(a_NAG_T_ALIAS, np->n_name)) == NULL){
1570 if((np->n_blink = nlist_tail) != NULL)
1571 nlist_tail->n_flink = np;
1572 else
1573 nlist = np;
1574 nlist_tail = np;
1575 np->n_flink = NULL;
1576 }else{
1577 nlist = a_nag_gexpand(0, nlist, ngp, metoo, np->n_type);
1578 if((nlist_tail = nlist) != NULL)
1579 while(nlist_tail->n_flink != NULL)
1580 nlist_tail = nlist_tail->n_flink;
1583 NYD_LEAVE;
1584 return nlist;
1587 FL struct name *
1588 elide(struct name *names)
1590 size_t i, j, k;
1591 struct name *nlist, *np, **nparr;
1592 NYD_ENTER;
1594 nlist = NULL;
1596 if(names == NULL)
1597 goto jleave;
1599 /* Throw away all deleted nodes */
1600 for(np = NULL, i = 0; names != NULL; names = names->n_flink)
1601 if(!(names->n_type & GDEL)){
1602 names->n_blink = np;
1603 if(np != NULL)
1604 np->n_flink = names;
1605 else
1606 nlist = names;
1607 np = names;
1608 ++i;
1610 if(nlist == NULL || i == 1)
1611 goto jleave;
1612 np->n_flink = NULL;
1614 /* Create a temporay array and sort that */
1615 nparr = n_lofi_alloc(sizeof(*nparr) * i);
1617 for(i = 0, np = nlist; np != NULL; np = np->n_flink)
1618 nparr[i++] = np;
1620 qsort(nparr, i, sizeof *nparr, &a_nag_elide_qsort);
1622 /* Remove duplicates XXX speedup, or list_uniq()! */
1623 for(j = 0, --i; j < i;){
1624 if(asccasecmp(nparr[j]->n_name, nparr[k = j + 1]->n_name))
1625 ++j;
1626 else{
1627 for(; k < i; ++k)
1628 nparr[k] = nparr[k + 1];
1629 --i;
1633 /* Throw away all list members which are not part of the array.
1634 * Note this keeps the original, possibly carefully crafted, order of the
1635 * addressees, thus */
1636 for(np = nlist; np != NULL; np = np->n_flink){
1637 for(j = 0; j <= i; ++j)
1638 if(np == nparr[j]){
1639 nparr[j] = NULL;
1640 goto jiter;
1642 /* Drop it */
1643 if(np == nlist){
1644 nlist = np->n_flink;
1645 np->n_blink = NULL;
1646 }else
1647 np->n_blink->n_flink = np->n_flink;
1648 if(np->n_flink != NULL)
1649 np->n_flink->n_blink = np->n_blink;
1650 jiter:;
1653 n_lofi_free(nparr);
1654 jleave:
1655 NYD_LEAVE;
1656 return nlist;
1659 FL int
1660 c_alternates(void *vp){
1661 struct a_nag_group *ngp;
1662 char const *varname, *ccp;
1663 char **argv;
1664 NYD_ENTER;
1666 n_pstate_err_no = n_ERR_NONE;
1668 argv = vp;
1669 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1671 if(*argv == NULL){
1672 if(!a_nag_group_print_all(a_NAG_T_ALTERNATES, varname))
1673 vp = NULL;
1674 }else{
1675 if(varname != NULL)
1676 n_err(_("`alternates': `vput' only supported for show mode\n"));
1678 /* Delete the old set to "declare a list", if *posix* */
1679 if(ok_blook(posix))
1680 a_nag_group_del(a_NAG_T_ALTERNATES, n_star);
1682 while((ccp = *argv++) != NULL){
1683 size_t l;
1684 struct name *np;
1686 if((np = lextract(ccp, GSKIN)) == NULL || np->n_flink != NULL ||
1687 (np = checkaddrs(np, EACM_STRICT, NULL)) == NULL){
1688 n_err(_("Invalid `alternates' argument: %s\n"),
1689 n_shexp_quote_cp(ccp, FAL0));
1690 n_pstate_err_no = n_ERR_INVAL;
1691 vp = NULL;
1692 continue;
1694 ccp = np->n_name;
1696 l = strlen(ccp) +1;
1697 if((ngp = a_nag_group_fetch(a_NAG_T_ALTERNATES, ccp, l)) == NULL){
1698 n_err(_("Failed to create storage for alternates: %s\n"),
1699 n_shexp_quote_cp(ccp, FAL0));
1700 n_pstate_err_no = n_ERR_NOMEM;
1701 vp = NULL;
1705 NYD_LEAVE;
1706 return (vp != NULL ? 0 : 1);
1709 FL int
1710 c_unalternates(void *vp){
1711 char **argv;
1712 int rv;
1713 NYD_ENTER;
1715 rv = 0;
1716 argv = vp;
1718 do if(!a_nag_group_del(a_NAG_T_ALTERNATES, *argv)){
1719 n_err(_("No such `alternates': %s\n"), n_shexp_quote_cp(*argv, FAL0));
1720 rv = 1;
1721 }while(*++argv != NULL);
1722 NYD_LEAVE;
1723 return rv;
1726 FL struct name *
1727 n_alternates_remove(struct name *np, bool_t keep_single){
1728 /* XXX keep a single pointer, initial null, and immediate remove nodes
1729 * XXX on successful match unless keep single and that pointer null! */
1730 struct a_nag_group_lookup ngl;
1731 struct a_nag_group *ngp;
1732 struct name *xp, *newnp;
1733 NYD_ENTER;
1735 /* Delete the temporary bit from all */
1736 for(xp = np; xp != NULL; xp = xp->n_flink)
1737 xp->n_flags &= ~(ui32_t)SI32_MIN;
1739 /* Mark all possible alternate names (xxx sic: instead walk over namelist
1740 * and hash-lookup alternate instead (unless *allnet*) */
1741 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1742 ngp = a_nag_group_go_next(&ngl))
1743 np = a_nag_namelist_mark_name(np, ngp->ng_id);
1745 np = a_nag_namelist_mark_name(np, ok_vlook(LOGNAME));
1747 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1748 xp = xp->n_flink)
1749 np = a_nag_namelist_mark_name(np, xp->n_name);
1751 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1752 xp = xp->n_flink)
1753 np = a_nag_namelist_mark_name(np, xp->n_name);
1755 /* C99 */{
1756 char const *v15compat;
1758 if((v15compat = ok_vlook(replyto)) != NULL){
1759 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1760 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1761 xp = xp->n_flink)
1762 np = a_nag_namelist_mark_name(np, xp->n_name);
1766 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1767 xp = xp->n_flink)
1768 np = a_nag_namelist_mark_name(np, xp->n_name);
1770 /* Clean the list by throwing away all deleted or marked (but one) nodes */
1771 for(xp = newnp = NULL; np != NULL; np = np->n_flink){
1772 if(np->n_type & GDEL)
1773 continue;
1774 if(np->n_flags & (ui32_t)SI32_MIN){
1775 if(!keep_single)
1776 continue;
1777 keep_single = FAL0;
1780 np->n_blink = xp;
1781 if(xp != NULL)
1782 xp->n_flink = np;
1783 else
1784 newnp = np;
1785 xp = np;
1786 xp->n_flags &= ~(ui32_t)SI32_MIN;
1788 if(xp != NULL)
1789 xp->n_flink = NULL;
1790 np = newnp;
1792 NYD_LEAVE;
1793 return np;
1796 FL bool_t
1797 n_is_myname(char const *name){
1798 struct a_nag_group_lookup ngl;
1799 struct a_nag_group *ngp;
1800 struct name *xp;
1801 NYD_ENTER;
1803 if(a_nag_is_same_name(ok_vlook(LOGNAME), name))
1804 goto jleave;
1806 if(!ok_blook(allnet)){
1807 if(a_nag_group_lookup(a_NAG_T_ALTERNATES, &ngl, name) != NULL)
1808 goto jleave;
1809 }else{
1810 for(ngp = a_nag_group_go_first(a_NAG_T_ALTERNATES, &ngl); ngp != NULL;
1811 ngp = a_nag_group_go_next(&ngl))
1812 if(a_nag_is_same_name(ngp->ng_id, name))
1813 goto jleave;
1816 for(xp = lextract(ok_vlook(from), GEXTRA | GSKIN); xp != NULL;
1817 xp = xp->n_flink)
1818 if(a_nag_is_same_name(xp->n_name, name))
1819 goto jleave;
1821 /* C99 */{
1822 char const *v15compat;
1824 if((v15compat = ok_vlook(replyto)) != NULL){
1825 n_OBSOLETE(_("please use *reply-to*, not *replyto*"));
1826 for(xp = lextract(v15compat, GEXTRA | GSKIN); xp != NULL;
1827 xp = xp->n_flink)
1828 if(a_nag_is_same_name(xp->n_name, name))
1829 goto jleave;
1833 for(xp = lextract(ok_vlook(reply_to), GEXTRA | GSKIN); xp != NULL;
1834 xp = xp->n_flink)
1835 if(a_nag_is_same_name(xp->n_name, name))
1836 goto jleave;
1838 for(xp = extract(ok_vlook(sender), GEXTRA | GSKIN); xp != NULL;
1839 xp = xp->n_flink)
1840 if(a_nag_is_same_name(xp->n_name, name))
1841 goto jleave;
1843 name = NULL;
1844 jleave:
1845 NYD_LEAVE;
1846 return (name != NULL);
1849 FL int
1850 c_addrcodec(void *vp){
1851 struct n_addrguts ag;
1852 struct str trims;
1853 struct n_string s_b, *sp;
1854 size_t alen;
1855 int mode;
1856 char const **argv, *varname, *act, *cp;
1857 NYD_ENTER;
1859 sp = n_string_creat_auto(&s_b);
1860 argv = vp;
1861 varname = (n_pstate & n_PS_ARGMOD_VPUT) ? *argv++ : NULL;
1863 act = *argv;
1864 for(cp = act; *cp != '\0' && !blankspacechar(*cp); ++cp)
1866 mode = 0;
1867 if(*act == '+')
1868 mode = 1, ++act;
1869 if(*act == '+')
1870 mode = 2, ++act;
1871 if(*act == '+')
1872 mode = 3, ++act;
1873 if(act >= cp)
1874 goto jesynopsis;
1875 alen = PTR2SIZE(cp - act);
1876 if(*cp != '\0')
1877 ++cp;
1879 trims.l = strlen(trims.s = n_UNCONST(cp));
1880 cp = savestrbuf(n_str_trim(&trims, n_STR_TRIM_BOTH)->s, trims.l);
1881 if(trims.l <= UIZ_MAX / 4)
1882 trims.l <<= 1;
1883 sp = n_string_reserve(sp, trims.l);
1885 n_pstate_err_no = n_ERR_NONE;
1887 if(is_ascncaseprefix(act, "encode", alen)){
1888 /* This function cannot be a simple nalloc() wrapper even later on, since
1889 * we may need to turn any ", () or \ into quoted-pairs */
1890 char c;
1892 while((c = *cp++) != '\0'){
1893 if(((c == '(' || c == ')') && mode < 1) || (c == '"' && mode < 2) ||
1894 (c == '\\' && mode < 3))
1895 sp = n_string_push_c(sp, '\\');
1896 sp = n_string_push_c(sp, c);
1899 if(n_addrspec_with_guts(&ag, n_string_cp(sp), TRU1, TRU1) == NULL ||
1900 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1901 ) != NAME_ADDRSPEC_ISADDR){
1902 cp = sp->s_dat;
1903 n_pstate_err_no = n_ERR_INVAL;
1904 vp = NULL;
1905 }else{
1906 struct name *np;
1908 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1909 cp = np->n_fullname;
1911 }else if(mode == 0){
1912 if(is_ascncaseprefix(act, "decode", alen)){
1913 char c;
1915 while((c = *cp++) != '\0'){
1916 switch(c){
1917 case '(':
1918 sp = n_string_push_c(sp, '(');
1919 act = skip_comment(cp);
1920 if(--act > cp)
1921 sp = n_string_push_buf(sp, cp, PTR2SIZE(act - cp));
1922 sp = n_string_push_c(sp, ')');
1923 cp = ++act;
1924 break;
1925 case '"':
1926 while(*cp != '\0'){
1927 if((c = *cp++) == '"')
1928 break;
1929 if(c == '\\' && (c = *cp) != '\0')
1930 ++cp;
1931 sp = n_string_push_c(sp, c);
1933 break;
1934 default:
1935 if(c == '\\' && (c = *cp++) == '\0')
1936 break;
1937 sp = n_string_push_c(sp, c);
1938 break;
1941 cp = n_string_cp(sp);
1942 }else if(is_ascncaseprefix(act, "skin", alen) ||
1943 (mode = 1, is_ascncaseprefix(act, "skinlist", alen))){
1944 /* Let's just use the is-single-address hack for this one, too.. */
1945 if(n_addrspec_with_guts(&ag, cp, TRU1, TRU1) == NULL ||
1946 (ag.ag_n_flags & (NAME_ADDRSPEC_ISADDR | NAME_ADDRSPEC_INVALID)
1947 ) != NAME_ADDRSPEC_ISADDR){
1948 n_pstate_err_no = n_ERR_INVAL;
1949 vp = NULL;
1950 }else{
1951 struct name *np;
1953 np = nalloc(ag.ag_input, GTO | GFULL | GSKIN);
1954 cp = np->n_name;
1956 if(mode == 1 && is_mlist(cp, FAL0) != MLIST_OTHER)
1957 n_pstate_err_no = n_ERR_EXIST;
1959 }else
1960 goto jesynopsis;
1961 }else
1962 goto jesynopsis;
1964 if(varname == NULL){
1965 if(fprintf(n_stdout, "%s\n", cp) < 0){
1966 n_pstate_err_no = n_err_no;
1967 vp = NULL;
1969 }else if(!n_var_vset(varname, (uintptr_t)cp)){
1970 n_pstate_err_no = n_ERR_NOTSUP;
1971 vp = NULL;
1974 jleave:
1975 NYD_LEAVE;
1976 return (vp != NULL ? 0 : 1);
1977 jesynopsis:
1978 n_err(_("Synopsis: addrcodec: <[+[+[+]]]e[ncode]|d[ecode]|s[kin]> "
1979 "<rest-of-line>\n"));
1980 n_pstate_err_no = n_ERR_INVAL;
1981 vp = NULL;
1982 goto jleave;
1985 FL int
1986 c_commandalias(void *vp){
1987 struct a_nag_group *ngp;
1988 char const **argv, *ccp;
1989 int rv;
1990 NYD_ENTER;
1992 rv = 0;
1993 argv = vp;
1995 if((ccp = *argv) == NULL){
1996 a_nag_group_print_all(a_NAG_T_COMMANDALIAS, NULL);
1997 goto jleave;
2000 /* Verify the name is a valid one, and not a command modifier.
2001 * NOTE: this list duplicates settings isolated somewhere else (go.c) */
2002 if(*ccp == '\0' || *n_cmd_isolate(ccp) != '\0' ||
2003 !asccasecmp(ccp, "ignerr") || !asccasecmp(ccp, "local") ||
2004 !asccasecmp(ccp, "wysh") || !asccasecmp(ccp, "vput") ||
2005 !asccasecmp(ccp, "scope") || !asccasecmp(ccp, "u")){
2006 n_err(_("`commandalias': not a valid command name: %s\n"),
2007 n_shexp_quote_cp(ccp, FAL0));
2008 rv = 1;
2009 goto jleave;
2012 if(argv[1] == NULL){
2013 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, ccp)) != NULL)
2014 a_nag_group_print(ngp, n_stdout, NULL);
2015 else{
2016 n_err(_("No such commandalias: %s\n"), n_shexp_quote_cp(ccp, FAL0));
2017 rv = 1;
2019 }else{
2020 /* Because one hardly ever redefines, anything is stored in one chunk */
2021 char *cp;
2022 size_t i, len;
2024 /* Delete the old one, if any; don't get fooled to remove them all */
2025 if(ccp[0] != '*' || ccp[1] != '\0')
2026 a_nag_group_del(a_NAG_T_COMMANDALIAS, ccp);
2028 for(i = len = 0, ++argv; argv[i] != NULL; ++i)
2029 len += strlen(argv[i]) + 1;
2030 if(len == 0)
2031 len = 1;
2033 if((ngp = a_nag_group_fetch(a_NAG_T_COMMANDALIAS, ccp, len)) == NULL){
2034 n_err(_("Failed to create storage for commandalias: %s\n"),
2035 n_shexp_quote_cp(ccp, FAL0));
2036 rv = 1;
2037 }else{
2038 struct a_nag_cmd_alias *ncap;
2040 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2041 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2042 cp += sizeof *ncap;
2043 ncap->nca_expand.s = cp;
2044 ncap->nca_expand.l = len - 1;
2046 for(len = 0; (ccp = *argv++) != NULL;)
2047 if((i = strlen(ccp)) > 0){
2048 if(len++ != 0)
2049 *cp++ = ' ';
2050 memcpy(cp, ccp, i);
2051 cp += i;
2053 *cp = '\0';
2056 jleave:
2057 NYD_LEAVE;
2058 return rv;
2061 FL int
2062 c_uncommandalias(void *vp){
2063 char **argv;
2064 int rv;
2065 NYD_ENTER;
2067 rv = 0;
2068 argv = vp;
2070 do if(!a_nag_group_del(a_NAG_T_COMMANDALIAS, *argv)){
2071 n_err(_("No such `commandalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2072 rv = 1;
2073 }while(*++argv != NULL);
2074 NYD_LEAVE;
2075 return rv;
2078 FL char const *
2079 n_commandalias_exists(char const *name, struct str const **expansion_or_null){
2080 struct a_nag_group *ngp;
2081 NYD_ENTER;
2083 if((ngp = a_nag_group_find(a_NAG_T_COMMANDALIAS, name)) != NULL){
2084 name = ngp->ng_id;
2086 if(expansion_or_null != NULL){
2087 struct a_nag_cmd_alias *ncap;
2089 a_NAG_GP_TO_SUBCLASS(ncap, ngp);
2090 *expansion_or_null = &ncap->nca_expand;
2092 }else
2093 name = NULL;
2094 NYD_LEAVE;
2095 return name;
2098 FL bool_t
2099 n_alias_is_valid_name(char const *name){
2100 char c;
2101 char const *cp;
2102 bool_t rv;
2103 NYD2_ENTER;
2105 for(rv = TRU1, cp = name++; (c = *cp++) != '\0';)
2106 /* User names, plus things explicitly mentioned in Postfix aliases(5),
2107 * i.e., [[:alnum:]_#:@.-]+$?.
2108 * As extensions allow high-bit bytes, semicolon and period. */
2109 if(!alnumchar(c) && c != '_' && c != '-' &&
2110 c != '#' && c != ':' && c != '@' &&
2111 !((ui8_t)c & 0x80) && c != '!' && c != '.'){
2112 if(c == '$' && cp != name && *cp == '\0')
2113 break;
2114 rv = FAL0;
2115 break;
2117 NYD2_LEAVE;
2118 return rv;
2121 FL int
2122 c_alias(void *v)
2124 char const *ecp;
2125 char **argv;
2126 struct a_nag_group *ngp;
2127 int rv;
2128 NYD_ENTER;
2130 rv = 0;
2131 argv = v;
2132 n_UNINIT(ecp, NULL);
2134 if(*argv == NULL)
2135 a_nag_group_print_all(a_NAG_T_ALIAS, NULL);
2136 else if(!n_alias_is_valid_name(*argv)){
2137 ecp = N_("Not a valid alias name: %s\n");
2138 goto jerr;
2139 }else if(argv[1] == NULL){
2140 if((ngp = a_nag_group_find(a_NAG_T_ALIAS, *argv)) != NULL)
2141 a_nag_group_print(ngp, n_stdout, NULL);
2142 else{
2143 ecp = N_("No such alias: %s\n");
2144 goto jerr;
2146 }else if((ngp = a_nag_group_fetch(a_NAG_T_ALIAS, *argv, 0)) == NULL){
2147 ecp = N_("Failed to create alias storage for: %s\n");
2148 jerr:
2149 n_err(V_(ecp), n_shexp_quote_cp(*argv, FAL0));
2150 rv = 1;
2151 }else{
2152 struct a_nag_grp_names *ngnp_tail, *ngnp;
2153 struct a_nag_grp_names_head *ngnhp;
2155 a_NAG_GP_TO_SUBCLASS(ngnhp, ngp);
2157 if((ngnp_tail = ngnhp->ngnh_head) != NULL)
2158 while((ngnp = ngnp_tail->ngn_next) != NULL)
2159 ngnp_tail = ngnp;
2161 for(++argv; *argv != NULL; ++argv){
2162 size_t i;
2164 i = strlen(*argv) +1;
2165 ngnp = n_alloc(n_VSTRUCT_SIZEOF(struct a_nag_grp_names, ngn_id) + i);
2166 if(ngnp_tail != NULL)
2167 ngnp_tail->ngn_next = ngnp;
2168 else
2169 ngnhp->ngnh_head = ngnp;
2170 ngnp_tail = ngnp;
2171 ngnp->ngn_next = NULL;
2172 memcpy(ngnp->ngn_id, *argv, i);
2175 NYD_LEAVE;
2176 return rv;
2179 FL int
2180 c_unalias(void *v){
2181 char **argv;
2182 int rv;
2183 NYD_ENTER;
2185 rv = 0;
2186 argv = v;
2188 do if(!a_nag_group_del(a_NAG_T_ALIAS, *argv)){
2189 n_err(_("No such alias: %s\n"), *argv);
2190 rv = 1;
2191 }while(*++argv != NULL);
2192 NYD_LEAVE;
2193 return rv;
2196 FL int
2197 c_mlist(void *v){
2198 int rv;
2199 NYD_ENTER;
2201 rv = a_nag_mlmux(a_NAG_T_MLIST, v);
2202 NYD_LEAVE;
2203 return rv;
2206 FL int
2207 c_unmlist(void *v){
2208 int rv;
2209 NYD_ENTER;
2211 rv = a_nag_unmlmux(a_NAG_T_MLIST, v);
2212 NYD_LEAVE;
2213 return rv;
2216 FL int
2217 c_mlsubscribe(void *v){
2218 int rv;
2219 NYD_ENTER;
2221 rv = a_nag_mlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2222 NYD_LEAVE;
2223 return rv;
2226 FL int
2227 c_unmlsubscribe(void *v){
2228 int rv;
2229 NYD_ENTER;
2231 rv = a_nag_unmlmux(a_NAG_T_MLIST | a_NAG_T_SUBSCRIBE, v);
2232 NYD_LEAVE;
2233 return rv;
2236 FL enum mlist_state
2237 is_mlist(char const *name, bool_t subscribed_only){
2238 struct a_nag_group *ngp;
2239 #ifdef HAVE_REGEX
2240 struct a_nag_grp_regex **lpp, *ngrp;
2241 bool_t re2;
2242 #endif
2243 enum mlist_state rv;
2244 NYD_ENTER;
2246 ngp = a_nag_group_find(a_NAG_T_MLIST, name);
2247 rv = (ngp != NULL) ? MLIST_KNOWN : MLIST_OTHER;
2249 if(rv == MLIST_KNOWN){
2250 if(ngp->ng_type & a_NAG_T_SUBSCRIBE)
2251 rv = MLIST_SUBSCRIBED;
2252 else if(subscribed_only)
2253 rv = MLIST_OTHER;
2254 /* Of course, if that is a regular expression it doesn't mean a thing */
2255 #ifdef HAVE_REGEX
2256 if(ngp->ng_type & a_NAG_T_REGEX)
2257 rv = MLIST_OTHER;
2258 else
2259 #endif
2260 goto jleave;
2263 /* Not in the hashmap (as something matchable), walk the lists */
2264 #ifdef HAVE_REGEX
2265 re2 = FAL0;
2266 lpp = &a_nag_mlsub_regex;
2268 jregex_redo:
2269 if((ngrp = *lpp) != NULL){
2270 do if(regexec(&ngrp->ngr_regex, name, 0,NULL, 0) != REG_NOMATCH){
2271 /* Relink as the head of this list if the hit count of this group is
2272 * >= 25% of the average hit count */
2273 size_t i;
2275 if(!re2)
2276 i = ++a_nag_mlsub_hits / a_nag_mlsub_size;
2277 else
2278 i = ++a_nag_mlist_hits / a_nag_mlist_size;
2279 i >>= 2;
2281 if(++ngrp->ngr_hits >= i && *lpp != ngrp && ngrp->ngr_next != ngrp){
2282 ngrp->ngr_last->ngr_next = ngrp->ngr_next;
2283 ngrp->ngr_next->ngr_last = ngrp->ngr_last;
2284 (ngrp->ngr_last = (*lpp)->ngr_last)->ngr_next = ngrp;
2285 (ngrp->ngr_next = *lpp)->ngr_last = ngrp;
2286 *lpp = ngrp;
2288 rv = !re2 ? MLIST_SUBSCRIBED : MLIST_KNOWN;
2289 goto jleave;
2290 }while((ngrp = ngrp->ngr_next) != *lpp);
2293 if(!re2 && !subscribed_only){
2294 re2 = TRU1;
2295 lpp = &a_nag_mlist_regex;
2296 goto jregex_redo;
2298 assert(rv == MLIST_OTHER);
2299 #endif /* HAVE_REGEX */
2301 jleave:
2302 NYD_LEAVE;
2303 return rv;
2306 FL enum mlist_state
2307 is_mlist_mp(struct message *mp, enum mlist_state what){
2308 struct name *np;
2309 bool_t cc;
2310 enum mlist_state rv;
2311 NYD_ENTER;
2313 rv = MLIST_OTHER;
2315 cc = FAL0;
2316 np = lextract(hfield1("to", mp), GTO | GSKIN);
2317 jredo:
2318 for(; np != NULL; np = np->n_flink){
2319 switch(is_mlist(np->n_name, FAL0)){
2320 case MLIST_OTHER:
2321 break;
2322 case MLIST_KNOWN:
2323 if(what == MLIST_KNOWN || what == MLIST_OTHER){
2324 if(rv == MLIST_OTHER)
2325 rv = MLIST_KNOWN;
2326 if(what == MLIST_KNOWN)
2327 goto jleave;
2329 break;
2330 case MLIST_SUBSCRIBED:
2331 if(what == MLIST_SUBSCRIBED || what == MLIST_OTHER){
2332 if(rv != MLIST_SUBSCRIBED)
2333 rv = MLIST_SUBSCRIBED;
2334 goto jleave;
2336 break;
2340 if(!cc){
2341 cc = TRU1;
2342 np = lextract(hfield1("cc", mp), GCC | GSKIN);
2343 goto jredo;
2345 jleave:
2346 NYD_LEAVE;
2347 return rv;
2350 FL int
2351 c_shortcut(void *vp){
2352 struct a_nag_group *ngp;
2353 char **argv;
2354 int rv;
2355 NYD_ENTER;
2357 rv = 0;
2358 argv = vp;
2360 if(*argv == NULL)
2361 a_nag_group_print_all(a_NAG_T_SHORTCUT, NULL);
2362 else if(argv[1] == NULL){
2363 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, *argv)) != NULL)
2364 a_nag_group_print(ngp, n_stdout, NULL);
2365 else{
2366 n_err(_("No such shortcut: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2367 rv = 1;
2369 }else for(; *argv != NULL; argv += 2){
2370 /* Because one hardly ever redefines, anything is stored in one chunk */
2371 size_t l;
2372 char *cp;
2374 if(argv[1] == NULL){
2375 n_err(_("Synopsis: shortcut: <shortcut> <expansion>\n"));
2376 rv = 1;
2377 break;
2379 if(a_nag_group_find(a_NAG_T_SHORTCUT, *argv) != NULL)
2380 a_nag_group_del(a_NAG_T_SHORTCUT, *argv);
2382 l = strlen(argv[1]) +1;
2383 if((ngp = a_nag_group_fetch(a_NAG_T_SHORTCUT, *argv, l)) == NULL){
2384 n_err(_("Failed to create storage for shortcut: %s\n"),
2385 n_shexp_quote_cp(*argv, FAL0));
2386 rv = 1;
2387 }else{
2388 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2389 memcpy(cp, argv[1], l);
2392 NYD_LEAVE;
2393 return rv;
2396 FL int
2397 c_unshortcut(void *vp){
2398 char **argv;
2399 int rv;
2400 NYD_ENTER;
2402 rv = 0;
2403 argv = vp;
2405 do if(!a_nag_group_del(a_NAG_T_SHORTCUT, *argv)){
2406 n_err(_("No such shortcut: %s\n"), *argv);
2407 rv = 1;
2408 }while(*++argv != NULL);
2409 NYD_LEAVE;
2410 return rv;
2413 FL char const *
2414 shortcut_expand(char const *str){
2415 struct a_nag_group *ngp;
2416 NYD_ENTER;
2418 if((ngp = a_nag_group_find(a_NAG_T_SHORTCUT, str)) != NULL)
2419 a_NAG_GP_TO_SUBCLASS(str, ngp);
2420 else
2421 str = NULL;
2422 NYD_LEAVE;
2423 return str;
2426 FL int
2427 c_charsetalias(void *vp){
2428 struct a_nag_group *ngp;
2429 char **argv;
2430 int rv;
2431 NYD_ENTER;
2433 rv = 0;
2434 argv = vp;
2436 if(*argv == NULL)
2437 a_nag_group_print_all(a_NAG_T_CHARSETALIAS, NULL);
2438 else if(argv[1] == NULL){
2439 if((ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, *argv)) != NULL)
2440 a_nag_group_print(ngp, n_stdout, NULL);
2441 else{
2442 n_err(_("No such charsetalias: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2443 rv = 1;
2445 }else for(; *argv != NULL; argv += 2){
2446 /* Because one hardly ever redefines, anything is stored in one chunk */
2447 char const *ccp;
2448 char *cp, c;
2449 size_t l;
2451 if(argv[1] == NULL){
2452 n_err(_("Synopsis: charsetalias: <charset> <charset-alias>\n"));
2453 rv = 1;
2454 break;
2457 /* Delete the old one, if any; don't get fooled to remove them all */
2458 ccp = argv[0];
2459 if(ccp[0] != '*' || ccp[1] != '\0')
2460 a_nag_group_del(a_NAG_T_CHARSETALIAS, ccp);
2462 l = strlen(argv[1]) +1;
2463 if((ngp = a_nag_group_fetch(a_NAG_T_CHARSETALIAS, ccp, l)) == NULL){
2464 n_err(_("Failed to create storage for charsetalias: %s\n"),
2465 n_shexp_quote_cp(ccp, FAL0));
2466 rv = 1;
2467 }else{
2468 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2470 for(ccp = argv[1]; (c = *ccp++) != '\0';)
2471 *cp++ = lowerconv(c);
2472 *cp = '\0';
2475 NYD_LEAVE;
2476 return rv;
2479 FL int
2480 c_uncharsetalias(void *vp){
2481 char **argv;
2482 int rv;
2483 NYD_ENTER;
2485 rv = 0;
2486 argv = vp;
2488 do if(!a_nag_group_del(a_NAG_T_CHARSETALIAS, *argv)){
2489 n_err(_("No such `charsetalias': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2490 rv = 1;
2491 }while(*++argv != NULL);
2492 NYD_LEAVE;
2493 return rv;
2496 FL char const *
2497 n_charsetalias_expand(char const *cp){
2498 struct a_nag_group *ngp;
2499 size_t i;
2500 char const *cp_orig;
2501 NYD_ENTER;
2503 cp_orig = cp;
2505 for(i = 0; (ngp = a_nag_group_find(a_NAG_T_CHARSETALIAS, cp)) != NULL;){
2506 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2507 if(++i == 8) /* XXX Magic (same as for `ghost' expansion) */
2508 break;
2511 if(cp != cp_orig)
2512 cp = savestr(cp);
2513 NYD_LEAVE;
2514 return cp;
2517 FL int
2518 c_filetype(void *vp){ /* TODO support automatic chains: .tar.gz -> .gz + .tar */
2519 struct a_nag_group *ngp;
2520 char **argv; /* TODO While there: let ! prefix mean: direct execlp(2) */
2521 int rv;
2522 NYD_ENTER;
2524 rv = 0;
2525 argv = vp;
2527 if(*argv == NULL)
2528 a_nag_group_print_all(a_NAG_T_FILETYPE, NULL);
2529 else if(argv[1] == NULL){
2530 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, *argv)) != NULL)
2531 a_nag_group_print(ngp, n_stdout, NULL);
2532 else{
2533 n_err(_("No such filetype: %s\n"), n_shexp_quote_cp(*argv, FAL0));
2534 rv = 1;
2536 }else for(; *argv != NULL; argv += 3){
2537 /* Because one hardly ever redefines, anything is stored in one chunk */
2538 char const *ccp;
2539 char *cp, c;
2540 size_t llc, lsc;
2542 if(argv[1] == NULL || argv[2] == NULL){
2543 n_err(_("Synopsis: filetype: <extension> <load-cmd> <save-cmd>\n"));
2544 rv = 1;
2545 break;
2548 /* Delete the old one, if any; don't get fooled to remove them all */
2549 ccp = argv[0];
2550 if(ccp[0] != '*' || ccp[1] != '\0')
2551 a_nag_group_del(a_NAG_T_FILETYPE, ccp);
2553 /* Lowercase it all (for display purposes) */
2554 cp = savestr(ccp);
2555 ccp = cp;
2556 while((c = *cp) != '\0')
2557 *cp++ = lowerconv(c);
2559 llc = strlen(argv[1]) +1;
2560 lsc = strlen(argv[2]) +1;
2561 if(UIZ_MAX - llc <= lsc)
2562 goto jenomem;
2564 if((ngp = a_nag_group_fetch(a_NAG_T_FILETYPE, ccp, llc + lsc)) == NULL){
2565 jenomem:
2566 n_err(_("Failed to create storage for filetype: %s\n"),
2567 n_shexp_quote_cp(argv[0], FAL0));
2568 rv = 1;
2569 }else{
2570 struct a_nag_file_type *nftp;
2572 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2573 a_NAG_GP_TO_SUBCLASS(cp, ngp);
2574 cp += sizeof *nftp;
2575 memcpy(nftp->nft_load.s = cp, argv[1], llc);
2576 cp += llc;
2577 nftp->nft_load.l = --llc;
2578 memcpy(nftp->nft_save.s = cp, argv[2], lsc);
2579 /*cp += lsc;*/
2580 nftp->nft_save.l = --lsc;
2583 NYD_LEAVE;
2584 return rv;
2587 FL int
2588 c_unfiletype(void *vp){
2589 char **argv;
2590 int rv;
2591 NYD_ENTER;
2593 rv = 0;
2594 argv = vp;
2596 do if(!a_nag_group_del(a_NAG_T_FILETYPE, *argv)){
2597 n_err(_("No such `filetype': %s\n"), n_shexp_quote_cp(*argv, FAL0));
2598 rv = 1;
2599 }while(*++argv != NULL);
2600 NYD_LEAVE;
2601 return rv;
2604 FL bool_t
2605 n_filetype_trial(struct n_file_type *res_or_null, char const *file){
2606 struct stat stb;
2607 struct a_nag_group_lookup ngl;
2608 struct n_string s, *sp;
2609 struct a_nag_group const *ngp;
2610 ui32_t l;
2611 NYD2_ENTER;
2613 sp = n_string_creat_auto(&s);
2614 sp = n_string_assign_cp(sp, file);
2615 sp = n_string_push_c(sp, '.');
2616 l = sp->s_len;
2618 for(ngp = a_nag_group_go_first(a_NAG_T_FILETYPE, &ngl); ngp != NULL;
2619 ngp = a_nag_group_go_next(&ngl)){
2620 sp = n_string_trunc(sp, l);
2621 sp = n_string_push_buf(sp, ngp->ng_id,
2622 ngp->ng_subclass_off - ngp->ng_id_len_sub);
2624 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2625 if(res_or_null != NULL){
2626 struct a_nag_file_type *nftp;
2628 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2629 res_or_null->ft_ext_dat = ngp->ng_id;
2630 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2631 res_or_null->ft_load_dat = nftp->nft_load.s;
2632 res_or_null->ft_load_len = nftp->nft_load.l;
2633 res_or_null->ft_save_dat = nftp->nft_save.s;
2634 res_or_null->ft_save_len = nftp->nft_save.l;
2636 goto jleave; /* TODO after v15 legacy drop: break; */
2640 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2641 * TODO but NOT supporting *file-hook-{load,save}-EXTENSION* */
2642 ngp = (struct a_nag_group*)0x1;
2644 sp = n_string_trunc(sp, l);
2645 sp = n_string_push_buf(sp, a_nag_OBSOLETE_xz.ft_ext_dat,
2646 a_nag_OBSOLETE_xz.ft_ext_len);
2647 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2648 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2649 if(res_or_null != NULL)
2650 *res_or_null = a_nag_OBSOLETE_xz;
2651 goto jleave;
2654 sp = n_string_trunc(sp, l);
2655 sp = n_string_push_buf(sp, a_nag_OBSOLETE_gz.ft_ext_dat,
2656 a_nag_OBSOLETE_gz.ft_ext_len);
2657 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2658 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2659 if(res_or_null != NULL)
2660 *res_or_null = a_nag_OBSOLETE_gz;
2661 goto jleave;
2664 sp = n_string_trunc(sp, l);
2665 sp = n_string_push_buf(sp, a_nag_OBSOLETE_bz2.ft_ext_dat,
2666 a_nag_OBSOLETE_bz2.ft_ext_len);
2667 if(!stat(n_string_cp(sp), &stb) && S_ISREG(stb.st_mode)){
2668 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2669 if(res_or_null != NULL)
2670 *res_or_null = a_nag_OBSOLETE_bz2;
2671 goto jleave;
2674 ngp = NULL;
2676 jleave:
2677 NYD2_LEAVE;
2678 return (ngp != NULL);
2681 FL bool_t
2682 n_filetype_exists(struct n_file_type *res_or_null, char const *file){
2683 char const *ext, *lext;
2684 NYD2_ENTER;
2686 if((ext = strrchr(file, '/')) != NULL)
2687 file = ++ext;
2689 for(lext = NULL; (ext = strchr(file, '.')) != NULL; lext = file = ext){
2690 struct a_nag_group const *ngp;
2692 if((ngp = a_nag_group_find(a_NAG_T_FILETYPE, ++ext)) != NULL){
2693 lext = ext;
2694 if(res_or_null != NULL){
2695 struct a_nag_file_type *nftp;
2697 a_NAG_GP_TO_SUBCLASS(nftp, ngp);
2698 res_or_null->ft_ext_dat = ngp->ng_id;
2699 res_or_null->ft_ext_len = ngp->ng_subclass_off - ngp->ng_id_len_sub;
2700 res_or_null->ft_load_dat = nftp->nft_load.s;
2701 res_or_null->ft_load_len = nftp->nft_load.l;
2702 res_or_null->ft_save_dat = nftp->nft_save.s;
2703 res_or_null->ft_save_len = nftp->nft_save.l;
2705 goto jleave; /* TODO after v15 legacy drop: break; */
2709 /* TODO v15 legacy code: automatic file hooks for .{bz2,gz,xz},
2710 * TODO as well as supporting *file-hook-{load,save}-EXTENSION* */
2711 if(lext == NULL)
2712 goto jleave;
2714 if(!asccasecmp(lext, "xz")){
2715 n_OBSOLETE(".xz support will vanish, please use the `filetype' command");
2716 if(res_or_null != NULL)
2717 *res_or_null = a_nag_OBSOLETE_xz;
2718 goto jleave;
2719 }else if(!asccasecmp(lext, "gz")){
2720 n_OBSOLETE(".gz support will vanish, please use the `filetype' command");
2721 if(res_or_null != NULL)
2722 *res_or_null = a_nag_OBSOLETE_gz;
2723 goto jleave;
2724 }else if(!asccasecmp(lext, "bz2")){
2725 n_OBSOLETE(".bz2 support will vanish, please use the `filetype' command");
2726 if(res_or_null != NULL)
2727 *res_or_null = a_nag_OBSOLETE_bz2;
2728 goto jleave;
2729 }else{
2730 char const *cload, *csave;
2731 char *vbuf;
2732 size_t l;
2734 #undef a_X1
2735 #define a_X1 "file-hook-load-"
2736 #undef a_X2
2737 #define a_X2 "file-hook-save-"
2738 l = strlen(lext);
2739 vbuf = n_lofi_alloc(l + n_MAX(sizeof(a_X1), sizeof(a_X2)));
2741 memcpy(vbuf, a_X1, sizeof(a_X1) -1);
2742 memcpy(&vbuf[sizeof(a_X1) -1], lext, l);
2743 vbuf[sizeof(a_X1) -1 + l] = '\0';
2744 cload = n_var_vlook(vbuf, FAL0);
2746 memcpy(vbuf, a_X2, sizeof(a_X2) -1);
2747 memcpy(&vbuf[sizeof(a_X2) -1], lext, l);
2748 vbuf[sizeof(a_X2) -1 + l] = '\0';
2749 csave = n_var_vlook(vbuf, FAL0);
2751 #undef a_X2
2752 #undef a_X1
2753 n_lofi_free(vbuf);
2755 if((csave != NULL) | (cload != NULL)){
2756 n_OBSOLETE("*file-hook-{load,save}-EXTENSION* will vanish, "
2757 "please use the `filetype' command");
2759 if(((csave != NULL) ^ (cload != NULL)) == 0){
2760 if(res_or_null != NULL){
2761 res_or_null->ft_ext_dat = lext;
2762 res_or_null->ft_ext_len = l;
2763 res_or_null->ft_load_dat = cload;
2764 res_or_null->ft_load_len = strlen(cload);
2765 res_or_null->ft_save_dat = csave;
2766 res_or_null->ft_save_len = strlen(csave);
2768 goto jleave;
2769 }else
2770 n_alert(_("Incomplete *file-hook-{load,save}-EXTENSION* for: .%s"),
2771 lext);
2775 lext = NULL;
2777 jleave:
2778 NYD2_LEAVE;
2779 return (lext != NULL);
2782 /* s-it-mode */